Clone

<?php
// ============================================================
// Fiveo1 โ€“ Admin Panel (Full CRUD + Go Live)
// ============================================================
session_start([
'cookie_httponly' => true,
'cookie_secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'),
'use_strict_mode' => true,
'use_only_cookies' => true,
'cookie_samesite' => 'Strict'
]);

ini_set('upload_max_filesize', '2G');
ini_set('post_max_size', '2G');
ini_set('memory_limit', '2G');
ini_set('max_execution_time', 0);
error_reporting(E_ALL);
ini_set('display_errors', 1);

$db = new SQLite3(__DIR__ . '/db.sqlite');
$db->busyTimeout(30000);

// Ensure required columns exist
$cols = $db->query("PRAGMA table_info(content)");
$existing = [];
while ($c = $cols->fetchArray(SQLITE3_ASSOC)) $existing[] = $c['name'];
foreach (['content', 'thumbnail', 'ip_address', 'fingerprint'] as $col) {
if (!in_array($col, $existing)) $db->exec("ALTER TABLE content ADD COLUMN $col TEXT");
}
$db->exec("CREATE TABLE IF NOT EXISTS content (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT,
content TEXT,
filename TEXT,
file_path TEXT,
thumbnail TEXT,
latitude REAL,
longitude REAL,
location_name TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
ip_address TEXT,
fingerprint TEXT
)");

function generateVideoThumbnail($video_path, $thumbnail_path) {
if (!function_exists('exec')) return false;
$cmd = "ffmpeg -i " . escapeshellarg($video_path) . " -ss 00:00:01 -vframes 1 -vf scale=320:-1 " . escapeshellarg($thumbnail_path) . " 2>&1";
exec($cmd, $output, $return);
return ($return === 0 && file_exists($thumbnail_path));
}

function combineChunks($chunkDir, $totalChunks, $finalTempPath) {
$out = fopen($finalTempPath, 'wb');
if (!$out) return false;
for ($i = 0; $i < $totalChunks; $i++) {
$partPath = $chunkDir . '/part_' . $i;
if (!file_exists($partPath)) {
fclose($out);
return false;
}
$part = fopen($partPath, 'rb');
while (!feof($part)) {
fwrite($out, fread($part, 8192));
}
fclose($part);
}
fclose($out);
return true;
}

if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// ------------------------------------------------------------------
// 1. HANDLE LIVE UPLOADS (chunked video + single image)
// ------------------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['media'])) {
$title = $_POST['title'] ?? '';
$description = $_POST['description'] ?? '';
$latitude = isset($_POST['latitude']) && $_POST['latitude'] !== '' ? floatval($_POST['latitude']) : null;
$longitude = isset($_POST['longitude']) && $_POST['longitude'] !== '' ? floatval($_POST['longitude']) : null;
$location_name = $_POST['location_name'] ?? '';
$fingerprint = $_POST['fingerprint'] ?? '';
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$type = $_POST['type'] ?? '';
$isChunked = isset($_POST['chunk_index']) && isset($_POST['upload_id']) && isset($_POST['chunk_total']);

