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:
@@ -266,6 +266,32 @@ var (
|
|||||||
tapirFrameHTML3 = tapirFrameHTML("~")
|
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.
|
// summarizeModeLabel names the current mode for display.
|
||||||
func summarizeModeLabel(auto bool) string {
|
func summarizeModeLabel(auto bool) string {
|
||||||
if auto {
|
if auto {
|
||||||
@@ -573,6 +599,16 @@ main { max-width: 60rem; margin: 0 auto; padding: var(--s4) var(--s3); }
|
|||||||
.confirm-delete > summary:hover { background: #3a1714; }
|
.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) {
|
@media (max-width: 640px) {
|
||||||
main { padding: var(--s3) var(--s2); }
|
main { padding: var(--s3) var(--s2); }
|
||||||
.filters { gap: var(--s2); }
|
.filters { gap: var(--s2); }
|
||||||
|
|||||||
@@ -31,6 +31,40 @@ templ Layout(title string) {
|
|||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WelcomePage is the public landing page (served at /welcome, outside the auth
|
||||||
|
// guard — ADR-012). Logged out: the tapir mascot, a one-line tagline, and a
|
||||||
|
// single "Get Started" CTA into the shared Dex flow (sign-in and sign-up are the
|
||||||
|
// same URL). Logged in: a greeting plus links back into the app and to log out.
|
||||||
|
templ WelcomePage(user User, loggedIn bool) {
|
||||||
|
@Layout("Tapir — Watch less, know more") {
|
||||||
|
<section class="welcome">
|
||||||
|
<div class="welcome-hero">
|
||||||
|
<pre aria-hidden="true">@templ.Raw(welcomeHero)</pre>
|
||||||
|
</div>
|
||||||
|
if loggedIn {
|
||||||
|
<h1 class="welcome-title">Welcome back</h1>
|
||||||
|
if user.Email != "" {
|
||||||
|
<p class="welcome-tagline">Signed in as { user.Email }.</p>
|
||||||
|
}
|
||||||
|
<div class="welcome-cta">
|
||||||
|
<a class="btn btn-lg" href="/">Go to my Tapir</a>
|
||||||
|
<a class="btn-secondary" href="/auth/logout">Log Out</a>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<h1 class="welcome-title">Watch less, know more</h1>
|
||||||
|
<p class="welcome-tagline">
|
||||||
|
Tapir summarizes the videos your subscriptions publish, so you can
|
||||||
|
skim the gist and decide what is worth your time.
|
||||||
|
</p>
|
||||||
|
<div class="welcome-cta">
|
||||||
|
<a class="btn btn-lg" href="/auth/login">Get Started</a>
|
||||||
|
</div>
|
||||||
|
<p class="welcome-sub">Already have an account? You'll go straight through.</p>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// flashBanner renders a one-shot notification for a flash code (connect success/
|
// flashBanner renders a one-shot notification for a flash code (connect success/
|
||||||
// failure, disconnect, delete, registration). An empty or unknown code renders
|
// failure, disconnect, delete, registration). An empty or unknown code renders
|
||||||
// nothing, so it is safe to drop into any page unconditionally. Reused across the
|
// nothing, so it is safe to drop into any page unconditionally. Reused across the
|
||||||
|
|||||||
+563
-466
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user