The previous pass kept the dark base and the red icons, which did not read as the acampada. Now: - Light theme: white page, black ink and borders, black buttons with a hard neon offset shadow, neon for focus and the active nav item; h1/h2 in Anton with the stepped neon shadow of the site headings. - Nav, empty-state, spinner and pattern assets in black; platform icons keep their dark disc with a neon stroke. - New mark: three dome tents wired from their doors into a switch with an antenna, drawn in the inked style of the acampada logo (curved poles, ball ends, hatching) over a neon offset copy. LogoMark renders it inline; visible product name is T-HUB. - docs/brand: t-hub.svg and a lockup with the Anton wordmark converted to outlines so it renders without the font. - Public submission page moved to the light palette. Checked with headless Chromium screenshots (offline sample data) on desktop and mobile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
4.9 KiB
TypeScript
137 lines
4.9 KiB
TypeScript
import type { ReactElement } from "react";
|
||
|
||
/**
|
||
* The T-HUB mark, inline: three dome tents in a circle, each with a cable
|
||
* out of its door into a switch with an antenna. Drawn in the hand-inked
|
||
* style of the acampada26s.net mark, ink in `currentColor` over a neon
|
||
* offset copy. Source: docs/brand/t-hub.svg (same 400×400 geometry).
|
||
*/
|
||
|
||
const TENTS: ReadonlyArray<readonly [number, number]> = [[105, 150], [295, 150], [200, 340]];
|
||
|
||
function Tent({ x, y }: { x: number; y: number }): ReactElement {
|
||
return (
|
||
<g transform={`translate(${x},${y})`}>
|
||
<path fill="none" d="M-58 0 Q-56 -66 -1 -72 Q55 -66 58 0" strokeWidth="7" />
|
||
<path fill="none" d="M-64 1 Q0 -3 64 1" strokeWidth="6" />
|
||
<path fill="none" d="M-44 0 Q-30 -58 -1 -72" strokeWidth="4.5" />
|
||
<path fill="none" d="M44 0 Q30 -58 -1 -72" strokeWidth="4.5" />
|
||
<path d="M-15 0 Q-14 -30 0 -33 Q14 -30 15 0 Z" strokeWidth="4" />
|
||
<path fill="none" d="M-50 -14 L-41 -25 M-50 -4 L-37 -19 M-43 -1 L-33 -13 M-52 -27 L-45 -36" strokeWidth="2.6" />
|
||
<path fill="none" d="M28 -50 L37 -42 M24 -40 L36 -30 M22 -29 L34 -19" strokeWidth="2.6" />
|
||
<circle cx="-64" cy="1" r="5.5" stroke="none" />
|
||
<circle cx="64" cy="1" r="5.5" stroke="none" />
|
||
<circle cx="-1" cy="-73" r="5.5" stroke="none" />
|
||
</g>
|
||
);
|
||
}
|
||
|
||
function Mark({ paper }: { paper: string }): ReactElement {
|
||
return (
|
||
<>
|
||
<path fill="none" d="M105 150 Q112 206 168 214" strokeWidth="5" />
|
||
<path fill="none" d="M295 150 Q288 206 232 214" strokeWidth="5" />
|
||
<path fill="none" d="M200 318 Q197 280 200 236" strokeWidth="5" />
|
||
<path fill="none" d="M200 200 Q198 160 200 118" strokeWidth="6" />
|
||
<path fill="none" d="M184 98 Q200 82 216 98" strokeWidth="5" />
|
||
<path fill="none" d="M171 88 Q200 58 229 88" strokeWidth="5" />
|
||
<circle cx="200" cy="112" r="7" stroke="none" />
|
||
<path d="M168 201 L233 200 L234 235 L167 236 Z" strokeWidth="6" fill={paper} />
|
||
<path d="M179 212 h9 v11 h-9 z M196 212 h9 v11 h-9 z M213 212 h9 v11 h-9 z" stroke="none" />
|
||
{TENTS.map(([x, y]) => <Tent key={`${x}-${y}`} x={x} y={y} />)}
|
||
</>
|
||
);
|
||
}
|
||
|
||
export function LogoMark({
|
||
size = 32,
|
||
className = "text-text",
|
||
title = "T-HUB",
|
||
}: {
|
||
size?: number;
|
||
className?: string;
|
||
title?: string;
|
||
}): ReactElement {
|
||
return (
|
||
<svg width={size} height={size} viewBox="0 0 400 400" role="img" aria-label={title} className={className}>
|
||
<g strokeLinecap="round" strokeLinejoin="round">
|
||
<g transform="translate(7,7)" stroke="#00e5ff" fill="#00e5ff"><Mark paper="#fff" /></g>
|
||
<g stroke="currentColor" fill="currentColor"><Mark paper="#fff" /></g>
|
||
</g>
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* The quiet version of the mark: just the circle-A outline, no arrowheads.
|
||
* Used wherever the brand needs to whisper — empty states, placeholders.
|
||
*/
|
||
export function CircleA({
|
||
size = 64,
|
||
className = "text-accent",
|
||
}: {
|
||
size?: number;
|
||
className?: string;
|
||
}): ReactElement {
|
||
return (
|
||
<svg
|
||
width={size}
|
||
height={size}
|
||
viewBox="0 0 1700 1700"
|
||
aria-hidden="true"
|
||
className={className}
|
||
>
|
||
<g transform="rotate(-17.5 850 850)">
|
||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||
<circle cx="850" cy="850" r="350" strokeWidth="74" />
|
||
<path d="M 850 405 L 464.77 1269.52" strokeWidth="88" />
|
||
<path d="M 850 405 L 1235.23 1269.52" strokeWidth="88" />
|
||
<path d="M 850 405 L 850 350" strokeWidth="88" />
|
||
<path d="M 345 850 L 1355 850" strokeWidth="88" />
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* A single arrowhead lifted from the mark — the brand's bullet point.
|
||
* Points the same way the right-hand barb of the circle-A does.
|
||
*/
|
||
export function ArrowGlyph({
|
||
size = 12,
|
||
className = "text-accent",
|
||
}: {
|
||
size?: number;
|
||
className?: string;
|
||
}): ReactElement {
|
||
return (
|
||
<svg width={size} height={size} viewBox="0 0 120 120" aria-hidden="true" className={className}>
|
||
<polygon points="120,60 30,14 30,106" fill="currentColor" />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Mark + wordmark. Inline below ~96px (sidebar, palette); stacked above it
|
||
* (onboarding hero) so the wordmark never gets dwarfed by the mark.
|
||
*/
|
||
export function Logo({ size = 32, withText = true }: { size?: number; withText?: boolean }): ReactElement {
|
||
if (!withText) return <LogoMark size={size} />;
|
||
const stacked = size >= 96;
|
||
return (
|
||
<span className={`inline-flex ${stacked ? "flex-col items-center gap-3" : "items-center gap-2.5"}`}>
|
||
<LogoMark size={size} />
|
||
<span
|
||
className={`uppercase tracking-wide text-text ${stacked ? "text-4xl" : ""}`}
|
||
style={{
|
||
fontFamily: "var(--font-title)",
|
||
textShadow: "0.04em 0.04em 0 var(--color-neon)",
|
||
...(stacked ? {} : { fontSize: `${Math.max(16, Math.round(size * 0.6))}px` }),
|
||
}}
|
||
>
|
||
T-HUB
|
||
</span>
|
||
</span>
|
||
);
|
||
}
|