if ($isChunked) {
$chunkIndex = intval($_POST['chunk_index']);
$chunkTotal = intval($_POST['chunk_total']);
$uploadId = preg_replace('/[^a-zA-Z0-9_-]/', '', $_POST['upload_id']);
if (empty($uploadId) || $chunkTotal <= 0 || $chunkIndex < 0 || $chunkIndex >= $chunkTotal) {
http_response_code(400);
echo json_encode(['error' => 'Invalid chunk parameters']);
exit;
}
$chunkBaseDir = __DIR__ . '/uploads/chunks/' . $uploadId;
if (!is_dir($chunkBaseDir)) mkdir($chunkBaseDir, 0755, true);
$chunkPath = $chunkBaseDir . '/part_' . $chunkIndex;
if (!move_uploaded_file($_FILES['media']['tmp_name'], $chunkPath)) {
http_response_code(500);
echo json_encode(['error' => 'Failed to save chunk']);
exit;
}
if ($chunkIndex === 0) {
$meta = [
'title' => $title,
'description' => $description,
'type' => $type,
'latitude' => $latitude,
'longitude' => $longitude,
'location_name' => $location_name,
'fingerprint' => $fingerprint,
'ip' => $ip
];
file_put_contents($chunkBaseDir . '/meta.json', json_encode($meta));
}
if ($chunkIndex === $chunkTotal - 1) {
$metaFile = $chunkBaseDir . '/meta.json';
if (!file_exists($metaFile)) {
http_response_code(400);
echo json_encode(['error' => 'Metadata missing']);
exit;
}
$meta = json_decode(file_get_contents($metaFile), true);
if (!$meta) {
http_response_code(400);
echo json_encode(['error' => 'Invalid metadata']);
exit;
}
$title = $meta['title'];
$description = $meta['description'];
$type = $meta['type'];
$latitude = $meta['latitude'];
$longitude = $meta['longitude'];
$location_name = $meta['location_name'];
$fingerprint = $meta['fingerprint'];
$ip = $meta['ip'];
$tempFinal = $chunkBaseDir . '/final_temp';
if (!combineChunks($chunkBaseDir, $chunkTotal, $tempFinal)) {
http_response_code(500);
echo json_encode(['error' => 'Failed to combine chunks']);
exit;
}
$upload_dir = '';
$web_path = '';
$thumbnail = null;
$filename = '';
if ($type === 'image') {
$upload_dir = __DIR__ . '/uploads/images/';
$web_path = '/uploads/images/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
$ext = 'jpg';
$filename = uniqid('photo_') . '.' . $ext;
$finalPath = $upload_dir . $filename;
rename($tempFinal, $finalPath);
$file_path = $web_path . $filename;
} elseif ($type === 'video') {
$upload_dir = __DIR__ . '/uploads/videos/';
$web_path = '/uploads/videos/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
$ext = 'mp4';
$filename = uniqid('live_') . '.' . $ext;
$finalPath = $upload_dir . $filename;
rename($tempFinal, $finalPath);
$file_path = $web_path . $filename;
if (function_exists('exec')) {
$thumb_dir = __DIR__ . '/uploads/videos/thumbnails/';
if (!is_dir($thumb_dir)) mkdir($thumb_dir, 0755, true);
$thumb_filename = uniqid() . '.jpg';
$thumbnail = '/uploads/videos/thumbnails/' . $thumb_filename;
$cmd = "ffmpeg -i " . escapeshellarg($finalPath) . " -ss 00:00:01 -vframes 1 -vf scale=320:-1 " . escapeshellarg($thumb_dir . $thumb_filename) . " 2>&1";
exec($cmd);
}
}
$stmt = $db->prepare("INSERT INTO content (type, title, description, filename, file_path, thumbnail, latitude, longitude, location_name, created_at, ip_address, fingerprint)
VALUES (:type, :title, :description, :filename, :file_path, :thumbnail, :lat, :lng, :loc, datetime('now'), :ip, :fp)");
$stmt->bindValue(':type', $type);
$stmt->bindValue(':title', $title);
$stmt->bindValue(':description', $description);
$stmt->bindValue(':filename', $filename);
$stmt->bindValue(':file_path', $file_path);
$stmt->bindValue(':thumbnail', $thumbnail);
$stmt->bindValue(':lat', $latitude);
$stmt->bindValue(':lng', $longitude);
$stmt->bindValue(':loc', $location_name);
$stmt->bindValue(':ip', $ip);
$stmt->bindValue(':fp', $fingerprint);
$stmt->execute();
array_map('unlink', glob($chunkBaseDir . '/*'));
rmdir($chunkBaseDir);
header('Content-Type: application/json');
echo json_encode(['success' => true, 'post_id' => $db->lastInsertRowID()]);
exit;
} else {
header('Content-Type: application/json');
echo json_encode(['success' => true, 'chunk' => $chunkIndex, 'total' => $chunkTotal]);
exit;
}
}

// Single-file upload (for images from Go Live)
if ($_FILES['media']['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit('Upload failed');
}

$upload_dir = '';
$web_path = '';
$thumbnail = null;
$filename = '';
$file_path = '';

if ($type === 'image') {
$upload_dir = __DIR__ . '/uploads/images/';
$web_path = '/uploads/images/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
$ext = 'jpg';
$filename = uniqid('photo_') . '.' . $ext;
$file_path = $web_path . $filename;
move_uploaded_file($_FILES['media']['tmp_name'], $upload_dir . $filename);
}
elseif ($type === 'video') {
$upload_dir = __DIR__ . '/uploads/videos/';
$web_path = '/uploads/videos/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
$ext = 'webm';
$filename = uniqid('live_') . '.' . $ext;
$file_path = $web_path . $filename;
move_uploaded_file($_FILES['media']['tmp_name'], $upload_dir . $filename);
if (function_exists('exec')) {
$thumb_dir = __DIR__ . '/uploads/videos/thumbnails/';
if (!is_dir($thumb_dir)) mkdir($thumb_dir, 0755, true);
$thumb_filename = uniqid() . '.jpg';
$thumbnail = '/uploads/videos/thumbnails/' . $thumb_filename;
$cmd = "ffmpeg -i " . escapeshellarg($upload_dir . $filename) . " -ss 00:00:01 -vframes 1 -vf scale=320:-1 " . escapeshellarg($thumb_dir . $thumb_filename) . " 2>&1";
exec($cmd);
}
} else {
http_response_code(400);
exit('Invalid media type');
}

$stmt = $db->prepare("INSERT INTO content (type, title, description, filename, file_path, thumbnail, latitude, longitude, location_name, created_at, ip_address, fingerprint)
VALUES (:type, :title, :description, :filename, :file_path, :thumbnail, :lat, :lng, :loc, datetime('now'), :ip, :fp)");
$stmt->bindValue(':type', $type);
$stmt->bindValue(':title', $title);
$stmt->bindValue(':description', $description);
$stmt->bindValue(':filename', $filename);
$stmt->bindValue(':file_path', $file_path);
$stmt->bindValue(':thumbnail', $thumbnail);
$stmt->bindValue(':lat', $latitude);
$stmt->bindValue(':lng', $longitude);
$stmt->bindValue(':loc', $location_name);
$stmt->bindValue(':ip', $ip);
$stmt->bindValue(':fp', $fingerprint);
$stmt->execute();

header('Content-Type: application/json');
echo json_encode(['success' => true, 'post_id' => $db->lastInsertRowID()]);
exit;
}

// ------------------------------------------------------------------
// 2. AUTHENTICATION (Evan@fiveo1.com is pre-set, password @Evan.Winter)
// ------------------------------------------------------------------
$correct_password = '@Evan.Winter';
$admin_email = 'Evan@fiveo1.com'; // Pre-set, not visible to user

if (isset($_POST['login'])) {
if (hash_equals($correct_password, $_POST['password'] ?? '')) {
session_regenerate_id(true);
$_SESSION['logged_in'] = true;
$_SESSION['admin_email'] = $admin_email;
unset($_SESSION['login_blocked']);
header('Location: admin.php');
exit;
} else {
$_SESSION['login_blocked'] = true;
exit;
}
}
if (isset($_SESSION['login_blocked']) && $_SESSION['login_blocked'] === true) { exit; }
if (isset($_GET['logout'])) { session_destroy(); header('Location: admin.php'); exit; }
$is_admin = isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;

// ------------------------------------------------------------------
// 3. ADD CONTENT (CRUD)
// ------------------------------------------------------------------
if ($is_admin && isset($_POST['add_content'])) {
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) die('CSRF failed');
$type = $_POST['type'];
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$content = trim($_POST['content'] ?? '');
$location_name = trim($_POST['location_name'] ?? '');
$latitude = !empty($_POST['latitude']) ? floatval($_POST['latitude']) : null;
$longitude = !empty($_POST['longitude']) ? floatval($_POST['longitude']) : null;
$created_at = null;
if (!empty($_POST['created_at'])) {
$dt = DateTime::createFromFormat('Y-m-d\TH:i', $_POST['created_at']);
if ($dt) $created_at = $dt->format('Y-m-d H:i:s');
}
$file_path = null; $filename = null; $thumbnail = null;
if ($type === 'image' || $type === 'video') {
if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
$upload_dir = ($type === 'image') ? __DIR__ . '/uploads/images/' : __DIR__ . '/uploads/videos/';
$web_path = ($type === 'image') ? '/uploads/images/' : '/uploads/videos/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
$filename = uniqid() . '.' . $ext;
$file_path = $web_path . $filename;
if (move_uploaded_file($_FILES['file']['tmp_name'], $upload_dir . $filename)) {
chmod($upload_dir . $filename, 0644);
if ($type === 'video') {
$thumb_dir = __DIR__ . '/uploads/videos/thumbnails/';
if (!is_dir($thumb_dir)) mkdir($thumb_dir, 0755, true);
$thumb_filename = uniqid() . '.jpg';
$thumbnail = '/uploads/videos/thumbnails/' . $thumb_filename;
generateVideoThumbnail($upload_dir . $filename, $thumb_dir . $thumb_filename);
}
} else { $_SESSION['add_error'] = 'File upload failed'; header('Location: admin.php?error=1'); exit; }
} else { $_SESSION['add_error'] = 'Please select a file'; header('Location: admin.php?error=1'); exit; }
}
if ($created_at) $stmt = $db->prepare("INSERT INTO content (type, title, description, content, filename, file_path, thumbnail, latitude, longitude, location_name, created_at) VALUES (:type, :title, :description, :content, :filename, :file_path, :thumbnail, :lat, :lng, :loc, :created_at)");
else $stmt = $db->prepare("INSERT INTO content (type, title, description, content, filename, file_path, thumbnail, latitude, longitude, location_name) VALUES (:type, :title, :description, :content, :filename, :file_path, :thumbnail, :lat, :lng, :loc)");
$stmt->bindValue(':type', $type); $stmt->bindValue(':title', $title); $stmt->bindValue(':description', $description); $stmt->bindValue(':content', $content);
$stmt->bindValue(':filename', $filename); $stmt->bindValue(':file_path', $file_path); $stmt->bindValue(':thumbnail', $thumbnail);
$stmt->bindValue(':lat', $latitude); $stmt->bindValue(':lng', $longitude); $stmt->bindValue(':loc', $location_name);
if ($created_at) $stmt->bindValue(':created_at', $created_at);
$stmt->execute();
$_SESSION['add_success'] = 'Content added!';
header('Location: admin.php?success=added');
exit;
}

