Files
nebula/views/welcome.templ

390 lines
11 KiB
Plaintext

package views
import (
"nebula/components"
"nebula/layouts"
)
var welcomeSteps = []string{"Welcome", "Learn", "Get Started"}
// WelcomePage renders the full onboarding page with the specified step
templ WelcomePage(currentStep int) {
@layouts.CenteredCard("Welcome") {
<div slot="header" id="stepper-container" hx-swap-oob:inherited="true">
@components.OnboardingStepper(currentStep, welcomeSteps)
</div>
<div id="step-content" class="step-content">
@WelcomeStepContent(currentStep)
</div>
<div id="htmx-indicator" class="htmx-indicator">
<wa-spinner></wa-spinner>
</div>
<footer slot="footer">
@WelcomeFooter()
</footer>
}
}
// WelcomeStepContent renders the content for a specific step (used for HTMX partials)
templ WelcomeStepContent(step int) {
switch step {
case 1:
@WelcomeStep1()
case 2:
@WelcomeStep2()
case 3:
@WelcomeStep3()
}
}
// WelcomeStepWithStepper renders step content with OOB stepper update for HTMX 4
templ WelcomeStepWithStepper(step int) {
@WelcomeStepContent(step)
<div id="stepper-container" hx-swap-oob="innerHTML">
@components.OnboardingStepper(step, welcomeSteps)
</div>
}
// WelcomeStep1 - Initial welcome screen with features
templ WelcomeStep1() {
@welcomeStyles()
<div class="wa-stack wa-gap-l">
<div class="hero-icon">
<wa-icon name="wallet" family="duotone"></wa-icon>
</div>
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h1 class="wa-heading-xl">Welcome to Sonr</h1>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Your self-sovereign identity wallet powered by WebAssembly
</p>
</div>
<wa-divider></wa-divider>
<div class="wa-stack wa-gap-s">
@FeatureItem("shield-check", "security", "Passwordless Security", "Authenticate with biometrics or hardware keys - no passwords to remember or steal")
@FeatureItem("bolt", "speed", "WASM-Powered", "Wallet runs entirely in your browser - fast, secure, and always available")
@FeatureItem("user-shield", "privacy", "You Own Your Data", "Self-sovereign identity means your keys never leave your device")
</div>
<wa-button
variant="brand"
size="large"
style="width: 100%;"
hx-get="/welcome/step/2"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
hx-on:htmx:after:swap="this.closest('.step-content')?.scrollTo(0,0)"
>
Learn More
<wa-icon slot="end" name="arrow-right"></wa-icon>
</wa-button>
</div>
}
// WelcomeStep2 - How Motr Works explanation
templ WelcomeStep2() {
@welcomeStyles()
<div class="wa-stack wa-gap-l">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">How Motr Works</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
A next-generation wallet built on WebAuthn and WASM
</p>
</div>
<wa-divider></wa-divider>
<div class="wa-stack wa-gap-m">
@InfoCallout("microchip", "WebAssembly Runtime", "Your wallet logic runs as compiled Go code in a secure WASM sandbox, directly in your browser.")
@InfoCallout("fingerprint", "Passkey Authentication", "Use Face ID, Touch ID, or hardware security keys. Your biometrics stay on your device.")
@InfoCallout("network-wired", "Decentralized Identity", "Connect to any app with OpenID Connect - you control what data to share.")
@InfoCallout("key", "Multi-Chain Support", "One wallet for Sonr, Ethereum, Cosmos, Bitcoin and more via IBC.")
</div>
<div class="wa-cluster wa-gap-s" style="justify-content: space-between;">
<wa-button
variant="neutral"
appearance="outlined"
hx-get="/welcome/step/1"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
>
<wa-icon slot="start" name="arrow-left"></wa-icon>
Back
</wa-button>
<wa-button
variant="brand"
hx-get="/welcome/step/3"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
>
Get Started
<wa-icon slot="end" name="arrow-right"></wa-icon>
</wa-button>
</div>
</div>
}
// WelcomeStep3 - Action selection (Create/Sign In)
templ WelcomeStep3() {
@welcomeStyles()
<div class="wa-stack wa-gap-l">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">Ready to Begin?</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Create a new wallet or sign in to an existing one
</p>
</div>
<wa-divider></wa-divider>
<div class="wa-grid" style="--min-column-size: 180px; gap: var(--wa-space-m);">
<a href="/register" class="action-card">
<wa-icon name="user-plus" family="duotone"></wa-icon>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-m">Create Wallet</span>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
New to Sonr? Set up your wallet in minutes
</span>
</div>
</a>
<a href="/login" class="action-card">
<wa-icon name="right-to-bracket" family="duotone"></wa-icon>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-m">Sign In</span>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
Access your existing wallet
</span>
</div>
</a>
</div>
<wa-divider>or</wa-divider>
<wa-button
variant="neutral"
appearance="outlined"
style="width: 100%;"
hx-get="/welcome/qr-scanner"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
>
<wa-icon slot="start" name="qrcode"></wa-icon>
Scan QR Code
</wa-button>
<wa-callout variant="brand" appearance="filled">
<wa-icon slot="icon" name="circle-info"></wa-icon>
<span class="wa-caption-s">
Already have a passkey registered on another device? Use QR code to sync your wallet.
</span>
</wa-callout>
<wa-button
variant="neutral"
appearance="plain"
style="width: 100%;"
hx-get="/welcome/step/2"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
>
<wa-icon slot="start" name="arrow-left"></wa-icon>
Back to How It Works
</wa-button>
</div>
}
// WelcomeFooter renders the network status and links footer
templ WelcomeFooter() {
@welcomeStyles()
<div class="wa-stack wa-gap-m wa-align-items-center">
<div class="network-status">
<div class="status-dot"></div>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-600);">
Sonr Network: <strong style="color: var(--wa-color-success);">Operational</strong>
</span>
</div>
<div class="wa-cluster wa-justify-content-center wa-gap-m">
<wa-button appearance="plain" size="small" onclick="window.open('https://sonr.io', '_blank')">
Learn about Sonr
</wa-button>
<span style="color: var(--wa-color-neutral-300);">|</span>
<wa-button appearance="plain" size="small" onclick="window.open('https://docs.sonr.io', '_blank')">
Documentation
</wa-button>
</div>
</div>
}
// FeatureItem renders a feature highlight with icon
templ FeatureItem(icon string, variant string, title string, description string) {
<div class="feature-item">
<div class={ "feature-icon", variant }>
<wa-icon name={ icon }></wa-icon>
</div>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-xs">{ title }</span>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
{ description }
</span>
</div>
</div>
}
// InfoCallout renders an informational callout box
templ InfoCallout(icon string, title string, description string) {
<wa-callout variant="neutral">
<wa-icon slot="icon" name={ icon }></wa-icon>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-xs">{ title }</span>
<span class="wa-caption-s">{ description }</span>
</div>
</wa-callout>
}
// welcomeStyles contains the CSS specific to the welcome page
templ welcomeStyles() {
<style>
/* HTMX 4 indicator and transition styles */
.htmx-indicator {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 100;
}
.htmx-request .htmx-indicator {
display: flex;
}
.htmx-request.step-content {
opacity: 0.5;
pointer-events: none;
transition: opacity 0.2s ease;
}
/* View transition support for HTMX 4 */
@view-transition {
navigation: auto;
}
::view-transition-old(step-content),
::view-transition-new(step-content) {
animation-duration: 0.25s;
}
.step-content {
view-transition-name: step-content;
position: relative;
}
.hero-icon {
width: 80px;
height: 80px;
border-radius: var(--wa-radius-l);
background: linear-gradient(135deg, var(--wa-color-primary), #0090ff);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--wa-space-l);
box-shadow: 0 8px 32px rgba(23, 194, 255, 0.3);
}
.hero-icon wa-icon {
font-size: 40px;
color: white;
}
.feature-item {
display: flex;
align-items: flex-start;
gap: var(--wa-space-m);
padding: var(--wa-space-m);
background: var(--wa-color-surface-alt);
border-radius: var(--wa-radius-m);
}
.feature-icon {
width: 40px;
height: 40px;
border-radius: var(--wa-radius-s);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.feature-icon.security {
background: var(--wa-color-success-subtle);
color: var(--wa-color-success);
}
.feature-icon.speed {
background: var(--wa-color-primary-subtle);
color: var(--wa-color-primary);
}
.feature-icon.privacy {
background: var(--wa-color-warning-subtle);
color: var(--wa-color-warning);
}
.action-card {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--wa-space-m);
padding: var(--wa-space-xl);
background: var(--wa-color-surface-alt);
border-radius: var(--wa-radius-l);
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s;
text-align: center;
text-decoration: none;
color: inherit;
}
.action-card:hover {
border-color: var(--wa-color-primary);
background: var(--wa-color-primary-subtle);
}
.action-card wa-icon {
font-size: 2.5rem;
color: var(--wa-color-primary);
}
.network-status {
display: flex;
align-items: center;
justify-content: center;
gap: var(--wa-space-xs);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--wa-color-success);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Viewport constraints for popup/webview */
@media (max-width: 400px) {
.hero-icon {
width: 64px;
height: 64px;
}
.hero-icon wa-icon {
font-size: 32px;
}
.feature-item {
padding: var(--wa-space-s);
}
.feature-icon {
width: 32px;
height: 32px;
}
.action-card {
padding: var(--wa-space-m);
}
.action-card wa-icon {
font-size: 2rem;
}
}
@media (max-height: 600px) {
.hero-icon {
width: 56px;
height: 56px;
margin-bottom: var(--wa-space-m);
}
.hero-icon wa-icon {
font-size: 28px;
}
}
</style>
}