gitstack

grindlemire/gothem-stack code browser

4.8 KB 165 lines 2025-11-29 · 98bbe10 raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package home

import "github.com/grindlemire/gothem-stack/web/components/page"

templ Page(username string) {
	@page.Base("home") {
		<div class="min-h-screen flex flex-col">
			<!-- Top bar -->
			<div class="navbar bg-base-100 border-b border-base-200">
				<div class="flex-1">
					<a class="btn btn-ghost text-xl normal-case">Gothem Stack</a>
				</div>
				<div class="flex-none gap-4">
					@userStatus(username)
					@themeToggle()
				</div>
			</div>
			<!-- Main content -->
			<main class="flex-1 container mx-auto px-6 py-12 max-w-2xl">
				<!-- Demo sections -->
				<div class="space-y-12">
					@htmxSection()
					<div class="divider">✦</div>
					@alpineSection()
				</div>
			</main>
			<!-- Footer -->
			<footer class="footer footer-center p-4 bg-base-200 text-base-content/60">
				<aside>
					<p class="text-sm">Built with Go, Templ, HTMX, Echo & Mage</p>
				</aside>
			</footer>
		</div>
	}
}

templ userStatus(username string) {
	if username != "" {
		<div class="badge badge-success gap-2">
			<div class="w-2 h-2 rounded-full bg-success-content animate-pulse"></div>
			{ username }
		</div>
	} else {
		<div class="dropdown dropdown-end">
			<div tabindex="0" class="badge badge-ghost cursor-help">Signed out</div>
			<div tabindex="0" class="dropdown-content z-10 card card-compact w-64 p-2 shadow bg-base-100">
				<div class="card-body text-sm space-y-2">
					<p>Try these query params:</p>
					<div class="space-y-1">
						<div><code class="text-xs bg-base-200 px-1 rounded">?username=foo</code> — simulated auth</div>
						<div><code class="text-xs bg-base-200 px-1 rounded">?username=reject</code> — auth failure</div>
					</div>
				</div>
			</div>
		</div>
	}
}

templ themeToggle() {
	<label class="swap swap-rotate btn btn-ghost btn-circle btn-sm" x-cloak>
		<input
			type="checkbox"
			x-model="isDark"
			x-on:change="localStorage.setItem('theme', isDark ? 'dark' : 'light')"
		/>
		<span class="swap-off i-mdi-white-balance-sunny text-xl"></span>
		<span class="swap-on i-mdi-moon-waning-crescent text-xl"></span>
	</label>
}

templ htmxSection() {
	<section class="space-y-4">
		<div class="flex items-center gap-3">
			<div class="badge badge-primary badge-lg">HTMX</div>
			<h2 class="text-lg font-semibold">Server-Side Generation</h2>
		</div>
		<p class="text-base-content/70">
			Click the button to fetch a random string from the server with a built-in delay to
			demonstrate the loading spinner. The response replaces the content below using HTMX swapping.
		</p>
		<div class="flex flex-col items-center gap-4 py-4">
			<button
				class="btn btn-primary"
				hx-get="/random-string"
				hx-target="#random-string"
				hx-swap="outerHTML swap:200ms"
				hx-indicator="#loading-spinner"
				_="on click 
					add .opacity-0 to #random-string
					wait 100ms
					set #random-string.innerHTML to ''"
			>
				<span class="i-mdi-dice-multiple lg:text-2xl"></span>
				Generate Random String
			</button>
			<div class="relative min-h-[3rem] flex items-center justify-center w-full">
				<div
					id="random-string"
					class="font-mono text-lg tracking-wide transition-opacity duration-200"
				></div>
				<span
					id="loading-spinner"
					class="htmx-indicator loading loading-spinner loading-md text-primary absolute"
				></span>
			</div>
		</div>
	</section>
}

templ alpineSection() {
	<section class="space-y-4" x-data="{ count: 0 }">
		<div class="flex items-center gap-3">
			<div class="badge badge-secondary badge-lg">Alpine.js</div>
			<h2 class="text-lg font-semibold">Client-Side Reactivity</h2>
		</div>
		<p class="text-base-content/70">
			A simple counter demonstrating Alpine.js reactive state. All changes happen instantly in the browser.
		</p>
		<div class="flex flex-col items-center gap-6 py-4">
			<!-- Counter display -->
			<div class="stats shadow">
				<div class="stat">
					<div class="stat-title">Current Count</div>
					<div class="stat-value text-center tabular-nums" x-text="count"></div>
					<div class="stat-desc text-center">
						<span x-show="count > 0" x-transition>↑ positive</span>
						<span x-show="count < 0" x-transition>↓ negative</span>
						<span x-show="count === 0" x-transition>neutral</span>
					</div>
				</div>
			</div>
			<!-- Controls -->
			<div class="join">
				<button
					class="btn join-item"
					x-on:click="count--"
				>
					<span class="i-mdi-minus"></span>
				</button>
				<button
					class="btn join-item"
					x-on:click="count = 0"
				>
					Reset
				</button>
				<button
					class="btn join-item"
					x-on:click="count++"
				>
					<span class="i-mdi-plus"></span>
				</button>
			</div>
		</div>
	</section>
}

templ RandomString(s string) {
	<div
		id="random-string"
		class="font-mono text-lg tracking-wide opacity-0 transition-opacity duration-200 badge badge-outline badge-lg py-4 px-6"
		_="on load wait 10ms then remove .opacity-0"
	>
		GS2-{ s }
	</div>
}