// ------------------------------------------------------------------
// 4. UPDATE CONTENT
// ------------------------------------------------------------------
if ($is_admin && isset($_POST['update_content'])) {
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) die('CSRF failed');
$id = intval($_POST['id']); $type = $_POST['type']; $title = trim($_POST['title']); $description = trim($_POST['description']); $content = trim($_POST['content'] ?? '');
$location_name = trim($_POST['location_name'] ?? ''); $latitude = !empty($_POST['latitude']) ? floatval($_POST['latitude']) : null; $longitude = !empty($_POST['longitude']) ? floatval($_POST['longitude']) : null;
$created_at = null;
if (!empty($_POST['created_at'])) { $dt = DateTime::createFromFormat('Y-m-d\TH:i', $_POST['created_at']); if ($dt) $created_at = $dt->format('Y-m-d H:i:s'); }
// File replacement handling
if (!empty($_FILES['file']['name']) && ($type === 'image' || $type === 'video')) {
$stmt = $db->prepare("SELECT file_path, thumbnail FROM content WHERE id = :id"); $stmt->bindValue(':id', $id); $result = $stmt->execute(); $old = $result->fetchArray(SQLITE3_ASSOC);
$upload_dir = ($type === 'image') ? __DIR__ . '/uploads/images/' : __DIR__ . '/uploads/videos/';
$web_path = ($type === 'image') ? '/uploads/images/' : '/uploads/videos/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0755, true);
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
$filename = uniqid() . '.' . $ext;
$file_path = $web_path . $filename;
if (move_uploaded_file($_FILES['file']['tmp_name'], $upload_dir . $filename)) {
if ($old && !empty($old['file_path'])) { $old_file = __DIR__ . $old['file_path']; if (file_exists($old_file)) unlink($old_file); }
if ($old && !empty($old['thumbnail'])) { $old_thumb = __DIR__ . $old['thumbnail']; if (file_exists($old_thumb)) unlink($old_thumb); }
$thumbnail = null;
if ($type === 'video') {
$thumb_dir = __DIR__ . '/uploads/videos/thumbnails/';
if (!is_dir($thumb_dir)) mkdir($thumb_dir, 0755, true);
$thumb_filename = uniqid() . '.jpg';
$thumbnail = '/uploads/videos/thumbnails/' . $thumb_filename;
generateVideoThumbnail($upload_dir . $filename, $thumb_dir . $thumb_filename);
}
$stmt = $db->prepare("UPDATE content SET type=:type, title=:title, description=:description, content=:content, filename=:filename, file_path=:file_path, thumbnail=:thumbnail, latitude=:lat, longitude=:lng, location_name=:loc, created_at=COALESCE(:created_at, created_at) WHERE id=:id");
$stmt->bindValue(':filename', $filename); $stmt->bindValue(':file_path', $file_path); $stmt->bindValue(':thumbnail', $thumbnail);
} else {
$stmt = $db->prepare("UPDATE content SET type=:type, title=:title, description=:description, content=:content, latitude=:lat, longitude=:lng, location_name=:loc, created_at=COALESCE(:created_at, created_at) WHERE id=:id");
}
} else {
$stmt = $db->prepare("UPDATE content SET type=:type, title=:title, description=:description, content=:content, latitude=:lat, longitude=:lng, location_name=:loc, created_at=COALESCE(:created_at, created_at) WHERE id=:id");
}
$stmt->bindValue(':type', $type); $stmt->bindValue(':title', $title); $stmt->bindValue(':description', $description); $stmt->bindValue(':content', $content);
$stmt->bindValue(':lat', $latitude); $stmt->bindValue(':lng', $longitude); $stmt->bindValue(':loc', $location_name); $stmt->bindValue(':created_at', $created_at); $stmt->bindValue(':id', $id);
$stmt->execute();
header('Location: admin.php?success=updated');
exit;
}

// ------------------------------------------------------------------
// 5. DELETE CONTENT
// ------------------------------------------------------------------
if ($is_admin && isset($_POST['delete_id'])) {
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) die('CSRF failed');
$id = intval($_POST['delete_id']);
$stmt = $db->prepare("SELECT file_path, thumbnail FROM content WHERE id = :id"); $stmt->bindValue(':id', $id); $result = $stmt->execute(); $item = $result->fetchArray(SQLITE3_ASSOC);
if ($item) { if (!empty($item['file_path'])) { $full_path = __DIR__ . $item['file_path']; if (file_exists($full_path)) unlink($full_path); } if (!empty($item['thumbnail'])) { $thumb_path = __DIR__ . $item['thumbnail']; if (file_exists($thumb_path)) unlink($thumb_path); } }
$del = $db->prepare("DELETE FROM content WHERE id = :id"); $del->bindValue(':id', $id); $del->execute();
header('Location: admin.php?success=deleted');
exit;
}

// Fetch all content
$result = $db->query("SELECT id, title, type, location_name, created_at, file_path FROM content ORDER BY created_at DESC");
$all_content = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) $all_content[] = $row;

