feat(web): WelcomePage landing component

Add the public landing page: a static Charm-box tapir mascot (reusing the
shared tapirLine helpers and charm palette), a tagline, and a single
'Get Started' CTA into the shared Dex flow — sign-in and sign-up are the
same URL (ADR-012). Logged-in visitors get a greeting plus links back into
the app and to log out. Regenerated views_templ.go committed alongside.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 21:50:21 +02:00
co-authored by Claude Opus 4.8
parent 8ca374e657
commit 3a27bf1126
3 changed files with 633 additions and 466 deletions
+36
View File
@@ -266,6 +266,32 @@ var (
tapirFrameHTML3 = tapirFrameHTML("~")
)
// welcomeHeroHTML is the static Charm-box tapir mascot on the public landing
// page — the same rounded purple box / pink tapir / mint accents as the spinner,
// but a single still frame with a friendly tagline instead of the animation.
// Built from the shared tapirLine helpers so the aesthetic stays in one place.
func welcomeHeroHTML() string {
top := tapirSpan(CharmPurple, "╭"+strings.Repeat("─", tapirInteriorW)+"╮")
bottom := tapirSpan(CharmPurple, "╰"+strings.Repeat("─", tapirInteriorW)+"╯")
lines := []string{
top,
tapirLine(tapirRun{s: " "}, tapirRun{s: "◆", color: CharmMint}, tapirRun{s: " "}, tapirRun{s: "tapir", color: CharmCream}),
tapirLine(),
tapirLine(tapirRun{s: " "}, tapirRun{s: "▄▄▄▄▄", color: CharmPink}),
tapirLine(tapirRun{s: " "}, tapirRun{s: "▄█▓▓▓▓█▄", color: CharmPink}, tapirRun{s: " "}, tapirRun{s: "∩", color: CharmMint}),
tapirLine(tapirRun{s: " "}, tapirRun{s: "█▓(", color: CharmPink}, tapirRun{s: " "}, tapirRun{s: "◕ ◕", color: CharmMint}, tapirRun{s: ")▓█", color: CharmPink}, tapirRun{s: "──┘", color: CharmMint}),
tapirLine(tapirRun{s: " "}, tapirRun{s: "▀█▓▓▓▓█▀", color: CharmPink}),
tapirLine(tapirRun{s: " "}, tapirRun{s: "██▄▄██", color: CharmPink}),
tapirLine(tapirRun{s: " "}, tapirRun{s: "▀▀", color: CharmPink}, tapirRun{s: " "}, tapirRun{s: "▀▀", color: CharmPink}),
tapirLine(),
tapirLine(tapirRun{s: " "}, tapirRun{s: "watch less, know more", color: CharmMint}),
bottom,
}
return strings.Join(lines, "\n")
}
var welcomeHero = welcomeHeroHTML()
// summarizeModeLabel names the current mode for display.
func summarizeModeLabel(auto bool) string {
if auto {
@@ -573,6 +599,16 @@ main { max-width: 60rem; margin: 0 auto; padding: var(--s4) var(--s3); }
.confirm-delete > summary:hover { background: #3a1714; }
}
/* public landing page (/welcome) — the Charm-box mascot hero plus the sign-in CTA */
.welcome { text-align: center; padding: var(--s5) var(--s3); display: flex; flex-direction: column; align-items: center; gap: var(--s4); }
.welcome-hero { background: #0d0d12; border-radius: 10px; padding: .9em 1.1em; display: inline-block; box-shadow: 0 2px 14px rgba(118, 83, 252, .25); }
.welcome-hero pre { margin: 0; white-space: pre; font: .82rem/1.15 ui-monospace, SFMono-Regular, Menlo, "Cascadia Code", monospace; }
.welcome-title { font-size: 1.9rem; line-height: 1.2; margin: 0; }
.welcome-tagline { color: var(--muted); font-size: 1.05rem; line-height: 1.5; margin: 0; max-width: 32rem; }
.welcome-cta { display: flex; gap: var(--s3); flex-wrap: wrap; justify-content: center; align-items: center; }
.welcome-sub { color: var(--muted); font-size: .9rem; margin: 0; }
.btn-lg { padding: .6rem 1.6rem; font-size: 1.05rem; }
@media (max-width: 640px) {
main { padding: var(--s3) var(--s2); }
.filters { gap: var(--s2); }