package sections import ( "fmt" models "github.com/onsonr/sonr/internal/orm/marketing" "github.com/onsonr/sonr/pkg/nebula/global/ui" ) // ╭───────────────────────────────────────────────────────────╮ // │ Data Model │ // ╰───────────────────────────────────────────────────────────╯ // hero is the (1st) home page hero section var hero = &models.Hero{ TitleFirst: "Simplified", TitleEmphasis: "self-custody", TitleSecond: "for everyone", Subtitle: "Sonr is a modern re-imagination of online user identity, empowering users to take ownership of their digital footprint and unlocking a new era of self-sovereignty.", PrimaryButton: &models.Button{Text: "Get Started", Href: "/register"}, SecondaryButton: &models.Button{Text: "Learn More", Href: "/about"}, Image: &models.Image{ Src: "https://cdn.sonr.id/img/hero-clipped.svg", Width: "500", Height: "500", }, Stats: []*models.Stat{ {Value: "476", Label: "Tradeable Crypto Assets", Denom: "+"}, {Value: "1.44", Label: "Verified Identities", Denom: "K"}, {Value: "1.5", Label: "Encrypted Messages Sent", Denom: "M+"}, {Value: "50", Label: "Decentralized Global Nodes", Denom: "+"}, }, } // ╭───────────────────────────────────────────────────────────╮ // │ Render Section View │ // ╰───────────────────────────────────────────────────────────╯ // Hero Section templ Hero() {
@heroTitle(hero.TitleFirst, hero.TitleEmphasis, hero.TitleSecond)

{ hero.Subtitle }

@ui.PrimaryButton("/register", "Get Started") @ui.SecondaryButton("/about", "Learn More")
@heroImage(hero) @stats(hero.Stats)
} templ heroTitle(first string, emphasis string, second string) {

{ first } { emphasis } { second }

} templ heroImage(hero *models.Hero) {
Hero
} templ stats(stats []*models.Stat) {
for _, item := range stats {

{ item.Value }{ item.Denom }

{ item.Label }

}
@counterAnimation()
} func counterXData(value string) string { return fmt.Sprintf("counter(%s)", value) } script counterAnimation() { document.addEventListener('alpine:init', () => { Alpine.data('counter', (target = 0, duration = 3000) => ({ startTimestamp: null, step: null, rawValue: 0, counterValue: 0, target: target, precision: (target % 1 === 0) ? 0 : (target.toString().split('.')[1] || []).length, animationRequestId: null, animationCompleted: false, observer: null, init() { // Intersection observer to watch visibility this.observer = new IntersectionObserver(entries => { entries.forEach(entry => { // Check if element is in view if (entry.isIntersecting && !this.animationCompleted) { this.startAnimation() } }) }) this.observer.observe(this.$el) }, startAnimation() { this.step = (timestamp) => { if (!this.startTimestamp) this.startTimestamp = timestamp const progress = Math.min((timestamp - this.startTimestamp) / duration, 1) const easedProgress = this.easeOut(progress) this.rawValue = parseFloat((easedProgress * this.target).toFixed(this.precision)) this.counterValue = this.rawValue.toFixed(this.precision) if (progress < 1) { this.animationRequestId = window.requestAnimationFrame(this.step) } else { this.animationCompleted = true } } this.animationRequestId = window.requestAnimationFrame(this.step); }, easeOut(t) { return 1 - Math.pow(1 - t, 5) }, destroy() { // Detach the handler, avoiding memory and side-effect leakage this.animationRequestId && window.cancelAnimationFrame(this.step) this.observer && this.observer.disconnect() }, })) }) }