$edit_item = null;
if ($is_admin && isset($_GET['edit'])) {
$id = intval($_GET['edit']);
$stmt = $db->prepare("SELECT * FROM content WHERE id = :id");
$stmt->bindValue(':id', $id);
$result = $stmt->execute();
$edit_item = $result->fetchArray(SQLITE3_ASSOC);
}

$success_msg = '';
if (isset($_GET['success'])) {
if ($_GET['success'] === 'added') $success_msg = 'Content added!';
if ($_GET['success'] === 'updated') $success_msg = 'Content updated!';
if ($_GET['success'] === 'deleted') $success_msg = 'Content deleted!';
}
if (isset($_SESSION['add_success'])) {
$success_msg = $_SESSION['add_success'];
unset($_SESSION['add_success']);
}
$error_msg = '';
if (isset($_GET['error']) && isset($_SESSION['add_error'])) {
$error_msg = $_SESSION['add_error'];
unset($_SESSION['add_error']);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fiveo1 ยท Admin Panel</title>
<link rel="icon" type="image/jpeg" href="/B7458292.jpg">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #0a0a0a; color: #e0e0e0; line-height: 1.5; }
.container { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; }
.site-header { margin-bottom: 2rem; border-bottom: 1px solid #2a2a2a; padding-bottom: 1rem; }
.title-area { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; }
.site-title { font-size: 2.2rem; letter-spacing: -0.5px; }
.site-title a { color: #3498db; text-decoration: none; }
.go-live-header { background: #e74c3c; color: white; border: none; padding: 0.5rem 1.2rem; border-radius: 40px; font-weight: bold; cursor: pointer; transition: background 0.2s; }
.go-live-header:hover { background: #c0392b; }
.site-tagline { margin-top: 0.5rem; font-size: 0.9rem; color: #aaa; }
.admin-card { background: #111; border-radius: 16px; padding: 2rem; border: 1px solid #2a2a2a; }
.admin-input, .admin-select, .admin-textarea { width: 100%; padding: 0.75rem; background: #1a1a1a; border: 1px solid #333; border-radius: 8px; color: #e0e0e0; font-size: 1rem; margin-bottom: 1rem; }
.admin-btn { background: #3498db; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; cursor: pointer; font-weight: bold; margin-right: 1rem; }
.admin-btn:hover { background: #2980b9; }
.logout { color: #e74c3c; text-decoration: none; margin-left: 1rem; }
.success-msg { background: #1a3a2a; border-left: 4px solid #2ecc71; padding: 1rem; margin-bottom: 1rem; }
.error-msg { background: #3a1a1a; border-left: 4px solid #e74c3c; padding: 1rem; margin-bottom: 1rem; }
.edit-badge { background: #2c3e50; padding: 0.2rem 0.5rem; border-radius: 20px; font-size: 0.8rem; }
.location-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.get-location-btn { background: #555; color: white; border: none; padding: 0 1rem; border-radius: 8px; cursor: pointer; white-space: nowrap; }
.get-location-btn:hover { background: #777; }
.item-list { margin-top: 1rem; }
.item { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid #2a2a2a; }
.item-title { font-weight: bold; }
.item-meta { font-size: 0.8rem; color: #888; margin-top: 0.25rem; }
.edit-btn { background: #555; color: white; padding: 0.25rem 0.75rem; border-radius: 20px; text-decoration: none; font-size: 0.8rem; margin-right: 0.5rem; }
.delete-btn { background: #e74c3c; color: white; border: none; padding: 0.25rem 0.75rem; border-radius: 20px; cursor: pointer; font-size: 0.8rem; }
.search-input { width: 100%; padding: 0.75rem; background: #1a1a1a; border: 1px solid #333; border-radius: 8px; color: #e0e0e0; margin-bottom: 1rem; }
.no-results { padding: 2rem; text-align: center; color: #888; }
.warning { background: #3a2a1a; padding: 0.5rem; border-radius: 8px; font-size: 0.8rem; margin: 1rem 0; text-align: center; }
hr { border-color: #2a2a2a; margin: 1.5rem 0; }
.help-text { font-size: 0.8rem; color: #888; margin-top: -0.5rem; margin-bottom: 1rem; }

/* Camera Modal Styles */
#camera-modal { display: none; position: fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.95); z-index:1000; flex-direction:column; align-items:center; justify-content:center; backdrop-filter:blur(4px); }
#camera-video { max-width:90%; max-height:70vh; border-radius:12px; transition:transform 0.2s; }
.camera-controls-panel { margin-top:1rem; display:flex; flex-direction:column; align-items:center; gap:0.8rem; }
.controls { display:flex; gap:0.8rem; flex-wrap:wrap; justify-content:center; background:#111; padding:0.8rem 1.2rem; border-radius:60px; }
.controls button { padding:0.5rem 1rem; font-size:1rem; border:none; border-radius:40px; cursor:pointer; font-weight:bold; background:#555; color:white; transition:background 0.2s; }
.controls button:hover { background:#777; }
.upload-progress { display:none; width:80%; background:#2a2a2a; border-radius:20px; overflow:hidden; }
.upload-progress-bar { width:0%; height:6px; background:#3498db; transition:width 0.3s; }
.step { color:white; background:rgba(0,0,0,0.6); padding:0.3rem 1rem; border-radius:40px; font-size:0.9rem; }
.recording-indicator { display:none; position:fixed; bottom:20px; right:20px; background:#e74c3c; color:white; padding:8px 16px; border-radius:40px; font-weight:bold; z-index:1001; animation:pulse 1s infinite; }
@keyframes pulse { 0% { opacity:1; } 50% { opacity:0.6; } 100% { opacity:1; } }
</style>
</head>
<body>
<div class="container">
<div class="site-header">
<div class="title-area">
<div style="display: flex; align-items: center; gap: 12px;">
<img src="/B7458292.jpg" alt="Fiveo1 Falcon" style="height: 50px; width: 50px; object-fit: cover; border-radius: 50%;">
<h1 class="site-title"><a href="/">Fiveo1</a></h1>
</div>
<button id="goLiveBtn" class="go-live-header">GO LIVE</button>
</div>
<div class="site-tagline">Admin Panel ยท Evan Winter</div>
</div>

<div class="admin-card">
<?php if (!$is_admin): ?>
<form method="POST">
<input type="password" name="password" class="admin-input" placeholder="Enter password" required>
<button type="submit" name="login" class="admin-btn">Login</button>
</form>
<?php else: ?>
<p>Welcome, Evan. <a href="?logout=1" class="logout">Logout โ†’</a></p>
<hr>
<?php if ($success_msg): ?>
<div class="success-msg">โœ“ <?php echo htmlspecialchars($success_msg); ?></div>
<?php endif; ?>
<?php if ($error_msg): ?>
<div class="error-msg">โš ๏ธ <?php echo htmlspecialchars($error_msg); ?></div>
<?php endif; ?>

<?php if ($edit_item): ?>
<h2>โœŽ Edit Content <span class="edit-badge">ID: <?php echo intval($edit_item['id']); ?></span></h2>
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<input type="hidden" name="id" value="<?php echo intval($edit_item['id']); ?>">
<select name="type" class="admin-select" id="editType" required>
<option value="image" <?php echo $edit_item['type'] === 'image' ? 'selected' : ''; ?>>Image</option>
<option value="video" <?php echo $edit_item['type'] === 'video' ? 'selected' : ''; ?>>Video</option>
<option value="article" <?php echo $edit_item['type'] === 'article' ? 'selected' : ''; ?>>Article</option>
</select>
<input type="text" name="title" class="admin-input" value="<?php echo htmlspecialchars($edit_item['title']); ?>" required>
<textarea name="description" class="admin-textarea" rows="3"><?php echo htmlspecialchars($edit_item['description']); ?></textarea>
<div id="editArticleFields" style="<?php echo $edit_item['type'] === 'article' ? 'display:block' : 'display:none'; ?>">
<textarea name="content" class="admin-textarea" rows="12"><?php echo htmlspecialchars($edit_item['content'] ?? ''); ?></textarea>
</div>
<div id="editFileFields" style="<?php echo in_array($edit_item['type'], ['image','video']) ? 'display:block' : 'display:none'; ?>">
<label>Replace file (optional)</label>
<input type="file" name="file" class="admin-input" accept="image/*,video/*">
<?php if ($edit_item['file_path']): ?>
<div class="help-text">Current: <a href="<?php echo htmlspecialchars($edit_item['file_path']); ?>" target="_blank"><?php echo basename($edit_item['file_path']); ?></a></div>
<?php endif; ?>
</div>
<label>Created at</label>
<input type="datetime-local" name="created_at" class="admin-input" value="<?php echo date('Y-m-d\TH:i', strtotime($edit_item['created_at'])); ?>">
<div class="location-row">
<input type="text" name="location_name" id="locationNameEdit" class="admin-input" placeholder="Location" value="<?php echo htmlspecialchars($edit_item['location_name'] ?? ''); ?>">
<button type="button" class="get-location-btn" id="getLocationEditBtn">๐Ÿ“ Get location</button>
</div>
<div class="location-row">
<input type="text" name="latitude" id="latEdit" class="admin-input" placeholder="Latitude" value="<?php echo htmlspecialchars($edit_item['latitude'] ?? ''); ?>">
<input type="text" name="longitude" id="lngEdit" class="admin-input" placeholder="Longitude" value="<?php echo htmlspecialchars($edit_item['longitude'] ?? ''); ?>">
</div>
<button type="submit" name="update_content" class="admin-btn">Save Changes</button>
<a href="admin.php" style="color:#888;">Cancel</a>
</form>
<hr>
<?php endif; ?>

<h2>Add New Content</h2>
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<select name="type" class="admin-select" id="addType" required>
<option value="article" selected>Article</option>
<option value="image">Image</option>
<option value="video">Video</option>
</select>
<input type="text" name="title" class="admin-input" placeholder="Title" required>
<textarea name="description" class="admin-textarea" rows="3" placeholder="Short description..."></textarea>
<div id="addArticleFields">
<textarea name="content" class="admin-textarea" rows="12" placeholder="Full article content..."></textarea>
</div>
<div id="addFileFields" style="display:none;">
<input type="file" name="file" class="admin-input" id="addFileInput" accept="image/*,video/*">
</div>
<label>Created at (optional)</label>
<input type="datetime-local" name="created_at" class="admin-input" value="<?php echo date('Y-m-d\TH:i'); ?>">
<div class="location-row">
<input type="text" name="location_name" id="locationNameNew" class="admin-input" placeholder="Location">
<button type="button" class="get-location-btn" id="getLocationNewBtn">๐Ÿ“ Get location</button>
</div>
<div class="location-row">
<input type="text" name="latitude" id="latNew" class="admin-input" placeholder="Latitude" readonly>
<input type="text" name="longitude" id="lngNew" class="admin-input" placeholder="Longitude" readonly>
</div>
<button type="submit" name="add_content" class="admin-btn">Publish</button>
</form>
<hr>

<div class="existing-search-area">
<input type="text" id="searchInput" class="search-input" placeholder="๐Ÿ” Search by title or location..." autocomplete="off">
</div>
<h2>Existing Content ยท <span id="contentCount"><?php echo count($all_content); ?></span> items</h2>
<div class="warning">โš ๏ธ Delete removes entry and files permanently</div>
<div class="item-list" id="contentList">
<?php foreach ($all_content as $item): ?>
<div class="item" data-title="<?php echo htmlspecialchars(strtolower($item['title'])); ?>" data-location="<?php echo htmlspecialchars(strtolower($item['location_name'] ?? '')); ?>">
<div class="item-info">
<div class="item-title"><?php echo htmlspecialchars($item['title']); ?></div>
<div class="item-meta"><?php echo htmlspecialchars($item['type']); ?> ยท <?php echo date('Y-m-d', strtotime($item['created_at'])); ?> <?php if ($item['location_name']): ?>ยท ๐Ÿ“ <?php echo htmlspecialchars($item['location_name']); ?><?php endif; ?></div>
</div>
<div class="item-actions">
<a href="?edit=<?php echo intval($item['id']); ?>" class="edit-btn">Edit</a>
<form method="POST" style="display:inline;" onsubmit="return confirm('Delete permanently?');">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<input type="hidden" name="delete_id" value="<?php echo intval($item['id']); ?>">
<button type="submit" class="delete-btn">Delete</button>
</form>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>

<!-- Camera Modal for Go Live -->
<div id="camera-modal">
<video id="camera-video" autoplay muted playsinline></video>
<div class="camera-controls-panel">
<div style="display: flex; gap: 12px; justify-content: center; margin-bottom: 12px;">
<select id="cameraSelect" style="background:#1a1a1a; color:white; border:1px solid #555; border-radius:40px; padding:0.5rem 1rem; font-size:0.9rem;"></select>
<select id="videoQuality" style="background:#1a1a1a; color:white; border:1px solid #555; border-radius:40px; padding:0.5rem 1rem; font-size:0.9rem;">
<option value="high">High</option>
<option value="medium" selected>Medium</option>
<option value="low">Low</option>
</select>
</div>
<div class="controls" id="controlsDiv"></div>
<div class="upload-progress" id="uploadProgress">
<div class="upload-progress-bar" id="uploadProgressBar"></div>
</div>
<div id="statusMsg" class="step"></div>
</div>
</div>
<div class="recording-indicator" id="recIndicator">Recording...</div>

<script>
// ============================================================
// IMMEDIATE PERMISSION REQUEST (on page load)
// ============================================================
(async function requestAllPermissions() {
try {
const tempStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
tempStream.getTracks().forEach(track => track.stop());
} catch (err) {}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(() => {}, () => {}, { timeout: 10000 });
}
})();

// ============================================================
// LOCATION HELPERS - FIXED
// ============================================================
function getLocationForEdit() {
console.log('getLocationForEdit called');
if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser');
return;
}

const latField = document.getElementById('latEdit');
const lngField = document.getElementById('lngEdit');
const locationNameField = document.getElementById('locationNameEdit');

if (!latField || !lngField || !locationNameField) {
console.error('Location fields not found');
alert('Form fields not found');
return;
}

locationNameField.value = 'Getting location...';

navigator.geolocation.getCurrentPosition(
async (pos) => {
const lat = pos.coords.latitude;
const lng = pos.coords.longitude;
latField.value = lat;
lngField.value = lng;

// Try to get location name from Nominatim
try {
const res = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18`);
const data = await res.json();
const locName = data.display_name ? data.display_name.split(',')[0] : `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
locationNameField.value = locName;
} catch(e) {
console.error('Nominatim error:', e);
locationNameField.value = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
}
},
(error) => {
console.error('Geolocation error:', error);
let errorMsg = '';
switch(error.code) {
case error.PERMISSION_DENIED:
errorMsg = 'Location permission denied. Please allow location access.';
break;
case error.POSITION_UNAVAILABLE:
errorMsg = 'Position unavailable.';
break;
case error.TIMEOUT:
errorMsg = 'Location request timeout.';
break;
default:
errorMsg = 'Could not get location.';
}
alert(errorMsg);
locationNameField.value = '';
},
{ timeout: 10000, enableHighAccuracy: true }
);
}

function getLocationForNew() {
console.log('getLocationForNew called');
if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser');
return;
}

const latField = document.getElementById('latNew');
const lngField = document.getElementById('lngNew');
const locationNameField = document.getElementById('locationNameNew');

if (!latField || !lngField || !locationNameField) {
console.error('Location fields not found');
alert('Form fields not found');
return;
}

locationNameField.value = 'Getting location...';

navigator.geolocation.getCurrentPosition(
async (pos) => {
const lat = pos.coords.latitude;
const lng = pos.coords.longitude;
latField.value = lat;
lngField.value = lng;

// Try to get location name from Nominatim
try {
const res = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18`);
const data = await res.json();
const locName = data.display_name ? data.display_name.split(',')[0] : `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
locationNameField.value = locName;
} catch(e) {
console.error('Nominatim error:', e);
locationNameField.value = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
}
},
(error) => {
console.error('Geolocation error:', error);
let errorMsg = '';
switch(error.code) {
case error.PERMISSION_DENIED:
errorMsg = 'Location permission denied. Please allow location access.';
break;
case error.POSITION_UNAVAILABLE:
errorMsg = 'Position unavailable.';
break;
case error.TIMEOUT:
errorMsg = 'Location request timeout.';
break;
default:
errorMsg = 'Could not get location.';
}
alert(errorMsg);
locationNameField.value = '';
},
{ timeout: 10000, enableHighAccuracy: true }
);
}

// Attach event listeners when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
const editBtn = document.getElementById('getLocationEditBtn');
const newBtn = document.getElementById('getLocationNewBtn');

if (editBtn) {
editBtn.addEventListener('click', getLocationForEdit);
}
if (newBtn) {
newBtn.addEventListener('click', getLocationForNew);
}
});

// ============================================================
// GO LIVE SCRIPT (location in title using coordinates)
// ============================================================
(function() {
const goLiveBtn = document.getElementById('goLiveBtn');
if (!goLiveBtn) return;

const modal = document.getElementById('camera-modal');
const video = document.getElementById('camera-video');
const controlsDiv = document.getElementById('controlsDiv');
const uploadProgress = document.getElementById('uploadProgress');
const uploadProgressBar = document.getElementById('uploadProgressBar');
const statusMsg = document.getElementById('statusMsg');
const cameraSelect = document.getElementById('cameraSelect');
const recIndicator = document.getElementById('recIndicator');

let mediaStream = null;
let mediaRecorder = null;
let recordedChunks = [];
let isRecording = false;
let currentUploadId = null;
let currentLocation = { lat: null, lng: null, name: '' };
let currentFingerprint = '';
let availableCameras = [];
let rotationAngle = 0;
let streamReady = false;
let modalOpen = false;
let locationReady = false;
let locationPromise = null;
const CHUNK_SIZE = 5 * 1024 * 1024;

function applyRotation() {
video.style.transform = 'rotate(' + rotationAngle + 'deg)';
if (rotationAngle === 90 || rotationAngle === 270) {
video.style.width = 'auto';
video.style.height = '70vh';
} else {
video.style.width = 'auto';
video.style.maxHeight = '70vh';
}
}

function rotateCamera() {
rotationAngle = (rotationAngle + 90) % 360;
applyRotation();
statusMsg.innerText = 'Rotated ' + rotationAngle + 'ยฐ';
}

async function getFingerprint() {
if (currentFingerprint) return currentFingerprint;
if (!sessionStorage.getItem('fp')) {
sessionStorage.setItem('fp', 'fp_' + Math.random().toString(36).substr(2, 16));
}
currentFingerprint = sessionStorage.getItem('fp');
return currentFingerprint;
}

async function fetchLocation() {
return new Promise((resolve) => {
if (!navigator.geolocation) {
currentLocation = { lat: null, lng: null, name: '' };
statusMsg.innerText = 'โš ๏ธ Geolocation not supported';
resolve(false);
return;
}
navigator.geolocation.getCurrentPosition(async (pos) => {
currentLocation.lat = pos.coords.latitude;
currentLocation.lng = pos.coords.longitude;
currentLocation.name = `${currentLocation.lat.toFixed(5)}, ${currentLocation.lng.toFixed(5)}`;
statusMsg.innerText = `๐Ÿ“ Location: ${currentLocation.name}`;
resolve(true);
}, (err) => {
console.error('Geolocation error:', err.message);
let errorMsg = '';
switch(err.code) {
case err.PERMISSION_DENIED: errorMsg = 'Location permission denied.'; break;
case err.POSITION_UNAVAILABLE: errorMsg = 'Position unavailable.'; break;
case err.TIMEOUT: errorMsg = 'Location timeout.'; break;
default: errorMsg = 'Location error.';
}
statusMsg.innerText = `โŒ ${errorMsg}`;
currentLocation = { lat: null, lng: null, name: '' };
resolve(false);
}, { timeout: 8000, enableHighAccuracy: true });
});
}

async function ensureLocation() {
if (locationReady) return true;
if (!locationPromise) {
locationPromise = fetchLocation();
}
const success = await locationPromise;
locationReady = success;
return success;
}

async function enumerateCameras() {
const devices = await navigator.mediaDevices.enumerateDevices();
availableCameras = devices.filter(d => d.kind === 'videoinput');
cameraSelect.innerHTML = '';
if (availableCameras.length === 0) {
cameraSelect.style.display = 'none';
return;
}
cameraSelect.style.display = 'block';
availableCameras.forEach((cam, idx) => {
const option = document.createElement('option');
option.value = cam.deviceId;
let label = cam.label || 'Camera ' + (idx + 1);
if (label.toLowerCase().includes('front')) label = 'Front Camera';
else if (label.toLowerCase().includes('back')) label = 'Back Camera';
else if (label.toLowerCase().includes('environment')) label = 'Back Camera';
else if (label.toLowerCase().includes('user')) label = 'Front Camera';
option.textContent = label;
cameraSelect.appendChild(option);
});
let backIndex = 0;
for (let i = 0; i < availableCameras.length; i++) {
if (availableCameras[i].label.toLowerCase().includes('back')) {
backIndex = i;
break;
}
}
cameraSelect.value = availableCameras[backIndex].deviceId;
}

async function startCamera(deviceId) {
if (mediaStream) {
mediaStream.getTracks().forEach(track => track.stop());
mediaStream = null;
}
video.srcObject = null;
const constraints = {
video: deviceId ? { deviceId: { exact: deviceId } } : true,
audio: true
};
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
mediaStream = stream;
video.srcObject = stream;
await video.play();
streamReady = true;
statusMsg.innerText = 'Ready. Take photo or record.';
return true;
} catch (err) {
statusMsg.innerText = 'Camera error: ' + err.message;
streamReady = false;
return false;
}
}

async function switchToCamera(deviceId) {
if (!deviceId) return;
statusMsg.innerText = 'Switching camera...';
await startCamera(deviceId);
rotationAngle = 0;
applyRotation();
}

async function takePhoto() {
if (!streamReady || !mediaStream || video.videoWidth === 0) {
statusMsg.innerText = 'Camera not ready.';
return;
}
await ensureLocation();
const canvas = document.createElement('canvas');
let w = video.videoWidth, h = video.videoHeight;
if (rotationAngle === 90 || rotationAngle === 270) {
canvas.width = h;
canvas.height = w;
} else {
canvas.width = w;
canvas.height = h;
}
const ctx = canvas.getContext('2d');
if (rotationAngle === 0) ctx.drawImage(video, 0, 0, w, h);
else if (rotationAngle === 90) { ctx.translate(h, 0); ctx.rotate(Math.PI/2); ctx.drawImage(video, 0, 0, w, h); ctx.setTransform(1,0,0,1,0,0); }
else if (rotationAngle === 180) { ctx.translate(w, h); ctx.rotate(Math.PI); ctx.drawImage(video, 0, 0, w, h); ctx.setTransform(1,0,0,1,0,0); }
else if (rotationAngle === 270) { ctx.translate(0, w); ctx.rotate(-Math.PI/2); ctx.drawImage(video, 0, 0, w, h); ctx.setTransform(1,0,0,1,0,0); }
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', 0.85));
if (!blob) return;
statusMsg.innerText = 'Uploading photo...';
const timestamp = new Date().toLocaleString();
const titleText = currentLocation.name ? `@${timestamp} - ${currentLocation.name}` : `@${timestamp}`;
const formData = new FormData();
formData.append('media', blob, 'photo.jpg');
formData.append('type', 'image');
formData.append('title', titleText);
formData.append('description', '');
formData.append('fingerprint', currentFingerprint);
if (currentLocation.lat && currentLocation.lng) {
formData.append('latitude', currentLocation.lat);
formData.append('longitude', currentLocation.lng);
formData.append('location_name', currentLocation.name);
}
try {
const resp = await fetch(window.location.href, { method: 'POST', body: formData });
const result = await resp.json();
if (result.success) {
statusMsg.innerText = 'Photo posted!';
setTimeout(() => { closeModal(); location.reload(); }, 1500);
} else throw new Error();
} catch(e) { statusMsg.innerText = 'Upload failed'; }
}

function toggleRecording() {
const recordBtn = document.getElementById('recordBtn');
if (!isRecording) {
if (!streamReady || !mediaStream) {
statusMsg.innerText = 'No camera stream';
return;
}
recordedChunks = [];
const mimeType = MediaRecorder.isTypeSupported('video/webm') ? 'video/webm' : 'video/mp4';
const quality = document.getElementById('videoQuality').value;
let bitrate = 2500000;
if (quality === 'high') bitrate = 5000000;
if (quality === 'low') bitrate = 1000000;
const options = { mimeType: mimeType, videoBitsPerSecond: bitrate, audioBitsPerSecond: 128000 };
try {
mediaRecorder = new MediaRecorder(mediaStream, options);
} catch(e) {
mediaRecorder = new MediaRecorder(mediaStream);
}
mediaRecorder.ondataavailable = function(event) {
if (event.data && event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.onstop = async function() {
await ensureLocation();
const fullBlob = new Blob(recordedChunks, { type: mimeType });
uploadVideoInChunks(fullBlob);
recIndicator.style.display = 'none';
isRecording = false;
if (recordBtn) recordBtn.textContent = 'Record Video';
if (recordBtn) recordBtn.style.background = '#555';
};
mediaRecorder.start(1000);
isRecording = true;
if (recordBtn) {
recordBtn.textContent = 'Stop Recording';
recordBtn.style.background = '#e74c3c';
}
recIndicator.style.display = 'block';
statusMsg.innerText = 'Recording...';
} else {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
statusMsg.innerText = 'Stopping, uploading...';
}
}
}

async function uploadVideoInChunks(blob) {
const totalChunks = Math.ceil(blob.size / CHUNK_SIZE);
currentUploadId = 'upload_' + Date.now() + '_' + Math.random().toString(36).substr(2,8);
uploadProgress.style.display = 'block';
const timestamp = new Date().toLocaleString();
const locationPart = currentLocation.name ? currentLocation.name : (currentLocation.lat && currentLocation.lng ? `${currentLocation.lat.toFixed(5)}, ${currentLocation.lng.toFixed(5)}` : '');
const titleText = locationPart ? `@${timestamp} - ${locationPart}` : `@${timestamp}`;
for (let i = 0; i < totalChunks; i++) {
const chunk = blob.slice(i * CHUNK_SIZE, Math.min((i+1) * CHUNK_SIZE, blob.size));
const formData = new FormData();
formData.append('media', chunk, 'chunk.webm');
formData.append('chunk_index', i);
formData.append('chunk_total', totalChunks);
formData.append('upload_id', currentUploadId);
formData.append('type', 'video');
formData.append('title', titleText);
formData.append('description', '');
formData.append('fingerprint', currentFingerprint);
if (currentLocation.lat && currentLocation.lng) {
formData.append('latitude', currentLocation.lat);
formData.append('longitude', currentLocation.lng);
formData.append('location_name', currentLocation.name);
}
try {
const resp = await fetch(window.location.href, { method: 'POST', body: formData });
const result = await resp.json();
if (!result.success && !result.chunk) throw new Error();
const percent = Math.round(((i+1)/totalChunks)*100);
uploadProgressBar.style.width = percent + '%';
statusMsg.innerText = 'Uploading ' + (i+1) + '/' + totalChunks + ' (' + percent + '%)';
} catch(e) {
statusMsg.innerText = 'Upload failed';
uploadProgress.style.display = 'none';
return;
}
}
statusMsg.innerText = 'Video posted!';
uploadProgress.style.display = 'none';
setTimeout(() => { closeModal(); location.reload(); }, 2000);
}

function closeModal() {
if (mediaStream) {
mediaStream.getTracks().forEach(t => t.stop());
mediaStream = null;
}
modal.style.display = 'none';
video.srcObject = null;
uploadProgress.style.display = 'none';
recIndicator.style.display = 'none';
isRecording = false;
streamReady = false;
modalOpen = false;
locationReady = false;
locationPromise = null;
}

async function showCameraModal() {
if (modalOpen) return;
modalOpen = true;
modal.style.display = 'flex';
statusMsg.innerText = 'Getting location...';
await ensureLocation();
statusMsg.innerText = 'Loading cameras...';
await getFingerprint();
await enumerateCameras();
if (availableCameras.length > 0) {
await startCamera(cameraSelect.value);
} else {
statusMsg.innerText = 'No camera found.';
}
controlsDiv.innerHTML = '';
const takePhotoBtn = document.createElement('button');
takePhotoBtn.textContent = 'Take Photo';
takePhotoBtn.onclick = takePhoto;
const rotateBtn = document.createElement('button');
rotateBtn.textContent = 'Rotate';
rotateBtn.onclick = rotateCamera;
const recordBtn = document.createElement('button');
recordBtn.id = 'recordBtn';
recordBtn.textContent = 'Record Video';
recordBtn.onclick = toggleRecording;
const closeModalBtn = document.createElement('button');
closeModalBtn.textContent = 'Close';
closeModalBtn.onclick = closeModal;
controlsDiv.appendChild(takePhotoBtn);
controlsDiv.appendChild(rotateBtn);
controlsDiv.appendChild(recordBtn);
controlsDiv.appendChild(closeModalBtn);
}

goLiveBtn.onclick = showCameraModal;
cameraSelect.addEventListener('change', async (e) => {
if (e.target.value) await switchToCamera(e.target.value);
});
})();

// ============================================================
// FORM DYNAMICS & SEARCH
// ============================================================
document.addEventListener('DOMContentLoaded', function() {
const addType = document.getElementById('addType');
if(addType) {
const articleFields = document.getElementById('addArticleFields');
const fileFields = document.getElementById('addFileFields');
const fileInput = document.getElementById('addFileInput');
function updateAddForm() {
if(addType.value === 'article') {
articleFields.style.display = 'block';
fileFields.style.display = 'none';
if(fileInput) fileInput.required = false;
} else {
articleFields.style.display = 'none';
fileFields.style.display = 'block';
if(fileInput) fileInput.required = true;
}
}
addType.addEventListener('change', updateAddForm);
updateAddForm();
}
const editType = document.getElementById('editType');
if(editType) {
const editArticle = document.getElementById('editArticleFields');
const editFile = document.getElementById('editFileFields');
editType.addEventListener('change', function() {
if(this.value === 'article') {
editArticle.style.display = 'block';
editFile.style.display = 'none';
} else if(this.value === 'image' || this.value === 'video') {
editArticle.style.display = 'none';
editFile.style.display = 'block';
} else {
editArticle.style.display = 'none';
editFile.style.display = 'none';
}
});
}

// Clientโ€‘side search filter
const searchInput = document.getElementById('searchInput');
if(searchInput) {
const contentList = document.getElementById('contentList');
const items = contentList ? Array.from(contentList.querySelectorAll('.item')) : [];
const contentCountSpan = document.getElementById('contentCount');
function filterItems() {
const searchTerm = searchInput.value.trim().toLowerCase();
let visibleCount = 0;
items.forEach(item => {
const title = item.getAttribute('data-title') || '';
const location = item.getAttribute('data-location') || '';
const matches = searchTerm === '' || title.includes(searchTerm) || location.includes(searchTerm);
item.style.display = matches ? '' : 'none';
if(matches) visibleCount++;
});
if(contentCountSpan) contentCountSpan.textContent = visibleCount;
let noResultsMsg = document.getElementById('noSearchResults');
if(visibleCount === 0 && items.length > 0) {
if(!noResultsMsg) {
noResultsMsg = document.createElement('div');
noResultsMsg.id = 'noSearchResults';
noResultsMsg.className = 'no-results';
noResultsMsg.innerHTML = '๐Ÿ” No content matches your search.';
if(contentList) contentList.appendChild(noResultsMsg);
}
} else if(noResultsMsg) { noResultsMsg.remove(); }
}
searchInput.addEventListener('input', filterItems);
}
});
</script>
</body>
</html>
โ† Back to feed