Add channels page

This commit is contained in:
2025-12-10 18:22:04 +01:00
parent 009ecdc2ca
commit cd77b08df2
10 changed files with 242 additions and 3 deletions

View File

@@ -188,6 +188,50 @@ body {
box-shadow: inset 0 1px 0 rgba(255,255,255,0.02);
}
.search-panel {
display: flex;
flex-direction: column;
gap: 12px;
}
.text-input {
width: 100%;
padding: 12px;
border-radius: 10px;
border: 1px solid #1f2937;
background: #0f172a;
color: #e2e8f0;
font-size: 15px;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.text-input:focus {
outline: none;
border-color: #7c3aed;
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25);
}
.search-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.search-row {
display: flex;
gap: 10px;
align-items: stretch;
}
.search-row .text-input {
flex: 1;
}
.search-row .button {
padding: 0 16px;
height: 46px;
}
.badge {
display: inline-flex;
align-items: center;
@@ -1133,6 +1177,10 @@ body {
font-size: 0.9em;
}
.tiny {
font-size: 13px;
}
.range-value {
color: #a5b4fc;
font-size: 12px;

View File

@@ -0,0 +1,54 @@
document.addEventListener("DOMContentLoaded", () => {
const searchForm = document.getElementById("channel-search-form");
const searchInput = document.getElementById("channel-search");
const suggestions = document.getElementById("channel-suggestions");
if (!searchForm || !searchInput || !suggestions) {
return;
}
let channels = [];
function updateSuggestions(term) {
const normalizedTerm = term.trim().toLowerCase();
const filtered = channels
.filter((name) => !normalizedTerm || name.includes(normalizedTerm))
.slice(0, 20);
suggestions.innerHTML = "";
filtered.forEach((name) => {
const option = document.createElement("option");
option.value = name;
suggestions.appendChild(option);
});
}
async function loadChannels() {
try {
const response = await fetch("/api/channels");
if (!response.ok) {
throw new Error(`Failed to load channels: ${response.status}`);
}
channels = await response.json();
updateSuggestions(searchInput.value || "");
} catch (error) {
console.error("Could not load channel directory", error);
}
}
searchInput.addEventListener("input", (event) => {
updateSuggestions(event.target.value || "");
});
searchForm.addEventListener("submit", (event) => {
event.preventDefault();
const broadcaster = (searchInput.value || "").trim().toLowerCase();
if (!broadcaster) {
searchInput.focus();
return;
}
window.location.href = `/view/${encodeURIComponent(broadcaster)}/broadcast`;
});
loadChannels();
});

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Browse channels - Imgfloat</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body class="landing-body">
<div class="landing">
<header class="landing-header">
<div class="brand">
<div class="brand-mark">IF</div>
<div>
<div class="brand-title">Imgfloat</div>
<div class="brand-subtitle">Twitch overlay manager</div>
</div>
</div>
<div class="cta-row">
<a class="button ghost" href="/">Home</a>
<a class="button" href="/oauth2/authorization/twitch">Login</a>
</div>
</header>
<main class="hero hero-compact">
<div class="hero-text">
<p class="eyebrow">Broadcast views</p>
<h1>Open a public channel overlay.</h1>
<p class="lead">Search any broadcaster with published overlays and jump straight to their on-stream view without logging in.</p>
<ul class="pill-list minimal">
<li>Autocomplete from all published overlays</li>
<li>Direct broadcast view links</li>
</ul>
</div>
<div class="hero-panel search-panel">
<div class="panel-header">
<div>
<p class="eyebrow subtle">Search</p>
<h3>Find a broadcaster</h3>
</div>
<span class="badge">Live</span>
</div>
<p class="muted">Type a channel name to get autocomplete suggestions and open the broadcast overlay.</p>
<form id="channel-search-form" class="search-form">
<label class="sr-only" for="channel-search">Search by broadcaster</label>
<div class="search-row">
<input id="channel-search" name="channel" class="text-input" type="text" list="channel-suggestions" placeholder="Search broadcaster..." autocomplete="off" />
<datalist id="channel-suggestions"></datalist>
<button type="submit" class="button secondary">Open</button>
</div>
<p class="muted tiny">Autocomplete shows all broadcasters with published overlays.</p>
</form>
</div>
</main>
<footer class="landing-footer compact">
<div>
<div class="brand-title">Imgfloat</div>
<div class="muted">Ready when you go live.</div>
</div>
<a class="button ghost" href="/oauth2/authorization/twitch">Login</a>
</footer>
</div>
<script src="/js/landing.js"></script>
</body>
</html>

View File

@@ -25,7 +25,7 @@
<p class="lead">Upload artwork, drop it into a shared dashboard, and stay in sync with your mods.</p>
<div class="cta-row">
<a class="button" href="/oauth2/authorization/twitch">Login with Twitch</a>
<span class="muted">Secure OAuth login. No bots needed.</span>
<a class="button ghost" href="/channels">Browse channels</a>
</div>
<ul class="pill-list minimal">
<li>Instant overlay updates</li>