feat: white label transformation and bugfix for audio stream

This commit is contained in:
Hördle Bot
2025-11-27 11:19:32 +01:00
parent 7a65c58214
commit d69ac28bb3
9 changed files with 249 additions and 84 deletions

View File

@@ -44,11 +44,35 @@ export async function GET(
const stream = createReadStream(filePath, { start, end });
// Convert Node stream to Web stream
const readable = new ReadableStream({
start(controller) {
stream.on('data', (chunk: any) => controller.enqueue(chunk));
stream.on('end', () => controller.close());
stream.on('error', (err: any) => controller.error(err));
let isClosed = false;
stream.on('data', (chunk: any) => {
if (isClosed) return;
try {
controller.enqueue(chunk);
} catch (e) {
isClosed = true;
stream.destroy();
}
});
stream.on('end', () => {
if (isClosed) return;
isClosed = true;
controller.close();
});
stream.on('error', (err: any) => {
if (isClosed) return;
isClosed = true;
controller.error(err);
});
},
cancel() {
stream.destroy();
}
});
@@ -68,9 +92,32 @@ export async function GET(
// Convert Node stream to Web stream
const readable = new ReadableStream({
start(controller) {
stream.on('data', (chunk: any) => controller.enqueue(chunk));
stream.on('end', () => controller.close());
stream.on('error', (err: any) => controller.error(err));
let isClosed = false;
stream.on('data', (chunk: any) => {
if (isClosed) return;
try {
controller.enqueue(chunk);
} catch (e) {
isClosed = true;
stream.destroy();
}
});
stream.on('end', () => {
if (isClosed) return;
isClosed = true;
controller.close();
});
stream.on('error', (err: any) => {
if (isClosed) return;
isClosed = true;
controller.error(err);
});
},
cancel() {
stream.destroy();
}
});

View File

@@ -2,6 +2,24 @@
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
/* Theme Colors */
--primary: #000000;
--primary-foreground: #ffffff;
--secondary: #4b5563;
--secondary-foreground: #ffffff;
--accent: #667eea;
--accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--success: #22c55e;
--success-foreground: #ffffff;
--danger: #ef4444;
--danger-foreground: #ffffff;
--warning: #ffc107;
--muted: #f3f4f6;
--muted-foreground: #6b7280;
--border: #e5e7eb;
--input: #d1d5db;
--ring: #000000;
}
body {
@@ -51,13 +69,13 @@ body {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
color: #666;
color: var(--muted-foreground);
margin-bottom: 0.5rem;
}
/* Audio Player */
.audio-player {
background: #f3f4f6;
background: var(--muted);
padding: 1rem;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
@@ -73,8 +91,8 @@ body {
width: 3rem;
height: 3rem;
border-radius: 50%;
background: #000;
color: #fff;
background: var(--primary);
color: var(--primary-foreground);
border: none;
display: flex;
align-items: center;
@@ -85,19 +103,20 @@ body {
.play-button:hover {
background: #333;
/* Keep for now or add --primary-hover */
}
.progress-bar-container {
flex: 1;
height: 0.5rem;
background: #d1d5db;
background: var(--input);
border-radius: 999px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: #22c55e;
background: var(--success);
transition: width 0.1s linear;
}
@@ -114,7 +133,7 @@ body {
gap: 0.5rem;
padding: 0.5rem;
background: #f9fafb;
border: 1px solid #e5e7eb;
border: 1px solid var(--border);
border-radius: 0.25rem;
font-size: 0.875rem;
}
@@ -125,7 +144,7 @@ body {
}
.guess-text {
color: #ef4444;
color: var(--danger);
/* Red for wrong */
}
@@ -135,7 +154,7 @@ body {
}
.guess-text.correct {
color: #22c55e;
color: var(--success);
}
/* Input */
@@ -148,14 +167,14 @@ body {
.guess-input {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border: 1px solid var(--input);
border-radius: 0.25rem;
font-size: 1rem;
box-sizing: border-box;
}
.guess-input:focus {
outline: 2px solid #000;
outline: 2px solid var(--ring);
border-color: transparent;
}
@@ -163,7 +182,7 @@ body {
position: absolute;
width: 100%;
background: #fff;
border: 1px solid #d1d5db;
border: 1px solid var(--input);
border-radius: 0.25rem;
margin-top: 0.25rem;
max-height: 15rem;
@@ -177,11 +196,11 @@ body {
.suggestion-item {
padding: 0.75rem;
cursor: pointer;
border-bottom: 1px solid #f3f4f6;
border-bottom: 1px solid var(--muted);
}
.suggestion-item:hover {
background: #f3f4f6;
background: var(--muted);
}
.suggestion-title {
@@ -190,14 +209,14 @@ body {
.suggestion-artist {
font-size: 0.875rem;
color: #666;
color: var(--muted-foreground);
}
.skip-button {
width: 100%;
padding: 1rem 1.5rem;
margin-top: 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: var(--accent-gradient);
color: white;
border: none;
border-radius: 0.5rem;
@@ -246,7 +265,7 @@ body {
}
.admin-card {
background: #f3f4f6;
background: var(--muted);
padding: 2rem;
border-radius: 0.5rem;
}
@@ -265,14 +284,14 @@ body {
.form-input {
width: 100%;
padding: 0.5rem;
border: 1px solid #d1d5db;
border: 1px solid var(--input);
border-radius: 0.25rem;
box-sizing: border-box;
}
.btn-primary {
background: #000;
color: #fff;
background: var(--primary);
color: var(--primary-foreground);
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
@@ -292,8 +311,8 @@ body {
}
.btn-secondary {
background: #4b5563;
color: #fff;
background: var(--secondary);
color: var(--secondary-foreground);
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
@@ -312,8 +331,8 @@ body {
}
.btn-danger {
background: #ef4444;
color: #fff;
background: var(--danger);
color: var(--danger-foreground);
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
@@ -337,8 +356,8 @@ body {
padding: 2rem 1rem 1rem;
text-align: center;
font-size: 0.875rem;
color: #666;
border-top: 1px solid #e5e7eb;
color: var(--muted-foreground);
border-top: 1px solid var(--border);
width: 100%;
}
@@ -347,7 +366,7 @@ body {
}
.app-footer a {
color: #000;
color: var(--primary);
text-decoration: none;
font-weight: 500;
}
@@ -375,7 +394,7 @@ body {
font-size: 0.875rem;
text-align: center;
margin: 0 0 1rem 0;
color: #666;
color: var(--muted-foreground);
}
.statistics-grid {
@@ -391,7 +410,7 @@ body {
padding: 0.75rem 0.5rem;
background: rgba(255, 255, 255, 0.8);
border-radius: 0.375rem;
border: 1px solid #e5e7eb;
border: 1px solid var(--border);
}
.stat-badge {
@@ -401,7 +420,7 @@ body {
.stat-label {
font-size: 0.75rem;
color: #666;
color: var(--muted-foreground);
margin-bottom: 0.25rem;
text-align: center;
}
@@ -409,7 +428,7 @@ body {
.stat-count {
font-size: 1.25rem;
font-weight: bold;
color: #000;
color: var(--primary);
}
/* Tooltip */

View File

@@ -3,6 +3,8 @@ import { Geist, Geist_Mono } from "next/font/google";
import Script from "next/script";
import "./globals.css";
import { config } from "@/lib/config";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
@@ -14,12 +16,12 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Hördle",
description: "Daily music guessing game - Guess the song from short audio clips",
title: config.appName,
description: config.appDescription,
};
export const viewport: Viewport = {
themeColor: "#000000",
themeColor: config.colors.themeColor,
width: "device-width",
initialScale: 1,
maximumScale: 1,
@@ -38,8 +40,8 @@ export default function RootLayout({
<head>
<Script
defer
data-domain="hoerdle.elpatron.me"
src="https://plausible.elpatron.me/js/script.js"
data-domain={config.plausibleDomain}
src={config.plausibleScriptSrc}
strategy="beforeInteractive"
/>
</head>

View File

@@ -1,14 +1,15 @@
import type { MetadataRoute } from 'next'
import { config } from '@/lib/config'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Hördle',
short_name: 'Hördle',
description: 'Daily music guessing game - Guess the song from short audio clips',
name: config.appName,
short_name: config.appName,
description: config.appDescription,
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#000000',
background_color: config.colors.backgroundColor,
theme_color: config.colors.themeColor,
icons: [
{
src: '/favicon.ico',