Files
bitvid/index.html
T

437 lines
17 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" />
<!--
Use theme tokens so browsers pick up palette updates without hard-coded
hex values scattered across templates.
-->
<meta name="theme-color" content="var(--color-page)" />
<script>
(() => {
try {
const STORAGE_KEY = "bitvid:theme";
const root = document.documentElement;
const themeMeta = document.querySelector('meta[name="theme-color"]');
const fallbackColors = {
dark: "rgb(15, 23, 42)",
light: "rgb(248, 250, 252)",
};
const prefersDark =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
const storedTheme = window.localStorage.getItem(STORAGE_KEY);
let resolvedTheme = storedTheme || (prefersDark ? "dark" : "light");
if (resolvedTheme === "default") {
resolvedTheme = "dark";
}
if (resolvedTheme) {
root.dataset.theme = resolvedTheme;
}
const updateThemeColor = () => {
if (!themeMeta) {
return;
}
const computedThemeColor =
(window.getComputedStyle &&
getComputedStyle(root).getPropertyValue("--color-page").trim()) ||
"";
const metaColor =
computedThemeColor ||
fallbackColors[resolvedTheme] ||
fallbackColors[prefersDark ? "dark" : "light"];
if (metaColor) {
themeMeta.content = metaColor;
}
};
updateThemeColor();
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", updateThemeColor, {
once: true,
});
} else if ("requestAnimationFrame" in window) {
window.requestAnimationFrame(updateThemeColor);
}
} catch (error) {
/* no-op */
}
})();
</script>
<!-- Styles -->
<!--
Cache busting: keep these query strings in sync with config/asset-version.js
so browsers pick up the latest sidebar markup and styling without
hard-refreshing around CDN caches.
-->
<link href="css/tailwind.generated.css?v=2024.11.09-2" rel="stylesheet" />
<link href="css/docs.css?v=2024.11.09-2" rel="stylesheet" />
</head>
<body class="sidebar-collapsed" data-ds="new">
<!--
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:
Layout spacing follows the --sidebar-width custom property on every viewport so the
collapsible rail always shifts the application shell instead of floating above it.
-->
<!--
`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="fade-in flex min-h-screen flex-col px-4 pb-8"
data-sidebar-state="collapsed"
>
<!-- Header -->
<header class="app-shell__header ds-header mb-8 w-full">
<!-- 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"
>
<span
class="ds-header__logo inline-flex items-center text-text-strong bv-logo"
role="img"
aria-label="bitvid logo"
>
<svg
class="h-full w-auto"
viewBox="0 0 318.32 108.98"
aria-hidden="true"
focusable="false"
>
<g class="bv-logo__accent">
<rect x="7.1" y="7.8" width="5.84" height="5.48" />
<rect x="7.1" y="18.78" width="24.02" height="5.48" />
<rect x="7.1" y="29.77" width="42.85" height="5.48" />
<rect x="7.1" y="40.76" width="61.68" height="5.48" />
<rect x="7.1" y="51.75" width="80.88" height="5.48" />
<rect x="7.1" y="62.74" width="61.68" height="5.48" />
<rect x="7.1" y="73.73" width="42.85" height="5.48" />
<rect x="7.1" y="84.72" width="24.02" height="5.48" />
<rect x="7.1" y="95.71" width="5.84" height="5.48" />
</g>
<g class="bv-logo__wordmark">
<path d="M121.28 25.08v44.54c0 5.67 2.79 8.54 8.22 8.54h11.57c5.51 0 8.3-2.87 8.3-8.54v-13.09c0-5.67-2.79-8.46-8.3-8.46h-16.6v-5.83h17.72c8.7 0 13.01 4.47 13.01 13.33v15.01c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33V25.08h5.83Z" />
<path d="M168.53 25.08v6.26h-5.83v-6.26h5.83Zm0 17.56v41.27h-5.83v-41.27h5.83Z" />
<path d="M191.36 25.08v17.16h9.02v5.19h-9.02v36.48h-5.83v-36.48h-9.82v-5.19h9.82V25.08h5.83Z" />
<path d="M212.88 42.24l16.84 35.68 16.6-35.68h6.07l-19.56 41.67h-6.31l-20.04-41.67h6.39Z" />
<path d="M264.05 25.08v6.26h-5.83v-6.26h5.83Zm0 17.16v41.67h-5.83v-41.67h5.83Z" />
<path d="M311.21 25.08v45.5c0 8.86-4.31 13.33-13.01 13.33h-13.73c-8.7 0-13.01-4.47-13.01-13.33v-15.01c0-8.86 4.31-13.33 13.01-13.33h17.72v5.83h-16.6c-5.51 0-8.3 2.79-8.3 8.46v13.09c0 5.67 2.79 8.54 8.3 8.54h11.49c5.51 0 8.3-2.87 8.3-8.54V25.08h5.83Z" />
</g>
</svg>
</span>
</a>
<div class="header-search__region">
<form
id="headerSearchForm"
role="search"
class="header-search"
>
<button
type="button"
class="header-search__filter"
aria-label="Open search filters"
>
<span
aria-hidden="true"
class="header-search__icon header-search__icon--filter"
></span>
</button>
<label for="headerSearchInput" class="sr-only">Search videos</label>
<input
id="headerSearchInput"
class="header-search__input"
type="search"
name="query"
placeholder="Search videos"
autocomplete="off"
/>
<button type="submit" class="header-search__submit">
<span class="sr-only">Submit search</span>
<span
aria-hidden="true"
class="header-search__icon header-search__icon--submit"
></span>
</button>
</form>
</div>
<!-- Buttons on the far right -->
<!-- Header action buttons share the fade-in treatment for consistency. -->
<div class="ml-auto flex items-center">
<div class="fade-in fade-in-delay-200 flex items-center space-x-4">
<button
id="themeToggle"
class="header-icon-toggle"
type="button"
aria-pressed="false"
>
<span class="sr-only">Toggle theme</span>
<span
aria-hidden="true"
class="header-icon-toggle__icon-wrapper"
data-theme-toggle-icon
data-theme-icon-state="dark"
>
<svg
class="header-icon-toggle__icon header-icon-toggle__icon--dark"
viewBox="0 0 24 24"
role="presentation"
>
<path
d="M10.719 2.082c-2.572 2.028-4.719 5.212-4.719 9.918 0 4.569 1.938 7.798 4.548 9.895-4.829-.705-8.548-4.874-8.548-9.895 0-5.08 3.808-9.288 8.719-9.918zm1.281-2.082c-6.617 0-12 5.383-12 12s5.383 12 12 12c1.894 0 3.87-.333 5.37-1.179-3.453-.613-9.37-3.367-9.37-10.821 0-7.555 6.422-10.317 9.37-10.821-1.74-.682-3.476-1.179-5.37-1.179zm0 10.999c1.437.438 2.562 1.564 2.999 3.001.44-1.437 1.565-2.562 3.001-3-1.436-.439-2.561-1.563-3.001-3-.437 1.436-1.562 2.561-2.999 2.999zm8.001.001c.958.293 1.707 1.042 2 2.001.291-.959 1.042-1.709 1.999-2.001-.957-.292-1.707-1.042-2-2-.293.958-1.042 1.708-1.999 2zm-1-9c-.437 1.437-1.563 2.562-2.998 3.001 1.438.44 2.561 1.564 3.001 3.002.437-1.438 1.563-2.563 2.996-3.002-1.433-.437-2.559-1.564-2.999-3.001z"
fill="currentColor"
/>
</svg>
<svg
class="header-icon-toggle__icon header-icon-toggle__icon--light"
viewBox="0 0 24 24"
role="presentation"
>
<path
d="M4.069 13h-4.069v-2h4.069c-.041.328-.069.661-.069 1s.028.672.069 1zm3.034-7.312l-2.881-2.881-1.414 1.414 2.881 2.881c.411-.529.885-1.003 1.414-1.414zm11.209 1.414l2.881-2.881-1.414-1.414-2.881 2.881c.528.411 1.002.886 1.414 1.414zm-6.312-3.102c.339 0 .672.028 1 .069v-4.069h-2v4.069c.328-.041.661-.069 1-.069zm0 16c-.339 0-.672-.028-1-.069v4.069h2v-4.069c-.328.041-.661.069-1 .069zm7.931-9c.041.328.069.661.069 1s-.028.672-.069 1h4.069v-2h-4.069zm-3.033 7.312l2.88 2.88 1.415-1.414-2.88-2.88c-.412.528-.886 1.002-1.415 1.414zm-11.21-1.415l-2.88 2.88 1.414 1.414 2.88-2.88c-.528-.411-1.003-.885-1.414-1.414zm2.312-4.897c0 2.206 1.794 4 4 4s4-1.794 4-4-1.794-4-4-4-4 1.794-4 4zm10 0c0 3.314-2.686 6-6 6s-6-2.686-6-6 2.686-6 6-6 6 2.686 6 6z"
fill="currentColor"
/>
</svg>
</span>
</button>
<!-- Login Button -->
<button
id="loginButton"
class="btn focus-ring h-12 w-12 rounded-full p-0"
aria-label="Open login modal"
>
<svg
class="h-9 w-9"
viewBox="0 0 24 24"
role="presentation"
focusable="false"
aria-hidden="true"
>
<path
d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0Zm0 22c-3.123 0-5.914-1.441-7.749-3.69.259-.588.783-.995 1.867-1.246 2.244-.518 4.459-.981 3.393-2.945C6.356 8.299 8.612 5 12 5s4.366 3.177 1.489 9.119c-1.035 1.952 1.1 2.416 3.393 2.945 1.082.25 1.61.655 1.871 1.241C17.917 20.558 15.125 22 12 22Z"
fill="var(--color-bg)"
fill-rule="nonzero"
/>
</svg>
</button>
<!-- Upload Button (hidden by default) -->
<button
id="uploadButton"
class="btn focus-ring hidden h-12 w-12 rounded-full p-0 text-xl font-bold leading-none"
>
+
</button>
<!-- Profile Button (hidden by default) -->
<button
id="profileButton"
class="btn-ghost focus-ring relative hidden h-12 w-12 rounded-full p-0"
>
<div class="h-10 w-10 overflow-hidden rounded-full">
<img
id="profileAvatar"
src="assets/svg/default-profile.svg"
alt="Profile"
class="h-full w-full object-cover"
/>
</div>
<span id="profileUnreadDot" aria-hidden="true"></span>
</button>
</div>
</div>
</header>
<!-- Notification Portal -->
<div
id="notificationPortal"
class="notification-portal"
role="region"
aria-live="polite"
aria-label="System notifications"
>
<!-- Error Container -->
<div
id="errorContainer"
class="notification-banner notification-banner--critical hidden"
data-state="critical"
></div>
<!-- Status Container -->
<div
id="statusContainer"
class="notification-banner notification-banner--info status-banner hidden"
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="notification-banner notification-banner--success hidden"
></div>
</div>
<!-- Dynamic views (like most-recent-videos.html, etc.) -->
<main id="viewContainer" class="mb-8 flex-grow"></main>
<!-- Modal Container (external modals) -->
<div id="modalContainer"></div>
<!-- Tagline / Slogan -->
<!-- Tagline fade matches the cards. -->
<div class="fade-in fade-in-delay-300 mb-8 text-center">
<h2 class="text-2xl font-bold tracking-wide text-muted-strong">
seed. zap. subscribe.
</h2>
</div>
<!-- Mobile Search FAB -->
<button
id="mobileSearchFab"
class="focus-ring fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-accent-strong shadow-lg transition-transform duration-200 hover:scale-105 active:scale-95 md:hidden"
aria-label="Open search"
>
<svg
class="h-6 w-6 text-white"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
<!-- Mobile Search Container -->
<div
id="mobileSearchContainer"
class="fixed top-4 left-4 right-4 z-50 hidden max-w-sm mx-auto origin-top transition-all duration-200 md:hidden"
>
<form
id="mobileSearchForm"
role="search"
class="flex items-center gap-3 rounded-full border border-border bg-overlay-panel bg-opacity-95 p-2 shadow-xl backdrop-blur-md"
>
<button
type="button"
class="header-search__filter shrink-0"
aria-label="Open search filters"
>
<span
aria-hidden="true"
class="header-search__icon header-search__icon--filter"
></span>
</button>
<label for="mobileSearchInput" class="sr-only">Search videos</label>
<input
id="mobileSearchInput"
class="header-search__input min-w-0 flex-1 bg-transparent px-2 text-base text-text outline-none placeholder:text-muted"
type="search"
name="query"
placeholder="Search videos"
autocomplete="off"
/>
<button type="submit" class="header-search__submit shrink-0">
<span class="sr-only">Submit search</span>
<span
aria-hidden="true"
class="header-search__icon header-search__icon--submit"
></span>
</button>
</form>
</div>
</div>
<!-- Additional Scripts -->
<!-- Bootstraps nostr-tools before other modules rely on nostrToolsReady -->
<script type="module" src="js/nostrToolsBootstrap.js?v=2024.11.09"></script>
<script type="module" src="js/config.js?v=2024.11.09"></script>
<script type="module" src="js/lists.js?v=2024.11.09"></script>
<script type="module" src="js/accessControl.js?v=2024.11.09"></script>
<script type="module" src="js/webtorrent.js?v=2024.11.09"></script>
<script type="module" src="js/viewManager.js?v=2024.11.09"></script>
<script type="module" src="js/bootstrap.js?v=2024.11.09"></script>
<script type="module" src="js/disclaimer.js?v=2024.11.09"></script>
<script type="module" src="js/index.js?v=2024.11.09"></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>
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
</body>
</html>