style: acampada26s.net palette, Anton titles and T-HUB logo

Black/white/neon (#00e5ff) on the existing dark base, h1/h2 in Anton
uppercase with the neon offset used on acampada26s.net. Neon fills get
dark text. The public submission page and the control-room focus ring
follow the same accent. Anton ships under the SIL OFL (license copied
next to the font). New logo in docs/brand/t-hub.svg, also the favicon.
This commit is contained in:
hacklab 2026-10-15 14:15:03 +02:00
parent fb81fab7bb
commit f6840ff69b
9 changed files with 264 additions and 32 deletions

View file

@ -13,4 +13,4 @@
}
.control-room label { max-width: 100%; }
.control-room button { min-width: 44px; }
.control-room :focus-visible { outline: 2px solid #ff1610; outline-offset: 3px; }
.control-room :focus-visible { outline: 2px solid #00e5ff; outline-offset: 3px; }

View file

@ -1,17 +1,25 @@
@import "tailwindcss";
/* Anton (SIL OFL 1.1, public/fonts/Anton-OFL.txt) — same title face as acampada26s.net. */
@font-face {
font-family: "Anton";
src: url("/fonts/anton.woff2") format("woff2");
font-display: swap;
}
@theme {
--color-bg: oklch(0.11 0.008 25);
--color-surface: oklch(0.15 0.01 25);
--color-surface-2: oklch(0.19 0.012 25);
--color-border: oklch(0.26 0.02 25);
--color-border-subtle: oklch(0.22 0.015 25);
--color-text: oklch(0.93 0.005 25);
--color-text-dim: oklch(0.62 0.02 25);
--color-text-faint: oklch(0.46 0.02 25);
--color-accent: #ff1610;
--color-accent-hover: #e3130e;
--color-accent-dim: rgb(255 22 16 / 0.15);
/* T-HUB: the acampada26s.net palette (black, white, neon) on a dark base. */
--color-bg: #000;
--color-surface: #0d0d0d;
--color-surface-2: #181818;
--color-border: #3d3d3d;
--color-border-subtle: #262626;
--color-text: #fff;
--color-text-dim: #b3b3b3;
--color-text-faint: #808080;
--color-accent: #00e5ff;
--color-accent-hover: #5cefff;
--color-accent-dim: rgb(0 229 255 / 0.15);
--color-live: oklch(0.72 0.19 145);
--color-warn: oklch(0.78 0.16 85);
--color-danger: oklch(0.55 0.24 15);
@ -20,6 +28,7 @@
--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-mono: ui-monospace, "SF Mono", Menlo, monospace;
--font-title: "Anton", Impact, "Arial Narrow", sans-serif;
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
@ -55,6 +64,23 @@ body {
html, body { max-width: 100%; }
/* Titles as on acampada26s.net: Anton, uppercase, hard neon offset. */
h1, h2 {
font-family: var(--font-title);
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.01em;
}
h1 {
text-shadow: 0.04em 0.04em 0 var(--color-accent);
}
/* Neon fills need dark text; unlayered so it outranks the text-white utility. */
.bg-accent.text-white,
.bg-accent .text-white {
color: #000;
}
@media (max-width: 1023px) {
body { overflow-x: hidden; }
[data-mobile-main] :is(button, a[href], input, select, textarea, [role="tab"]) {

View file

@ -13,7 +13,7 @@ export const viewport: Viewport = {
initialScale: 1,
viewportFit: "cover",
interactiveWidget: "resizes-content",
themeColor: "#FF1610",
themeColor: "#000000",
};
export default function RootLayout({ children }: { children: ReactNode }): React.ReactNode {

View file

@ -135,7 +135,7 @@ export default function SubmitPage({ params }: { params: { token: string } }): R
tone="warn"
title="Can't reach the server"
body="Your link is still fine — the server isn't answering. Try again in a moment."
action={<button type="button" onClick={() => setAttempt((n) => n + 1)} className="mt-6 rounded-md bg-[#FF1610] px-3 py-1.5 text-xs font-medium text-white hover:bg-[#e3130e]">Retry</button>}
action={<button type="button" onClick={() => setAttempt((n) => n + 1)} className="mt-6 rounded-md bg-[#00E5FF] px-3 py-1.5 text-xs font-medium text-black hover:bg-[#5cefff]">Retry</button>}
/>
</main>
);
@ -165,7 +165,7 @@ function DeadCard({ tone, title, body, action }: { tone: "warn" | "danger"; titl
<div className="w-full max-w-md rounded-lg border border-[#2a2320] bg-[#0F0A09] p-6 text-center">
<div
className={`mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full text-lg ${
tone === "warn" ? "bg-[#FF1610]/15 text-[#FF1610]" : "bg-[#FF1610]/15 text-[#FF1610]"
tone === "warn" ? "bg-[#00E5FF]/15 text-[#00E5FF]" : "bg-[#00E5FF]/15 text-[#00E5FF]"
}`}
>
{tone === "warn" ? "!" : "✕"}
@ -283,7 +283,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
if (phase === "sent" && done !== null) {
return (
<div className="mx-auto flex w-full max-w-xl flex-col items-center gap-4 rounded-lg border border-[#2a2320] bg-[#0F0A09] p-8 text-center">
<LogoMark size={44} className="text-[#FF1610]" title="" />
<LogoMark size={44} className="text-[#00E5FF]" title="" />
<h1 className="text-xl font-semibold tracking-tight text-[#f5efed]">Received</h1>
<p className="max-w-sm text-sm leading-relaxed text-[#c7c3c1]">
Received — the collective will review it. Nothing is published until they approve it.
@ -304,7 +304,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
className="mx-auto flex w-full max-w-xl flex-col gap-6 rounded-lg border border-[#2a2320] bg-[#0F0A09] p-6 sm:p-8"
>
<header className="flex flex-col items-center gap-3 text-center">
<LogoMark size={36} className="text-[#FF1610]" title="Tactical Media Broadcast Hub" />
<LogoMark size={36} className="text-[#00E5FF]" title="Tactical Media Broadcast Hub" />
<div>
<h1 className="text-xl font-semibold tracking-tight text-[#f5efed]">Send footage to the collective</h1>
<p className="mt-1 text-xs leading-relaxed text-[#a4a09e]">
@ -340,7 +340,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
className={`flex min-h-[180px] flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors duration-150 ${
uploading
? "cursor-not-allowed border-[#3a312e] opacity-60"
: "cursor-pointer border-[#3a312e] hover:border-[#FF1610] focus-visible:border-[#FF1610]"
: "cursor-pointer border-[#3a312e] hover:border-[#00E5FF] focus-visible:border-[#00E5FF]"
}`}
>
{uploading ? (
@ -350,7 +350,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
{progress < 100 ? `Uploading ${progress}%` : "Upload sent — the collective's server is taking it in…"}{" "}
{file !== null && `${file.name} (${formatBytes(file.size)})`}
</span>
<progress className="h-2 w-full accent-[#FF1610]" aria-label="Upload progress" value={progress} max={100} />
<progress className="h-2 w-full accent-[#00E5FF]" aria-label="Upload progress" value={progress} max={100} />
</div>
) : file !== null && kind !== null ? (
<>
@ -365,7 +365,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
<span className="text-2xs leading-relaxed text-[#a4a09e]">
or browse your device, or paste a file. Nothing is sent until you press Send.
</span>
<span className="mt-2 rounded-md bg-[#FF1610] px-3 py-1.5 text-xs font-medium text-white">Browse files</span>
<span className="mt-2 rounded-md bg-[#00E5FF] px-3 py-1.5 text-xs font-medium text-black">Browse files</span>
</>
)}
<input
@ -382,7 +382,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
</div>
{fileError !== null && <p role="alert" className="text-xs text-[#ff6f6b]">{fileError}</p>}
{error !== null && <p role="alert" className="rounded-md border border-[#FF1610]/30 bg-[#FF1610]/10 px-3 py-2 text-xs text-[#ff9b97]">{error}</p>}
{error !== null && <p role="alert" className="rounded-md border border-[#00E5FF]/30 bg-[#00E5FF]/10 px-3 py-2 text-xs text-[#ff9b97]">{error}</p>}
<div className="space-y-4">
<label className="flex cursor-pointer items-start gap-2.5 rounded-lg border border-[#3a312e] bg-[#060403] p-3">
@ -391,10 +391,10 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
checked={licence}
onChange={(event) => setLicence(event.target.checked)}
disabled={uploading}
className="mt-0.5 h-4 w-4 shrink-0 accent-[#FF1610]"
className="mt-0.5 h-4 w-4 shrink-0 accent-[#00E5FF]"
/>
<span className="text-xs leading-relaxed text-[#c7c3c1]">
{SUBMISSION_LICENSE_TEXT} <span className="text-[#FF1610]">(required)</span>
{SUBMISSION_LICENSE_TEXT} <span className="text-[#00E5FF]">(required)</span>
</span>
</label>
@ -407,7 +407,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
onChange={(event) => setCreditName(event.target.value)}
disabled={uploading}
placeholder="How your name should appear in the credit"
className="mt-1 w-full rounded-md border border-[#3a312e] bg-[#060403] px-3 py-2 text-sm text-[#f5efed] placeholder:text-[#6b6563] focus:border-[#FF1610] focus:outline-none"
className="mt-1 w-full rounded-md border border-[#3a312e] bg-[#060403] px-3 py-2 text-sm text-[#f5efed] placeholder:text-[#6b6563] focus:border-[#00E5FF] focus:outline-none"
/>
{creditTooLong && <span className="mt-1 block text-[#ff6f6b]">Up to {SUBMISSION_CREDIT_MAX} characters.</span>}
</label>
@ -421,7 +421,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
disabled={uploading}
rows={2}
placeholder="How licensors can reach you — shown only to admins"
className="mt-1 w-full resize-none rounded-md border border-[#3a312e] bg-[#060403] px-3 py-2 text-sm text-[#f5efed] placeholder:text-[#6b6563] focus:border-[#FF1610] focus:outline-none"
className="mt-1 w-full resize-none rounded-md border border-[#3a312e] bg-[#060403] px-3 py-2 text-sm text-[#f5efed] placeholder:text-[#6b6563] focus:border-[#00E5FF] focus:outline-none"
/>
{contactTooLong && <span className="mt-1 block text-[#ff6f6b]">Up to {SUBMISSION_CONTACT_MAX} characters.</span>}
</label>
@ -430,7 +430,7 @@ function SubmitForm({ token, box }: { token: string; box: SubmissionBoxInfo }):
<button
type="submit"
disabled={!ready || uploading}
className="w-full rounded-md bg-[#FF1610] px-4 py-2.5 text-sm font-semibold text-white transition-colors duration-150 hover:bg-[#e3130e] disabled:cursor-not-allowed disabled:opacity-40"
className="w-full rounded-md bg-[#00E5FF] px-4 py-2.5 text-sm font-semibold text-black transition-colors duration-150 hover:bg-[#5cefff] disabled:cursor-not-allowed disabled:opacity-40"
>
{uploading ? `Sending… ${progress}%` : "Send to the collective"}
</button>