Files
bitvid/index.html
T

314 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Decentralized Video Sharing</title>
<meta
name="description"
content="Bitvid is a decentralized video hub that prioritizes URL-first playback with WebTorrent fallback so your audience can stream reliably."
/>
<meta name="robots" content="index,follow" />
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="BitVid - Decentralized Video Sharing" />
<meta
property="og:description"
content="Share videos and follow creators freely, in a truly decentralized way."
/>
<meta property="og:image" content="assets/jpg/bitvid.jpg" />
<meta property="og:url" content="https://bitvid.network" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href="assets/favicon.ico" sizes="any" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="assets/png/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Styles -->
<script src="js/tracking.js" defer></script>
<link href="css/tailwind.generated.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" />
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<!--
SIDEBAR CONTAINER:
The <aside id="sidebar"> inside handles visibility.
By default, no special position or z-index on #sidebarContainer.
-->
<div id="sidebarContainer">
<!-- components/sidebar.html gets injected here -->
</div>
<!--
MAIN CONTENT:
md:ml-64 ensures content is shifted on desktop so the pinned sidebar doesn't overlap.
On mobile, we also toggle .sidebar-open to shift the entire content.
-->
<!--
`fade-in` is scrubbed by js/index.js after the first animation so that
re-rendering views does not cause the shell to flicker. Only apply it to
elements that should animate exactly once on initial paint.
-->
<div id="app" class="md:ml-64 px-4 py-8 min-h-screen flex flex-col fade-in">
<!-- Header -->
<header class="mb-8 flex items-center w-full">
<!-- Mobile hamburger button (hidden on md+) -->
<!-- New button -->
<!--
Mobile menu button: uses `fade-in` to match the rest of the chrome.
The JS animation cleanup removes the helper classes once the entrance
finishes to prevent flashes on future sidebar toggles.
-->
<button
id="mobileMenuBtn"
class="md:hidden ml-2 mr-4 flex items-center justify-center w-10 h-10 rounded-full bg-transparent hover:bg-transparent focus:outline-none focus:ring-2 focus:ring-[#0f172a] z-[60] fade-in"
>
<img
src="assets/svg/mobile-sidebar-menu-icon.svg"
alt="Mobile Sidebar Menu Icon"
class="w-6 h-6"
/>
</button>
<!-- Logo -->
<!-- Logo fades in once on initial load. -->
<a
href="index.html"
aria-label="Return to the bitvid home page"
class="fade-in fade-in-delay-100"
>
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</a>
<!-- Buttons on the far right -->
<!-- Header action buttons share the fade-in treatment for consistency. -->
<div class="ml-auto flex items-center space-x-4 fade-in fade-in-delay-200">
<!-- Login Button -->
<button
id="loginButton"
style="background-color: #1e293b"
class="inline-flex items-center justify-center w-12 h-12 rounded-full text-white text-sm font-bold leading-none hover:bg-[#e6002c] focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
<img
src="assets/svg/default-profile.svg"
alt="Profile Icon"
class="w-9 h-9"
/>
</button>
<!-- Upload Button (hidden by default) -->
<button
id="uploadButton"
style="background-color: #fe0032"
class="hidden inline-flex items-center justify-center w-12 h-12 rounded-full text-white text-xl font-bold leading-none hover:bg-[#e6002c] focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
+
</button>
<!-- Profile Button (hidden by default) -->
<button
id="profileButton"
class="hidden inline-flex items-center justify-center w-12 h-12 rounded-full bg-black text-white text-sm leading-none hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black"
>
<div class="w-10 h-10 rounded-full overflow-hidden">
<img
id="profileAvatar"
src="assets/svg/default-profile.svg"
alt="Profile"
class="w-full h-full object-cover"
/>
</div>
</button>
</div>
</header>
<!-- Notification Portal -->
<div
id="notificationPortal"
role="region"
aria-live="polite"
aria-label="System notifications"
>
<!-- Error Container -->
<div
id="errorContainer"
class="hidden bg-red-100 text-red-900 p-4 rounded-md mb-4"
></div>
<!-- Status Container -->
<div
id="statusContainer"
class="hidden status-banner"
role="status"
aria-live="polite"
>
<span class="status-spinner" aria-hidden="true"></span>
<span class="status-message" data-status-message></span>
</div>
<!-- Success Container -->
<div
id="successContainer"
class="hidden bg-green-100 text-green-900 p-4 rounded-md mb-4"
></div>
</div>
<!-- Dynamic views (like most-recent-videos.html, etc.) -->
<main id="viewContainer" class="flex-grow mb-8"></main>
<!-- Modal Container (external modals) -->
<div id="modalContainer"></div>
<!-- Tagline / Slogan -->
<!-- Tagline fade matches the cards. -->
<div class="text-center mb-8 fade-in fade-in-delay-300">
<h2 class="text-2xl font-bold text-gray-500 tracking-wide">
seed. zap. subscribe.
</h2>
</div>
</div>
<!-- Additional Scripts -->
<script type="module">
import * as nostrTools from "https://esm.sh/[email protected]";
const normalizedGeneratePrivateKey =
typeof nostrTools.generatePrivateKey === "function"
? nostrTools.generatePrivateKey
: typeof nostrTools.generateSecretKey === "function"
? nostrTools.generateSecretKey
: undefined;
const normalizedGenerateSecretKey =
typeof nostrTools.generateSecretKey === "function"
? nostrTools.generateSecretKey
: typeof nostrTools.generatePrivateKey === "function"
? nostrTools.generatePrivateKey
: undefined;
const hasWorkingNip04 = (candidate) =>
candidate &&
typeof candidate.encrypt === "function" &&
typeof candidate.decrypt === "function";
let resolvedNip04 = hasWorkingNip04(nostrTools?.nip04)
? nostrTools.nip04
: null;
let nip04Source = resolvedNip04 ? "esm-main" : "";
if (!resolvedNip04) {
try {
const fallbackModule = await import(
"https://esm.sh/[email protected]?target=es2022&exports=nip04"
);
if (hasWorkingNip04(fallbackModule?.nip04)) {
resolvedNip04 = fallbackModule.nip04;
nip04Source = "esm-nip04-export";
}
} catch (error) {
console.warn("[bitvid] Failed to load nostr nip04 helper", error);
}
}
if (!resolvedNip04) {
try {
const bundleNip04 = await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src =
"https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js";
script.async = true;
script.onload = () =>
resolve(window?.NostrTools?.nip04 ? window.NostrTools.nip04 : null);
script.onerror = () =>
reject(new Error("nostr-tools bundle failed to load"));
document.head.appendChild(script);
});
if (hasWorkingNip04(bundleNip04)) {
resolvedNip04 = bundleNip04;
nip04Source = "cdn-bundle";
}
} catch (error) {
console.warn(
"[bitvid] Failed to load nostr nip04 bundle fallback",
error,
);
}
}
const toolSources = [];
if (nostrTools && typeof nostrTools === "object") {
toolSources.push(nostrTools);
}
const globalTools =
typeof window !== "undefined" && window?.NostrTools
? window.NostrTools
: null;
if (globalTools && globalTools !== nostrTools) {
toolSources.push(globalTools);
}
const toolsBundle = Object.assign({}, ...toolSources);
if (normalizedGeneratePrivateKey) {
toolsBundle.generatePrivateKey = normalizedGeneratePrivateKey;
}
if (normalizedGenerateSecretKey) {
toolsBundle.generateSecretKey = normalizedGenerateSecretKey;
}
if (resolvedNip04) {
toolsBundle.nip04 = resolvedNip04;
console.info(
`[bitvid] Initialized nostr nip04 helpers using ${nip04Source}.`,
);
} else {
console.warn(
"[bitvid] NIP-04 helpers remain unavailable after fallback attempts.",
);
}
window.NostrTools = toolsBundle;
</script>
<script type="module" src="js/config.js"></script>
<script type="module" src="js/lists.js"></script>
<script type="module" src="js/accessControl.js"></script>
<script type="module" src="js/webtorrent.js"></script>
<script type="module" src="js/nostr.js"></script>
<script type="module" src="js/viewManager.js"></script>
<script type="module" src="js/bootstrap.js"></script>
<script type="module" src="js/disclaimer.js"></script>
<script type="module" src="js/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
</body>
</html>