ReviewOS

also looking at this

stacks/wildloop

Migrate :text/x-text bindings to {⁠{ }} interpolation (stx 0.2.72)

#990
Merged glennmichael123 wants to merge chore/migrate-text-to-mustache into main
50 files +611 -371

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

resources/components/nav.stxmodified+2-2
Changes to resources/components/nav.stx
@@ -25,7 +25,7 @@
2525 x-to="link.to"
2626 activeClass="nav-active"
2727 class="px-3 py-2 font-medium text-slate-500 text-sm dark:text-slate-400 hover:text-slate-900 dark:hover:text-white rounded transition-colors"
28 x-text="link.label" />
28 >{{ link.label }}</StxLink>
2929 </div>
3030 </div>
3131
@@ -59,7 +59,7 @@
5959 <StxLink :for="link in navLinks" :key="link.to"
6060 x-to="link.to"
6161 class="block px-3 py-2 text-slate-500 text-sm dark:text-slate-400 rounded"
62 x-text="link.label" />
62 >{{ link.label }}</StxLink>
6363 <StxLink to="/record" class="block px-3 py-2 font-semibold text-emerald-600 text-sm rounded">+ Record Activity</StxLink>
6464 <StxLink to="/clubs" class="block px-3 py-2 text-slate-500 text-sm dark:text-slate-400 rounded">Clubs</StxLink>
6565 <StxLink to="/stats" class="block px-3 py-2 text-slate-500 text-sm dark:text-slate-400 rounded">Stats</StxLink>
resources/views/activity/[id].stxmodified+30-30
Changes to resources/views/activity/[id].stx
@@ -99,13 +99,13 @@
9999 <div class="card mb-6">
100100 <div class="flex items-center justify-between mb-4">
101101 <div class="flex items-center gap-3">
102 <div class="w-12 h-12 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 text-lg font-bold" :text="activity.userName.charAt(0)"></div>
102 <div class="w-12 h-12 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 text-lg font-bold">{{ activity.userName.charAt(0) }}</div>
103103 <div>
104 <p class="font-bold dark:text-white" :text="activity.userName"></p>
104 <p class="font-bold dark:text-white">{{ activity.userName }}</p>
105105 <p class="text-sm text-gray-500 dark:text-slate-400">
106 <span :text="activity.activityType"></span>
106 <span>{{ activity.activityType }}</span>
107107 <span class="mx-1">·</span>
108 <span :text="tb.timeAgo(activity.created_at)"></span>
108 <span>{{ tb.timeAgo(activity.created_at) }}</span>
109109 </p>
110110 </div>
111111 </div>
@@ -113,7 +113,7 @@
113113 <button
114114 @click="editing ? closeEditor() : openEditor(activity)"
115115 class="px-3 py-1.5 text-xs font-semibold rounded-lg border border-gray-200 dark:border-slate-600 text-gray-600 dark:text-slate-300 hover:border-emerald-500 hover:text-emerald-600 transition"
116 :text="editing ? 'Close' : 'Edit'"></button>
116>{{ editing ? 'Close' : 'Edit' }}</button>
117117 <button
118118 :if="confirmingDelete"
119119 @click="cancelDelete()"
@@ -122,14 +122,14 @@
122122 @click="deleteRun(activity)"
123123 x-class="confirmingDelete ? 'bg-red-600 text-white border-red-600' : 'border-gray-200 dark:border-slate-600 text-red-500 hover:border-red-400'"
124124 class="px-3 py-1.5 text-xs font-semibold rounded-lg border transition"
125 :text="confirmingDelete ? 'Confirm delete' : 'Delete'"></button>
125>{{ confirmingDelete ? 'Confirm delete' : 'Delete' }}</button>
126126 </div>
127127 </div>
128128
129 <h1 class="text-2xl font-bold dark:text-white mb-2" :text="activity.title"></h1>
130 <StxLink :if="activity.trail_id" x-to="'/trail/' + activity.trail_id" class="text-sm text-emerald-600 hover:underline" :text="'→ ' + activity.trail_name"></StxLink>
131 <p :if="activity.notes && !editing" class="mt-3 text-sm text-gray-600 dark:text-slate-300" :text="activity.notes"></p>
132 <p :if="editorError && !editing" class="mt-2 text-sm text-red-500" :text="editorError"></p>
129 <h1 class="text-2xl font-bold dark:text-white mb-2">{{ activity.title }}</h1>
130 <StxLink :if="activity.trail_id" x-to="'/trail/' + activity.trail_id" class="text-sm text-emerald-600 hover:underline">{{ '→ ' + activity.trail_name }}</StxLink>
131 <p :if="activity.notes && !editing" class="mt-3 text-sm text-gray-600 dark:text-slate-300">{{ activity.notes }}</p>
132 <p :if="editorError && !editing" class="mt-2 text-sm text-red-500">{{ editorError }}</p>
133133
134134 <!-- Owner edit panel (#954) -->
135135 <div :if="editing" class="mt-4 pt-4 border-t border-gray-100 dark:border-slate-700 space-y-3">
@@ -161,9 +161,9 @@
161161 <textarea rows="2" x-value="formNotes" @input="e => formNotes.set(e.target.value)" placeholder="How did it go?" class="w-full px-3 py-2 border border-gray-200 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-sm"></textarea>
162162 </div>
163163 <p :if="activity.hasGps" class="text-xs text-gray-400">Distance, time and elevation come from the GPS track and can't be edited.</p>
164 <p :if="editorError" class="text-sm text-red-500" :text="editorError"></p>
164 <p :if="editorError" class="text-sm text-red-500">{{ editorError }}</p>
165165 <div class="flex gap-2">
166 <button @click="saveEdit(activity)" class="px-4 py-2 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition" :text="saving ? 'Saving…' : 'Save changes'"></button>
166 <button @click="saveEdit(activity)" class="px-4 py-2 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition">{{ saving ? 'Saving…' : 'Save changes' }}</button>
167167 <button @click="closeEditor()" class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-slate-400 hover:text-gray-700 transition">Cancel</button>
168168 </div>
169169 </div>
@@ -177,28 +177,28 @@
177177 <!-- Stats -->
178178 <div class="grid grid-cols-3 sm:grid-cols-6 gap-4 mb-6">
179179 <div class="card text-center">
180 <p class="text-xl font-bold text-emerald-600" :text="activity.distance.toFixed(1)"></p>
180 <p class="text-xl font-bold text-emerald-600">{{ activity.distance.toFixed(1) }}</p>
181181 <p class="metric-label">Miles</p>
182182 </div>
183183 <div class="card text-center">
184 <p class="text-xl font-bold dark:text-white" :text="activity.moving_time || activity.duration"></p>
184 <p class="text-xl font-bold dark:text-white">{{ activity.moving_time || activity.duration }}</p>
185185 <p class="metric-label">Moving Time</p>
186 <p :if="activity.moving_time && activity.moving_time !== activity.duration" class="text-xs text-gray-400 mt-1" :text="'Elapsed ' + activity.duration"></p>
186 <p :if="activity.moving_time && activity.moving_time !== activity.duration" class="text-xs text-gray-400 mt-1">{{ 'Elapsed ' + activity.duration }}</p>
187187 </div>
188188 <div class="card text-center">
189 <p class="text-xl font-bold dark:text-white" :text="activity.pace"></p>
189 <p class="text-xl font-bold dark:text-white">{{ activity.pace }}</p>
190190 <p class="metric-label">Pace</p>
191191 </div>
192192 <div class="card text-center">
193 <p class="text-xl font-bold text-blue-600" :text="activity.elevation_gain.toLocaleString()"></p>
193 <p class="text-xl font-bold text-blue-600">{{ activity.elevation_gain.toLocaleString() }}</p>
194194 <p class="metric-label">Elev (ft)</p>
195195 </div>
196196 <div class="card text-center">
197 <p class="text-xl font-bold text-orange-600" :text="activity.calories || 0"></p>
197 <p class="text-xl font-bold text-orange-600">{{ activity.calories || 0 }}</p>
198198 <p class="metric-label">Calories</p>
199199 </div>
200200 <div class="card text-center">
201 <p class="text-xl font-bold text-red-500" :text="activity.heartRateAvg || '--'"></p>
201 <p class="text-xl font-bold text-red-500">{{ activity.heartRateAvg || '--' }}</p>
202202 <p class="metric-label">Avg HR</p>
203203 </div>
204204 </div>
@@ -209,9 +209,9 @@
209209 <div class="space-y-2">
210210 <template :for="split in activity.splits">
211211 <div class="grid grid-cols-3 gap-2 items-center p-3 rounded-lg">
212 <span class="text-sm font-medium dark:text-white" :text="'Mile ' + split.mile"></span>
213 <span class="text-center text-sm font-semibold dark:text-white" :text="split.pace"></span>
214 <span class="text-right text-sm text-gray-600 dark:text-slate-400" :text="split.elev + ' ft'"></span>
212 <span class="text-sm font-medium dark:text-white">{{ 'Mile ' + split.mile }}</span>
213 <span class="text-center text-sm font-semibold dark:text-white">{{ split.pace }}</span>
214 <span class="text-right text-sm text-gray-600 dark:text-slate-400">{{ split.elev + ' ft' }}</span>
215215 </div>
216216 </template>
217217 </div>
@@ -222,11 +222,11 @@
222222 <div class="flex items-center gap-6">
223223 <button @click="handlers.toggleKudos()" class="flex items-center gap-2 transition" x-class="hasKudos ? 'text-red-500' : 'text-gray-500 hover:text-red-500'">
224224 <Icon name="heart" size="18" />
225 <span class="font-medium" x-text="activity.kudos_count + ' Kudos'"></span>
225 <span class="font-medium">{{ activity.kudos_count + ' Kudos' }}</span>
226226 </button>
227227 <span class="flex items-center gap-2 text-gray-500">
228228 <Icon name="message-circle" size="18" />
229 <span class="font-medium" x-text="comments.length + ' Comments'"></span>
229 <span class="font-medium">{{ comments.length + ' Comments' }}</span>
230230 </span>
231231 </div>
232232 </div>
@@ -237,16 +237,16 @@
237237 <div class="space-y-4 mb-4">
238238 <template :for="comment in comments">
239239 <div class="flex gap-3">
240 <div class="w-8 h-8 rounded-full bg-gray-100 dark:bg-slate-700 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-slate-300 shrink-0" :text="comment.userName.charAt(0)"></div>
240 <div class="w-8 h-8 rounded-full bg-gray-100 dark:bg-slate-700 flex items-center justify-center text-xs font-bold text-gray-600 dark:text-slate-300 shrink-0">{{ comment.userName.charAt(0) }}</div>
241241 <div class="flex-1">
242242 <div class="flex items-center gap-2 mb-1">
243 <span class="text-sm font-semibold dark:text-white" :text="comment.userName"></span>
244 <span class="text-xs text-gray-400" :text="tb.timeAgo(comment.createdAt)"></span>
243 <span class="text-sm font-semibold dark:text-white">{{ comment.userName }}</span>
244 <span class="text-xs text-gray-400">{{ tb.timeAgo(comment.createdAt) }}</span>
245245 </div>
246246 <p class="text-sm text-gray-600 dark:text-slate-400">
247247 <template :for="seg in mentionSegments(comment.body)">
248 <StxLink :if="seg.userId" x-to="'/athlete/' + seg.userId" class="text-emerald-600 font-medium hover:underline" :text="seg.text"></StxLink>
249 <span :if="!seg.userId" :text="seg.text"></span>
248 <StxLink :if="seg.userId" x-to="'/athlete/' + seg.userId" class="text-emerald-600 font-medium hover:underline">{{ seg.text }}</StxLink>
249 <span :if="!seg.userId">{{ seg.text }}</span>
250250 </template>
251251 </p>
252252 </div>
@@ -264,7 +264,7 @@
264264 <button
265265 @click="pickMention(candidate.name)"
266266 class="px-3 py-1.5 text-xs font-medium rounded-full bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800/40 hover:bg-emerald-100 transition"
267 :text="'@' + candidate.name"></button>
267>{{ '@' + candidate.name }}</button>
268268 </template>
269269 </div>
270270 <div class="flex gap-3">
resources/views/athlete/[id].stxmodified+14-14
Changes to resources/views/athlete/[id].stx
@@ -76,40 +76,40 @@
7676 <!-- Header -->
7777 <div class="mb-6 card">
7878 <div class="flex flex-col gap-6 items-center sm:flex-row">
79 <div class="flex items-center justify-center h-24 w-24 font-bold text-3xl text-emerald-600 bg-emerald-100 dark:bg-emerald-900/30 rounded-full" :text="(profile.user.name || 'U').charAt(0)"></div>
79 <div class="flex items-center justify-center h-24 w-24 font-bold text-3xl text-emerald-600 bg-emerald-100 dark:bg-emerald-900/30 rounded-full">{{ (profile.user.name || 'U').charAt(0) }}</div>
8080 <div class="flex-1 text-center sm:text-left">
8181 <div class="flex flex-col sm:flex-row sm:items-center gap-3 justify-center sm:justify-start">
82 <h1 class="font-bold text-2xl dark:text-white" :text="profile.user.name"></h1>
82 <h1 class="font-bold text-2xl dark:text-white">{{ profile.user.name }}</h1>
8383 <button
8484 :if="!isSelf"
8585 @click="onFollow()"
8686 x-class="isFollowing ? 'bg-gray-100 text-gray-600 dark:bg-slate-700 dark:text-slate-300' : 'bg-emerald-600 text-white hover:bg-emerald-700'"
8787 class="px-4 py-1.5 font-semibold text-sm rounded-lg transition"
88 :text="isFollowing ? 'Following' : 'Follow'"></button>
88>{{ isFollowing ? 'Following' : 'Follow' }}</button>
8989 </div>
9090 <div class="flex flex-wrap gap-4 justify-center sm:justify-start mt-3">
9191 <div class="text-center">
92 <p class="text-lg font-bold text-emerald-600" :text="profile.stats.totalDistance.toFixed(1)"></p>
92 <p class="text-lg font-bold text-emerald-600">{{ profile.stats.totalDistance.toFixed(1) }}</p>
9393 <p class="metric-label">Miles</p>
9494 </div>
9595 <div class="text-center">
96 <p class="text-lg font-bold text-emerald-600" :text="profile.stats.activityCount"></p>
96 <p class="text-lg font-bold text-emerald-600">{{ profile.stats.activityCount }}</p>
9797 <p class="metric-label">Activities</p>
9898 </div>
9999 <div class="text-center">
100 <p class="text-lg font-bold text-emerald-600" :text="profile.stats.totalElevation.toLocaleString()"></p>
100 <p class="text-lg font-bold text-emerald-600">{{ profile.stats.totalElevation.toLocaleString() }}</p>
101101 <p class="metric-label">Elev (ft)</p>
102102 </div>
103103 <div class="text-center">
104 <p class="text-lg font-bold text-emerald-600" :text="profile.stats.territoriesOwned"></p>
104 <p class="text-lg font-bold text-emerald-600">{{ profile.stats.territoriesOwned }}</p>
105105 <p class="metric-label">Territories</p>
106106 </div>
107107 <div class="text-center">
108 <p class="text-lg font-bold text-emerald-600" :text="profile.followerCount"></p>
108 <p class="text-lg font-bold text-emerald-600">{{ profile.followerCount }}</p>
109109 <p class="metric-label">Followers</p>
110110 </div>
111111 <div class="text-center">
112 <p class="text-lg font-bold text-emerald-600" :text="profile.followingCount"></p>
112 <p class="text-lg font-bold text-emerald-600">{{ profile.followingCount }}</p>
113113 <p class="metric-label">Following</p>
114114 </div>
115115 </div>
@@ -125,14 +125,14 @@
125125 <StxLink x-to="'/activity/' + a.id" class="flex items-center justify-between py-3 border-b border-gray-100 dark:border-slate-700 last:border-0">
126126 <div>
127127 <p class="font-semibold text-sm dark:text-white">
128 <span :text="a.activityType"></span>
129 <span :if="a.trailName" class="text-gray-500 dark:text-slate-400"> · <span :text="a.trailName"></span></span>
128 <span>{{ a.activityType }}</span>
129 <span :if="a.trailName" class="text-gray-500 dark:text-slate-400"> · <span>{{ a.trailName }}</span></span>
130130 </p>
131 <p class="text-gray-400 text-xs" :text="tb.timeAgo(a.completedAt)"></p>
131 <p class="text-gray-400 text-xs">{{ tb.timeAgo(a.completedAt) }}</p>
132132 </div>
133133 <div class="text-right">
134 <p class="font-bold text-emerald-600 text-sm" :text="a.distance.toFixed(1) + ' mi'"></p>
135 <p class="text-gray-400 text-xs" :text="a.elevationGain.toLocaleString() + ' ft'"></p>
134 <p class="font-bold text-emerald-600 text-sm">{{ a.distance.toFixed(1) + ' mi' }}</p>
135 <p class="text-gray-400 text-xs">{{ a.elevationGain.toLocaleString() + ' ft' }}</p>
136136 </div>
137137 </StxLink>
138138 </template>
resources/views/athletes.stxmodified+8-8
Changes to resources/views/athletes.stx
@@ -93,7 +93,7 @@
9393 <Icon name="alert-triangle" size="28" class="text-red-400" />
9494 </div>
9595 <h3 class="text-base font-semibold text-gray-900 dark:text-white mb-1">Couldn't load athletes</h3>
96 <p class="text-sm text-gray-500 dark:text-slate-400 mb-4" :text="error"></p>
96 <p class="text-sm text-gray-500 dark:text-slate-400 mb-4">{{ error }}</p>
9797 <button @click="retrySearch()" class="px-5 py-2 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition">Try again</button>
9898 </div>
9999
@@ -101,15 +101,15 @@
101101 <template :for="athlete in athletes">
102102 <div class="card flex items-center gap-4">
103103 <StxLink x-to="'/athlete/' + athlete.id" class="flex items-center gap-3 flex-1 min-w-0 group">
104 <div class="w-12 h-12 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 text-lg font-bold shrink-0" :text="athlete.name.charAt(0)"></div>
104 <div class="w-12 h-12 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 text-lg font-bold shrink-0">{{ athlete.name.charAt(0) }}</div>
105105 <div class="min-w-0">
106 <p class="font-bold dark:text-white group-hover:text-emerald-600 transition truncate" :text="athlete.name"></p>
106 <p class="font-bold dark:text-white group-hover:text-emerald-600 transition truncate">{{ athlete.name }}</p>
107107 <p class="text-xs text-gray-500 dark:text-slate-400">
108 <span :text="athlete.activityCount + ' activities'"></span>
108 <span>{{ athlete.activityCount + ' activities' }}</span>
109109 <span class="mx-1">·</span>
110 <span :text="athlete.followerCount + ' followers'"></span>
110 <span>{{ athlete.followerCount + ' followers' }}</span>
111111 <span :if="athlete.territoriesOwned > 0" class="mx-1">·</span>
112 <span :if="athlete.territoriesOwned > 0" :text="athlete.territoriesOwned + ' territories (' + areaKm2(athlete.totalAreaOwned) + ' km²)'"></span>
112 <span :if="athlete.territoriesOwned > 0">{{ athlete.territoriesOwned + ' territories (' + areaKm2(athlete.totalAreaOwned) + ' km²)' }}</span>
113113 </p>
114114 </div>
115115 </StxLink>
@@ -118,7 +118,7 @@
118118 @click="onFollow(athlete.id)"
119119 x-class="tb.isFollowing(athlete.id) ? 'bg-gray-100 text-gray-600 dark:bg-slate-700 dark:text-slate-300' : 'bg-emerald-600 text-white hover:bg-emerald-700'"
120120 class="px-4 py-2 font-semibold text-xs rounded-lg transition shrink-0"
121 :text="tb.isFollowing(athlete.id) ? 'Following' : 'Follow'"></button>
121>{{ tb.isFollowing(athlete.id) ? 'Following' : 'Follow' }}</button>
122122 <span :if="tb && athlete.id === tb.currentUserId()" class="px-3 py-2 text-xs text-gray-400 shrink-0">That's you</span>
123123 </div>
124124 </template>
@@ -128,7 +128,7 @@
128128 <Icon name="search" size="28" class="text-emerald-400" />
129129 </div>
130130 <h3 class="text-base font-semibold text-gray-900 dark:text-white mb-1">No athletes found</h3>
131 <p class="text-sm text-gray-500 dark:text-slate-400" x-text="'Nothing matched “' + query + '” — try another name.'"></p>
131 <p class="text-sm text-gray-500 dark:text-slate-400">{{ 'Nothing matched “' + query + '” — try another name.' }}</p>
132132 </div>
133133 </div>
134134 </div>
resources/views/battles.stxmodified+14-14
Changes to resources/views/battles.stx
@@ -55,9 +55,9 @@
5555 <div class="bg-orange-50/50 dark:bg-orange-900/5 border-2 border-orange-200 dark:border-orange-800/50 card">
5656 <div class="flex gap-4 items-center">
5757 <div class="flex flex-1 gap-2 items-center">
58 <div class="flex items-center justify-center h-10 w-10 font-bold text-orange-600 text-sm bg-orange-100 dark:bg-orange-900/30 rounded-full" :text="battle.attackerName.charAt(0)"></div>
58 <div class="flex items-center justify-center h-10 w-10 font-bold text-orange-600 text-sm bg-orange-100 dark:bg-orange-900/30 rounded-full">{{ battle.attackerName.charAt(0) }}</div>
5959 <div>
60 <p class="font-semibold text-sm dark:text-white" :text="battle.attackerName"></p>
60 <p class="font-semibold text-sm dark:text-white">{{ battle.attackerName }}</p>
6161 <p class="text-orange-600 text-xs">Attacking</p>
6262 </div>
6363 </div>
@@ -66,14 +66,14 @@
6666 </div>
6767 <div class="flex flex-1 gap-2 items-center justify-end text-right">
6868 <div>
69 <p class="font-semibold text-sm dark:text-white" :text="battle.defenderName"></p>
69 <p class="font-semibold text-sm dark:text-white">{{ battle.defenderName }}</p>
7070 <p class="text-blue-600 text-xs">Defending</p>
7171 </div>
72 <div class="flex items-center justify-center h-10 w-10 font-bold text-blue-600 text-sm bg-blue-100 dark:bg-blue-900/30 rounded-full" :text="battle.defenderName.charAt(0)"></div>
72 <div class="flex items-center justify-center h-10 w-10 font-bold text-blue-600 text-sm bg-blue-100 dark:bg-blue-900/30 rounded-full">{{ battle.defenderName.charAt(0) }}</div>
7373 </div>
7474 </div>
7575 <div class="mt-3 text-xs text-center">
76 <StxLink x-to="'/territory/' + battle.territory_id" class="text-emerald-600 hover:underline font-medium" :text="battle.territoryName"></StxLink>
76 <StxLink x-to="'/territory/' + battle.territory_id" class="text-emerald-600 hover:underline font-medium">{{ battle.territoryName }}</StxLink>
7777 </div>
7878 </div>
7979 </template>
@@ -99,18 +99,18 @@
9999 </div>
100100 <div class="flex-1 min-w-0">
101101 <p class="text-sm text-gray-900 dark:text-white">
102 <span class="font-semibold" :text="battle.attackerName"></span>
103 <span :text="battle.status === 'conquered' ? ' conquered ' : ' failed to conquer '"></span>
104 <StxLink x-to="'/territory/' + battle.territory_id" class="font-semibold text-emerald-600 hover:underline" :text="battle.territoryName"></StxLink>
102 <span class="font-semibold">{{ battle.attackerName }}</span>
103 <span>{{ battle.status === 'conquered' ? ' conquered ' : ' failed to conquer ' }}</span>
104 <StxLink x-to="'/territory/' + battle.territory_id" class="font-semibold text-emerald-600 hover:underline">{{ battle.territoryName }}</StxLink>
105105 <span> from </span>
106 <span class="font-semibold" :text="battle.defenderName"></span>
106 <span class="font-semibold">{{ battle.defenderName }}</span>
107107 </p>
108108 <div class="flex gap-3 mt-1 text-xs text-gray-500 dark:text-slate-400">
109 <span :text="tb.timeAgo(battle.created_at)"></span>
110 <span :text="battle.areaCaptured.toLocaleString() + ' m²'"></span>
109 <span>{{ tb.timeAgo(battle.created_at) }}</span>
110 <span>{{ battle.areaCaptured.toLocaleString() + ' m²' }}</span>
111111 </div>
112112 </div>
113 <span class="shrink-0 text-xs px-2 py-1 rounded-full font-medium" x-class="battle.status === 'conquered' ? 'bg-orange-100 text-orange-700' : 'bg-emerald-100 text-emerald-700'" :text="battle.status === 'conquered' ? 'Conquered' : 'Defended'"></span>
113 <span class="shrink-0 text-xs px-2 py-1 rounded-full font-medium" x-class="battle.status === 'conquered' ? 'bg-orange-100 text-orange-700' : 'bg-emerald-100 text-emerald-700'">{{ battle.status === 'conquered' ? 'Conquered' : 'Defended' }}</span>
114114 </div>
115115 </template>
116116
@@ -130,11 +130,11 @@
130130 <h3 class="font-bold text-lg text-gray-900 dark:text-white mb-4">Battle Stats</h3>
131131 <div class="grid grid-cols-2 gap-3">
132132 <div class="text-center p-3 bg-orange-50 dark:bg-orange-900/10 rounded-xl">
133 <p class="text-xl font-bold text-orange-600" :text="conqueredCount"></p>
133 <p class="text-xl font-bold text-orange-600">{{ conqueredCount }}</p>
134134 <p class="text-xs text-gray-500 dark:text-slate-400">Conquests</p>
135135 </div>
136136 <div class="text-center p-3 bg-emerald-50 dark:bg-emerald-900/10 rounded-xl">
137 <p class="text-xl font-bold text-emerald-600" :text="defendedCount"></p>
137 <p class="text-xl font-bold text-emerald-600">{{ defendedCount }}</p>
138138 <p class="text-xs text-gray-500 dark:text-slate-400">Defenses</p>
139139 </div>
140140 </div>
resources/views/challenges.stxmodified+12-12
Changes to resources/views/challenges.stx
@@ -49,34 +49,34 @@
4949 <template :for="challenge in filtered">
5050 <div class="card">
5151 <div class="flex items-center justify-between mb-4">
52 <span class="px-2 py-0.5 font-semibold text-xs rounded-full" x-class="challenge.status === 'active' ? 'bg-orange-100 text-orange-700' : challenge.status === 'pending' ? 'bg-yellow-100 text-yellow-700' : challenge.status === 'declined' ? 'bg-gray-100 text-gray-600 dark:bg-slate-700 dark:text-slate-300' : challenge.winner_id === currentUserId ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'" :text="challenge.status === 'active' ? 'IN PROGRESS' : challenge.status === 'pending' ? 'PENDING' : challenge.status === 'declined' ? 'DECLINED' : challenge.winner_id === currentUserId ? 'WON' : 'LOST'"></span>
53 <span class="text-gray-500 text-xs dark:text-slate-400" :text="tb.timeAgo(challenge.created_at)"></span>
52 <span class="px-2 py-0.5 font-semibold text-xs rounded-full" x-class="challenge.status === 'active' ? 'bg-orange-100 text-orange-700' : challenge.status === 'pending' ? 'bg-yellow-100 text-yellow-700' : challenge.status === 'declined' ? 'bg-gray-100 text-gray-600 dark:bg-slate-700 dark:text-slate-300' : challenge.winner_id === currentUserId ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'">{{ challenge.status === 'active' ? 'IN PROGRESS' : challenge.status === 'pending' ? 'PENDING' : challenge.status === 'declined' ? 'DECLINED' : challenge.winner_id === currentUserId ? 'WON' : 'LOST' }}</span>
53 <span class="text-gray-500 text-xs dark:text-slate-400">{{ tb.timeAgo(challenge.created_at) }}</span>
5454 </div>
5555
5656 <div class="flex items-center justify-between gap-4">
5757 <div class="flex items-center gap-3 flex-1">
58 <div class="flex items-center justify-center h-12 w-12 font-bold text-orange-600 bg-orange-100 dark:bg-orange-900/20 rounded-full" :text="challenge.challengerName.charAt(0)"></div>
58 <div class="flex items-center justify-center h-12 w-12 font-bold text-orange-600 bg-orange-100 dark:bg-orange-900/20 rounded-full">{{ challenge.challengerName.charAt(0) }}</div>
5959 <div>
60 <p class="font-semibold text-sm text-gray-900 dark:text-white" :text="challenge.challenger_id === currentUserId ? 'You' : challenge.challengerName"></p>
60 <p class="font-semibold text-sm text-gray-900 dark:text-white">{{ challenge.challenger_id === currentUserId ? 'You' : challenge.challengerName }}</p>
6161 <p class="text-xs text-gray-500 dark:text-slate-400">Challenger</p>
6262 </div>
6363 </div>
6464 <div class="shrink-0 w-10 h-10 rounded-full bg-gray-100 dark:bg-slate-700 flex items-center justify-center text-xs font-bold text-gray-500 dark:text-slate-400">VS</div>
6565 <div class="flex items-center gap-3 flex-1 justify-end text-right">
6666 <div>
67 <p class="font-semibold text-sm text-gray-900 dark:text-white" :text="challenge.challenged_id === currentUserId ? 'You' : challenge.challengedName"></p>
67 <p class="font-semibold text-sm text-gray-900 dark:text-white">{{ challenge.challenged_id === currentUserId ? 'You' : challenge.challengedName }}</p>
6868 <p class="text-xs text-gray-500 dark:text-slate-400">Defender</p>
6969 </div>
70 <div class="flex items-center justify-center h-12 w-12 font-bold text-blue-600 bg-blue-100 dark:bg-blue-900/20 rounded-full" :text="challenge.challengedName.charAt(0)"></div>
70 <div class="flex items-center justify-center h-12 w-12 font-bold text-blue-600 bg-blue-100 dark:bg-blue-900/20 rounded-full">{{ challenge.challengedName.charAt(0) }}</div>
7171 </div>
7272 </div>
7373
7474 <div class="mt-4 p-3 bg-gray-50 dark:bg-slate-700/50 rounded-xl flex items-center justify-between">
7575 <div class="flex items-center gap-2 text-sm text-gray-600 dark:text-slate-300">
7676 Territory:
77 <StxLink x-to="'/territory/' + challenge.territory_id" class="font-medium text-emerald-600 hover:underline" :text="challenge.territoryName"></StxLink>
77 <StxLink x-to="'/territory/' + challenge.territory_id" class="font-medium text-emerald-600 hover:underline">{{ challenge.territoryName }}</StxLink>
7878 </div>
79 <span class="text-xs text-gray-500 dark:text-slate-400" :text="(challenge.areaAtStake / 1000).toFixed(1) + ' km²'"></span>
79 <span class="text-xs text-gray-500 dark:text-slate-400">{{ (challenge.areaAtStake / 1000).toFixed(1) + ' km²' }}</span>
8080 </div>
8181
8282 <!-- Accept / decline (received + pending only) (#965) -->
@@ -84,7 +84,7 @@
8484 <button @click="respond(challenge, 'accept')" x-disabled="busyId === challenge.id" class="flex-1 px-4 py-2 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition disabled:opacity-60">Accept</button>
8585 <button @click="respond(challenge, 'decline')" x-disabled="busyId === challenge.id" class="flex-1 px-4 py-2 border border-gray-200 dark:border-slate-600 text-gray-600 dark:text-slate-300 text-sm font-semibold rounded-lg hover:border-red-400 hover:text-red-500 transition disabled:opacity-60">Decline</button>
8686 </div>
87 <p :if="challenge.challenger_id === currentUserId && challenge.status === 'pending'" class="mt-3 text-xs text-gray-400 text-center">Waiting for <span :text="challenge.challengedName"></span> to respond…</p>
87 <p :if="challenge.challenger_id === currentUserId && challenge.status === 'pending'" class="mt-3 text-xs text-gray-400 text-center">Waiting for <span>{{ challenge.challengedName }}</span> to respond…</p>
8888 </div>
8989 </template>
9090
@@ -112,14 +112,14 @@
112112 <label class="block text-xs font-medium text-gray-500 dark:text-slate-400 mb-1">Territory to challenge for</label>
113113 <select @change="targetTerritoryId.set($event.target.value)" class="w-full px-3 py-2 border border-gray-200 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-sm">
114114 <template :for="t in rivalTerritories()">
115 <option x-value="t.id" x-selected="String(t.id) === targetTerritoryId" x-text="t.name + ' (' + (t.areaSize / 1000).toFixed(1) + ' km²)'"></option>
115 <option x-value="t.id" x-selected="String(t.id) === targetTerritoryId" :text="t.name + ' (' + (t.areaSize / 1000).toFixed(1) + ' km²)'"></option>
116116 </template>
117117 </select>
118118 </div>
119119 <p class="text-xs text-gray-400">You'll challenge whoever currently holds it. The territory's area is what's at stake.</p>
120 <p :if="createError" class="text-sm text-red-500" :text="createError"></p>
120 <p :if="createError" class="text-sm text-red-500">{{ createError }}</p>
121121 <div class="flex gap-2 pt-1">
122 <button @click="submitCreate()" class="flex-1 px-4 py-2.5 bg-orange-600 text-white text-sm font-semibold rounded-lg hover:bg-orange-700 transition" :text="submitting ? 'Sending…' : 'Throw down'"></button>
122 <button @click="submitCreate()" class="flex-1 px-4 py-2.5 bg-orange-600 text-white text-sm font-semibold rounded-lg hover:bg-orange-700 transition">{{ submitting ? 'Sending…' : 'Throw down' }}</button>
123123 <button @click="closeCreate()" class="px-4 py-2.5 text-sm font-medium text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 transition">Cancel</button>
124124 </div>
125125 </div>
resources/views/club/[id].stxmodified+20-20
Changes to resources/views/club/[id].stx
@@ -71,27 +71,27 @@
7171 <!-- Header -->
7272 <div class="card mb-6">
7373 <div class="flex flex-col sm:flex-row gap-6">
74 <div class="w-16 h-16 rounded-2xl flex items-center justify-center text-2xl font-bold shrink-0 bg-emerald-100 dark:bg-emerald-900/20 text-emerald-600" :text="club.name.charAt(0)"></div>
74 <div class="w-16 h-16 rounded-2xl flex items-center justify-center text-2xl font-bold shrink-0 bg-emerald-100 dark:bg-emerald-900/20 text-emerald-600">{{ club.name.charAt(0) }}</div>
7575 <div class="flex-1 min-w-0">
7676 <div class="flex items-start justify-between gap-4">
7777 <div>
78 <h1 class="text-2xl font-bold dark:text-white" :text="club.name"></h1>
78 <h1 class="text-2xl font-bold dark:text-white">{{ club.name }}</h1>
7979 <div class="flex flex-wrap items-center gap-3 text-xs text-gray-500 dark:text-slate-400 mt-1">
80 <span :text="club.location || 'Worldwide'"></span>
81 <span :text="club.memberCount + ' members'"></span>
82 <span class="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-slate-700 font-medium" :text="club.type"></span>
80 <span>{{ club.location || 'Worldwide' }}</span>
81 <span>{{ club.memberCount + ' members' }}</span>
82 <span class="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-slate-700 font-medium">{{ club.type }}</span>
8383 <span :if="club.isPrivate" class="px-1.5 py-0.5 rounded bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 font-medium">Private</span>
8484 </div>
8585 </div>
8686 <div class="flex items-center gap-2 shrink-0">
87 <button :if="!isOwner" @click="onToggle()" x-disabled="busy" class="px-4 py-2 text-sm font-semibold rounded-lg transition disabled:opacity-60" x-class="club.isMember ? 'bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-slate-300' : 'bg-emerald-600 text-white hover:bg-emerald-700'" :text="club.isMember ? 'Joined' : 'Join'"></button>
87 <button :if="!isOwner" @click="onToggle()" x-disabled="busy" class="px-4 py-2 text-sm font-semibold rounded-lg transition disabled:opacity-60" x-class="club.isMember ? 'bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-slate-300' : 'bg-emerald-600 text-white hover:bg-emerald-700'">{{ club.isMember ? 'Joined' : 'Join' }}</button>
8888 <button :if="isOwner" @click="onDelete()" x-disabled="busy" class="px-4 py-2 text-sm font-semibold rounded-lg border border-gray-200 dark:border-slate-600 text-red-500 hover:border-red-400 transition disabled:opacity-60">Delete club</button>
8989 </div>
9090 </div>
91 <p class="text-sm text-gray-600 dark:text-slate-400 mt-3" :text="club.description"></p>
91 <p class="text-sm text-gray-600 dark:text-slate-400 mt-3">{{ club.description }}</p>
9292 <div class="flex items-center gap-6 pt-3 mt-3 border-t border-gray-100 dark:border-slate-700 text-sm">
93 <div><span class="font-semibold dark:text-white" :text="club.weeklyDistance + ' mi'"></span> <span class="text-gray-500 dark:text-slate-400">this week</span></div>
94 <div><span class="font-semibold dark:text-white" :text="club.activitiesThisWeek"></span> <span class="text-gray-500 dark:text-slate-400">activities</span></div>
93 <div><span class="font-semibold dark:text-white">{{ club.weeklyDistance + ' mi' }}</span> <span class="text-gray-500 dark:text-slate-400">this week</span></div>
94 <div><span class="font-semibold dark:text-white">{{ club.activitiesThisWeek }}</span> <span class="text-gray-500 dark:text-slate-400">activities</span></div>
9595 </div>
9696 </div>
9797 </div>
@@ -103,14 +103,14 @@
103103 <h2 class="font-bold text-lg dark:text-white">Recent activity</h2>
104104 <template :for="a in club.recentFeed">
105105 <StxLink x-to="'/activity/' + a.id" class="card flex items-center gap-4 hover:shadow-md transition block">
106 <div class="w-10 h-10 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 font-bold shrink-0" :text="a.userName.charAt(0)"></div>
106 <div class="w-10 h-10 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 font-bold shrink-0">{{ a.userName.charAt(0) }}</div>
107107 <div class="flex-1 min-w-0">
108 <p class="font-semibold text-sm dark:text-white truncate" :text="a.userName + ' · ' + a.activityType"></p>
109 <p class="text-xs text-gray-500 dark:text-slate-400" :text="tb.timeAgo(a.completedAt)"></p>
108 <p class="font-semibold text-sm dark:text-white truncate">{{ a.userName + ' · ' + a.activityType }}</p>
109 <p class="text-xs text-gray-500 dark:text-slate-400">{{ tb.timeAgo(a.completedAt) }}</p>
110110 </div>
111111 <div class="text-right shrink-0">
112 <p class="font-bold text-sm dark:text-white" :text="(a.distance || 0).toFixed(1) + ' mi'"></p>
113 <p class="metric-label" :text="a.duration"></p>
112 <p class="font-bold text-sm dark:text-white">{{ (a.distance || 0).toFixed(1) + ' mi' }}</p>
113 <p class="metric-label">{{ a.duration }}</p>
114114 </div>
115115 </StxLink>
116116 </template>
@@ -129,9 +129,9 @@
129129 <div class="space-y-2">
130130 <template :for="row in club.leaderboard">
131131 <div class="flex items-center gap-3 text-sm">
132 <span class="w-5 text-center font-bold" x-class="row.rank <= 3 ? 'text-yellow-500' : 'text-gray-400'" :text="row.rank"></span>
133 <StxLink x-to="'/athlete/' + row.userId" class="flex-1 truncate dark:text-white hover:text-emerald-600 transition" :text="row.name"></StxLink>
134 <span class="text-gray-500 dark:text-slate-400" :text="row.weeklyDistance + ' mi'"></span>
132 <span class="w-5 text-center font-bold" x-class="row.rank <= 3 ? 'text-yellow-500' : 'text-gray-400'">{{ row.rank }}</span>
133 <StxLink x-to="'/athlete/' + row.userId" class="flex-1 truncate dark:text-white hover:text-emerald-600 transition">{{ row.name }}</StxLink>
134 <span class="text-gray-500 dark:text-slate-400">{{ row.weeklyDistance + ' mi' }}</span>
135135 </div>
136136 </template>
137137 <div :if="club.leaderboard.length === 0" class="text-center py-4 text-sm text-gray-500 dark:text-slate-400">No members yet.</div>
@@ -139,12 +139,12 @@
139139 </div>
140140
141141 <div class="card">
142 <h3 class="font-bold dark:text-white mb-3" :text="'Members (' + club.memberCount + ')'"></h3>
142 <h3 class="font-bold dark:text-white mb-3">{{ 'Members (' + club.memberCount + ')' }}</h3>
143143 <div class="space-y-2">
144144 <template :for="m in club.members">
145145 <div class="flex items-center gap-3 text-sm">
146 <StxLink x-to="'/athlete/' + m.userId" class="flex-1 truncate dark:text-white hover:text-emerald-600 transition" :text="m.name"></StxLink>
147 <span :if="m.role !== 'member'" class="px-1.5 py-0.5 rounded bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400 text-xs font-medium capitalize" :text="m.role"></span>
146 <StxLink x-to="'/athlete/' + m.userId" class="flex-1 truncate dark:text-white hover:text-emerald-600 transition">{{ m.name }}</StxLink>
147 <span :if="m.role !== 'member'" class="px-1.5 py-0.5 rounded bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400 text-xs font-medium capitalize">{{ m.role }}</span>
148148 </div>
149149 </template>
150150 </div>
resources/views/clubs.stxmodified+11-11
Changes to resources/views/clubs.stx
@@ -47,28 +47,28 @@
4747 <template :for="club in filtered">
4848 <div class="card">
4949 <div class="flex flex-col sm:flex-row gap-6">
50 <div class="w-16 h-16 rounded-2xl flex items-center justify-center text-2xl font-bold shrink-0" x-class="club.type === 'Running' ? 'bg-emerald-100 dark:bg-emerald-900/20 text-emerald-600' : club.type === 'Hiking' ? 'bg-blue-100 dark:bg-blue-900/20 text-blue-600' : club.type === 'Territory Game' ? 'bg-orange-100 dark:bg-orange-900/20 text-orange-600' : 'bg-purple-100 dark:bg-purple-900/20 text-purple-600'" :text="club.name.charAt(0)"></div>
50 <div class="w-16 h-16 rounded-2xl flex items-center justify-center text-2xl font-bold shrink-0" x-class="club.type === 'Running' ? 'bg-emerald-100 dark:bg-emerald-900/20 text-emerald-600' : club.type === 'Hiking' ? 'bg-blue-100 dark:bg-blue-900/20 text-blue-600' : club.type === 'Territory Game' ? 'bg-orange-100 dark:bg-orange-900/20 text-orange-600' : 'bg-purple-100 dark:bg-purple-900/20 text-purple-600'">{{ club.name.charAt(0) }}</div>
5151 <div class="flex-1 min-w-0">
5252 <div class="flex items-start justify-between gap-4 mb-2">
5353 <div>
54 <StxLink x-to="'/club/' + club.id" class="font-bold text-lg text-gray-900 dark:text-white hover:text-emerald-600 transition" :text="club.name"></StxLink>
54 <StxLink x-to="'/club/' + club.id" class="font-bold text-lg text-gray-900 dark:text-white hover:text-emerald-600 transition">{{ club.name }}</StxLink>
5555 <div class="flex items-center gap-3 text-xs text-gray-500 dark:text-slate-400 mt-1">
56 <span :text="club.location"></span>
57 <span :text="club.memberCount + ' members'"></span>
58 <span class="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-slate-700 font-medium" :text="club.type"></span>
56 <span>{{ club.location }}</span>
57 <span>{{ club.memberCount + ' members' }}</span>
58 <span class="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-slate-700 font-medium">{{ club.type }}</span>
5959 <span :if="club.isPrivate" class="px-1.5 py-0.5 rounded bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 font-medium">Private</span>
6060 </div>
6161 </div>
62 <button @click="onToggleMembership(club)" class="shrink-0 px-4 py-2 text-sm font-semibold rounded-lg transition" x-class="isMember(club) ? 'bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-slate-300' : 'bg-emerald-600 text-white hover:bg-emerald-700'" :text="isMember(club) ? 'Joined' : 'Join'"></button>
62 <button @click="onToggleMembership(club)" class="shrink-0 px-4 py-2 text-sm font-semibold rounded-lg transition" x-class="isMember(club) ? 'bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-slate-300' : 'bg-emerald-600 text-white hover:bg-emerald-700'">{{ isMember(club) ? 'Joined' : 'Join' }}</button>
6363 </div>
64 <p class="text-sm text-gray-600 dark:text-slate-400 mb-4" :text="club.description"></p>
64 <p class="text-sm text-gray-600 dark:text-slate-400 mb-4">{{ club.description }}</p>
6565 <div class="flex items-center gap-6 pt-3 border-t border-gray-100 dark:border-slate-700 text-sm">
6666 <div class="flex items-center gap-1.5">
67 <span class="font-semibold dark:text-white" :text="club.weeklyDistance + ' mi'"></span>
67 <span class="font-semibold dark:text-white">{{ club.weeklyDistance + ' mi' }}</span>
6868 <span class="text-gray-500 dark:text-slate-400">this week</span>
6969 </div>
7070 <div class="flex items-center gap-1.5">
71 <span class="font-semibold dark:text-white" :text="club.activitiesThisWeek"></span>
71 <span class="font-semibold dark:text-white">{{ club.activitiesThisWeek }}</span>
7272 <span class="text-gray-500 dark:text-slate-400">activities</span>
7373 </div>
7474 </div>
@@ -124,9 +124,9 @@
124124 <input type="checkbox" x-checked="fPrivate" @change="fPrivate.set($event.target.checked)" class="h-4 w-4 rounded border-gray-300 text-emerald-600 focus:ring-emerald-500" />
125125 Private — only members can see it
126126 </label>
127 <p :if="createError" class="text-sm text-red-500" :text="createError"></p>
127 <p :if="createError" class="text-sm text-red-500">{{ createError }}</p>
128128 <div class="flex gap-2 pt-1">
129 <button @click="submitCreate()" class="flex-1 px-4 py-2.5 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition" :text="submitting ? 'Creating…' : 'Create club'"></button>
129 <button @click="submitCreate()" class="flex-1 px-4 py-2.5 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition">{{ submitting ? 'Creating…' : 'Create club' }}</button>
130130 <button @click="closeCreate()" class="px-4 py-2.5 text-sm font-medium text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 transition">Cancel</button>
131131 </div>
132132 </div>
resources/views/conquests.stxmodified+15-15
Changes to resources/views/conquests.stx
@@ -45,19 +45,19 @@
4545 <!-- Stats -->
4646 <div :if="conquestStats" class="grid gap-4 grid-cols-2 sm:grid-cols-4 mb-8">
4747 <div class="text-center card">
48 <p class="font-bold text-2xl text-orange-600" :text="conquestStats.totalConquests"></p>
48 <p class="font-bold text-2xl text-orange-600">{{ conquestStats.totalConquests }}</p>
4949 <p class="metric-label">Conquered</p>
5050 </div>
5151 <div class="text-center card">
52 <p class="font-bold text-2xl text-red-600" :text="conquestStats.territoriesLost"></p>
52 <p class="font-bold text-2xl text-red-600">{{ conquestStats.territoriesLost }}</p>
5353 <p class="metric-label">Lost</p>
5454 </div>
5555 <div class="text-center card">
56 <p class="font-bold text-2xl text-emerald-600" :text="conquestStats.successfulDefenses"></p>
56 <p class="font-bold text-2xl text-emerald-600">{{ conquestStats.successfulDefenses }}</p>
5757 <p class="metric-label">Defended</p>
5858 </div>
5959 <div class="text-center card">
60 <p class="font-bold text-2xl text-blue-600" :text="conquestStats.winRate + '%'"></p>
60 <p class="font-bold text-2xl text-blue-600">{{ conquestStats.winRate + '%' }}</p>
6161 <p class="metric-label">Win Rate</p>
6262 </div>
6363 </div>
@@ -81,14 +81,14 @@
8181 </div>
8282 <div class="flex-1 min-w-0">
8383 <div class="flex gap-2 items-center mb-1">
84 <span class="px-2 py-0.5 font-semibold text-xs rounded-full" x-class="conquest.attacker_id === currentUserId ? 'bg-orange-100 text-orange-700' : conquest.status === 'defended' ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'" :text="conquest.attacker_id === currentUserId ? 'ATTACK' : conquest.status === 'defended' ? 'DEFENDED' : 'CONQUERED'"></span>
85 <span class="text-xs text-gray-500 dark:text-slate-400" :text="tb.timeAgo(conquest.created_at)"></span>
84 <span class="px-2 py-0.5 font-semibold text-xs rounded-full" x-class="conquest.attacker_id === currentUserId ? 'bg-orange-100 text-orange-700' : conquest.status === 'defended' ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'">{{ conquest.attacker_id === currentUserId ? 'ATTACK' : conquest.status === 'defended' ? 'DEFENDED' : 'CONQUERED' }}</span>
85 <span class="text-xs text-gray-500 dark:text-slate-400">{{ tb.timeAgo(conquest.created_at) }}</span>
8686 </div>
8787 <p class="font-semibold text-gray-900 dark:text-white">
88 <span :text="conquest.attacker_id === currentUserId ? 'You conquered' : conquest.attackerName + ' attacked'"></span>
89 <StxLink x-to="'/territory/' + conquest.territory_id" class="text-emerald-600 hover:underline" :text="' ' + conquest.territoryName"></StxLink>
88 <span>{{ conquest.attacker_id === currentUserId ? 'You conquered' : conquest.attackerName + ' attacked' }}</span>
89 <StxLink x-to="'/territory/' + conquest.territory_id" class="text-emerald-600 hover:underline">{{ ' ' + conquest.territoryName }}</StxLink>
9090 </p>
91 <p class="text-sm text-gray-500 dark:text-slate-400 mt-1" :text="conquest.description"></p>
91 <p class="text-sm text-gray-500 dark:text-slate-400 mt-1">{{ conquest.description }}</p>
9292 </div>
9393 </div>
9494 </div>
@@ -112,15 +112,15 @@
112112 <div :if="conquestStats" class="space-y-3">
113113 <div class="flex items-center justify-between p-3 bg-orange-50 dark:bg-orange-900/10 rounded-xl">
114114 <span class="text-sm text-gray-600 dark:text-slate-400">Power Rating</span>
115 <span class="font-bold text-orange-600" :text="conquestStats.powerRating"></span>
115 <span class="font-bold text-orange-600">{{ conquestStats.powerRating }}</span>
116116 </div>
117117 <div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-slate-700/50 rounded-xl">
118118 <span class="text-sm text-gray-600 dark:text-slate-400">Longest Streak</span>
119 <span class="font-bold dark:text-white" :text="conquestStats.longestConquestStreak + ' wins'"></span>
119 <span class="font-bold dark:text-white">{{ conquestStats.longestConquestStreak + ' wins' }}</span>
120120 </div>
121121 <div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-slate-700/50 rounded-xl">
122122 <span class="text-sm text-gray-600 dark:text-slate-400">Rivals Beaten</span>
123 <span class="font-bold dark:text-white" :text="conquestStats.uniqueRivalsBeaten"></span>
123 <span class="font-bold dark:text-white">{{ conquestStats.uniqueRivalsBeaten }}</span>
124124 </div>
125125 </div>
126126 </div>
@@ -133,10 +133,10 @@
133133 <div class="space-y-3">
134134 <template :for="conquest in underAttack">
135135 <div class="flex items-center gap-3 p-3 bg-red-50 dark:bg-red-900/10 rounded-xl">
136 <div class="w-8 h-8 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center text-red-600 text-xs font-bold" :text="conquest.attackerName.charAt(0)"></div>
136 <div class="w-8 h-8 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center text-red-600 text-xs font-bold">{{ conquest.attackerName.charAt(0) }}</div>
137137 <div class="flex-1 min-w-0">
138 <p class="text-sm font-semibold text-gray-900 dark:text-white truncate" :text="conquest.attackerName"></p>
139 <p class="text-xs text-red-600" :text="'Targeting ' + conquest.territoryName"></p>
138 <p class="text-sm font-semibold text-gray-900 dark:text-white truncate">{{ conquest.attackerName }}</p>
139 <p class="text-xs text-red-600">{{ 'Targeting ' + conquest.territoryName }}</p>
140140 </div>
141141 <StxLink x-to="'/territory/' + conquest.territory_id" class="text-xs px-2 py-1 bg-red-600 text-white rounded-lg hover:bg-red-700 transition font-medium">Defend</StxLink>
142142 </div>
resources/views/feed.stxmodified+14-14
Changes to resources/views/feed.stx
@@ -108,13 +108,13 @@
108108 <div class="card">
109109 <div class="flex items-center justify-between mb-4">
110110 <div class="flex gap-3 items-center">
111 <div class="flex items-center justify-center h-10 w-10 font-bold text-emerald-600 text-sm bg-emerald-100 dark:bg-emerald-900/30 rounded-full" :text="activity.userName.charAt(0)"></div>
111 <div class="flex items-center justify-center h-10 w-10 font-bold text-emerald-600 text-sm bg-emerald-100 dark:bg-emerald-900/30 rounded-full">{{ activity.userName.charAt(0) }}</div>
112112 <div>
113 <StxLink x-to="'/athlete/' + activity.user_id" class="font-semibold text-sm dark:text-white hover:text-emerald-600 transition" :text="activity.userName"></StxLink>
113 <StxLink x-to="'/athlete/' + activity.user_id" class="font-semibold text-sm dark:text-white hover:text-emerald-600 transition">{{ activity.userName }}</StxLink>
114114 <p class="text-gray-500 text-xs dark:text-slate-400">
115 <span :text="activity.activityType"></span>
115 <span>{{ activity.activityType }}</span>
116116 <span class="mx-1">·</span>
117 <span :text="tb.timeAgo(activity.created_at)"></span>
117 <span>{{ tb.timeAgo(activity.created_at) }}</span>
118118 </p>
119119 </div>
120120 </div>
@@ -123,10 +123,10 @@
123123 @click="onFollow(activity.user_id)"
124124 x-class="tb.isFollowing(activity.user_id) ? 'bg-gray-100 text-gray-600 dark:bg-slate-700 dark:text-slate-300' : 'bg-emerald-600 text-white hover:bg-emerald-700'"
125125 class="px-3 py-1.5 font-semibold text-xs rounded-lg transition"
126 :text="tb.isFollowing(activity.user_id) ? 'Following' : 'Follow'"></button>
126>{{ tb.isFollowing(activity.user_id) ? 'Following' : 'Follow' }}</button>
127127 </div>
128128
129 <StxLink x-to="'/activity/' + activity.id" class="font-bold text-gray-900 text-lg dark:text-white hover:text-emerald-600 transition block" :text="activity.title"></StxLink>
129 <StxLink x-to="'/activity/' + activity.id" class="font-bold text-gray-900 text-lg dark:text-white hover:text-emerald-600 transition block">{{ activity.title }}</StxLink>
130130 <p :if="activity.title.includes('Capture')" class="mt-1 text-xs text-purple-600 font-medium">Territory capture run</p>
131131
132132 <div :if="activityRoutePreview(activity)" class="route-preview mt-3" x-style="'background-image:url(' + activityRoutePreview(activity) + ')'"></div>
@@ -139,19 +139,19 @@
139139
140140 <div class="grid gap-3 grid-cols-2 sm:grid-cols-4 mt-4">
141141 <div class="text-center">
142 <p class="font-bold text-lg dark:text-white" :text="activity.distance.toFixed(1)"></p>
142 <p class="font-bold text-lg dark:text-white">{{ activity.distance.toFixed(1) }}</p>
143143 <p class="metric-label">Miles</p>
144144 </div>
145145 <div class="text-center">
146 <p class="font-bold text-lg dark:text-white" :text="activity.duration"></p>
146 <p class="font-bold text-lg dark:text-white">{{ activity.duration }}</p>
147147 <p class="metric-label">Time</p>
148148 </div>
149149 <div class="text-center">
150 <p class="font-bold text-lg dark:text-white" :text="activity.pace"></p>
150 <p class="font-bold text-lg dark:text-white">{{ activity.pace }}</p>
151151 <p class="metric-label">Pace</p>
152152 </div>
153153 <div class="text-center">
154 <p class="font-bold text-lg dark:text-white" :text="activity.elevation_gain.toLocaleString()"></p>
154 <p class="font-bold text-lg dark:text-white">{{ activity.elevation_gain.toLocaleString() }}</p>
155155 <p class="metric-label">Elev (ft)</p>
156156 </div>
157157 </div>
@@ -159,7 +159,7 @@
159159 <div class="flex gap-6 items-center mt-4 pt-4 border-gray-100 border-t dark:border-slate-700">
160160 <button @click="onKudos(activity)" x-class="kudosed[activity.id] ? 'text-red-500' : 'text-gray-500 dark:text-slate-400 hover:text-red-500'" class="flex items-center gap-1 transition text-sm">
161161 <Icon name="heart" size="20" />
162 <span :text="activity.kudos_count + ' Kudos'"></span>
162 <span>{{ activity.kudos_count + ' Kudos' }}</span>
163163 </button>
164164 <StxLink x-to="'/activity/' + activity.id" class="flex items-center gap-1 text-gray-500 dark:text-slate-400 hover:text-emerald-500 transition text-sm">
165165 <Icon name="message-circle" size="20" />
@@ -224,7 +224,7 @@
224224 <select @change="mTrailId.set($event.target.value)" class="w-full px-3 py-2 border border-gray-200 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-sm">
225225 <option value="">No trail</option>
226226 <template :for="trail in tb.trails()">
227 <option x-value="trail.id" x-selected="String(trail.id) === mTrailId" x-text="trail.name"></option>
227 <option x-value="trail.id" x-selected="String(trail.id) === mTrailId" :text="trail.name"></option>
228228 </template>
229229 </select>
230230 </div>
@@ -232,9 +232,9 @@
232232 <label class="block text-xs font-medium text-gray-500 dark:text-slate-400 mb-1">Notes</label>
233233 <textarea rows="2" placeholder="How did it go?" x-value="mNotes" @input="mNotes.set($event.target.value)" class="w-full px-3 py-2 border border-gray-200 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-sm"></textarea>
234234 </div>
235 <p :if="manualError" class="text-sm text-red-500" :text="manualError"></p>
235 <p :if="manualError" class="text-sm text-red-500">{{ manualError }}</p>
236236 <div class="flex gap-2 pt-1">
237 <button @click="submitManualEntry()" class="flex-1 px-4 py-2.5 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition" :text="submitting ? 'Saving…' : 'Log activity'"></button>
237 <button @click="submitManualEntry()" class="flex-1 px-4 py-2.5 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition">{{ submitting ? 'Saving…' : 'Log activity' }}</button>
238238 <button @click="closeManualEntry()" class="px-4 py-2.5 text-sm font-medium text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 transition">Cancel</button>
239239 </div>
240240 <p class="text-xs text-gray-400">Manual entries don't capture territory — record with GPS to claim ground.</p>
resources/views/forgot-password.stxmodified+2-2
Changes to resources/views/forgot-password.stx
@@ -58,13 +58,13 @@
5858 <Icon name="mail" size="32" class="text-emerald-500" />
5959 </div>
6060 <h2 class="font-semibold text-lg text-gray-900 dark:text-white mb-1">Check your inbox</h2>
61 <p class="text-sm text-gray-500 dark:text-slate-400 mb-6">If an account exists for <span class="font-medium text-gray-700 dark:text-slate-300" :text="email"></span>, a password reset link is on its way.</p>
61 <p class="text-sm text-gray-500 dark:text-slate-400 mb-6">If an account exists for <span class="font-medium text-gray-700 dark:text-slate-300">{{ email }}</span>, a password reset link is on its way.</p>
6262 <StxLink to="/login" class="inline-block px-5 py-2.5 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition">Back to sign in</StxLink>
6363 </div>
6464
6565 <!-- Request form -->
6666 <div :if="!sent">
67 <div :if="error" class="mb-6 p-4 text-red-700 text-sm dark:text-red-400 bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-xl dark:border-red-800" :text="error"></div>
67 <div :if="error" class="mb-6 p-4 text-red-700 text-sm dark:text-red-400 bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-xl dark:border-red-800">{{ error }}</div>
6868
6969 <form @submit="handleSubmit" class="space-y-5">
7070 <div>
resources/views/index.stxmodified+19-19
Changes to resources/views/index.stx
@@ -41,26 +41,26 @@
4141 <p class="mb-4 text-xs font-bold text-emerald-400 uppercase tracking-wider">Your empire</p>
4242 <div class="grid grid-cols-2 gap-4 mb-4">
4343 <div>
44 <p class="text-3xl font-bold text-white" :text="myTerritories().length"></p>
44 <p class="text-3xl font-bold text-white">{{ myTerritories().length }}</p>
4545 <p class="metric-label text-slate-400">Zones owned</p>
4646 </div>
4747 <div>
48 <p class="text-3xl font-bold text-white" :text="(totalArea() / 1000).toFixed(1) + ' km²'"></p>
48 <p class="text-3xl font-bold text-white">{{ (totalArea() / 1000).toFixed(1) + ' km²' }}</p>
4949 <p class="metric-label text-slate-400">Turf controlled</p>
5050 </div>
5151 <div>
52 <p class="text-3xl font-bold text-emerald-400" :text="gameStats?.powerRating ?? 0"></p>
52 <p class="text-3xl font-bold text-emerald-400">{{ gameStats?.powerRating ?? 0 }}</p>
5353 <p class="metric-label text-slate-400">Power rating</p>
5454 </div>
5555 <div>
56 <p class="text-3xl font-bold text-white" :text="stats?.weeklyDistance?.toFixed(1) ?? '0'"></p>
56 <p class="text-3xl font-bold text-white">{{ stats?.weeklyDistance?.toFixed(1) ?? '0' }}</p>
5757 <p class="metric-label text-slate-400">Mi this week</p>
5858 </div>
5959 </div>
6060 <div :if="liveBattles.length > 0" class="pt-4 border-t border-white/10">
6161 <p class="flex gap-2 items-center mb-2 text-xs font-semibold text-orange-400">
6262 <span class="h-2 w-2 bg-red-500 rounded-full animate-pulse"></span>
63 <span :text="liveBattles.length + ' live battle' + (liveBattles.length > 1 ? 's' : '')"></span>
63 <span>{{ liveBattles.length + ' live battle' + (liveBattles.length > 1 ? 's' : '') }}</span>
6464 </p>
6565 <StxLink to="/battles" class="text-sm text-white/80 hover:text-white underline">Watch the map →</StxLink>
6666 </div>
@@ -75,19 +75,19 @@
7575 <div class="flex flex-col gap-6 sm:flex-row sm:items-center sm:justify-between">
7676 <div>
7777 <h2 class="font-bold text-lg dark:text-white">This week</h2>
78 <p class="text-sm text-gray-500 dark:text-slate-400" :text="(stats?.weeklyActivities ?? 0) + ' activities · ' + (stats?.currentStreak ?? 0) + ' day streak'"></p>
78 <p class="text-sm text-gray-500 dark:text-slate-400">{{ (stats?.weeklyActivities ?? 0) + ' activities · ' + (stats?.currentStreak ?? 0) + ' day streak' }}</p>
7979 </div>
8080 <div class="grid grid-cols-3 gap-6 sm:gap-10 text-center">
8181 <div>
82 <p class="text-2xl font-bold text-emerald-600" :text="stats?.weeklyDistance?.toFixed(1) ?? '0'"></p>
82 <p class="text-2xl font-bold text-emerald-600">{{ stats?.weeklyDistance?.toFixed(1) ?? '0' }}</p>
8383 <p class="metric-label">Miles</p>
8484 </div>
8585 <div>
86 <p class="text-2xl font-bold text-emerald-600" :text="(stats?.weeklyElevation ?? 0).toLocaleString()"></p>
86 <p class="text-2xl font-bold text-emerald-600">{{ (stats?.weeklyElevation ?? 0).toLocaleString() }}</p>
8787 <p class="metric-label">Elevation</p>
8888 </div>
8989 <div>
90 <p class="text-2xl font-bold text-emerald-600" :text="gameStats?.totalConquests ?? 0"></p>
90 <p class="text-2xl font-bold text-emerald-600">{{ gameStats?.totalConquests ?? 0 }}</p>
9191 <p class="metric-label">Conquests</p>
9292 </div>
9393 </div>
@@ -134,17 +134,17 @@
134134 <template :for="activity in recent">
135135 <StxLink x-to="'/activity/' + activity.id" class="card flex flex-col gap-4 sm:flex-row sm:items-center hover:shadow-md transition block">
136136 <div class="flex gap-3 items-center flex-1 min-w-0">
137 <div class="shrink-0 flex items-center justify-center h-12 w-12 font-bold text-emerald-600 bg-emerald-100 dark:bg-emerald-900/30 rounded-full" :text="activity.userName.charAt(0)"></div>
137 <div class="shrink-0 flex items-center justify-center h-12 w-12 font-bold text-emerald-600 bg-emerald-100 dark:bg-emerald-900/30 rounded-full">{{ activity.userName.charAt(0) }}</div>
138138 <div class="min-w-0">
139 <p class="font-bold dark:text-white truncate" :text="activity.title"></p>
140 <p class="text-sm text-gray-500 dark:text-slate-400" :text="activity.activityType + ' · ' + tb.timeAgo(activity.created_at)"></p>
139 <p class="font-bold dark:text-white truncate">{{ activity.title }}</p>
140 <p class="text-sm text-gray-500 dark:text-slate-400">{{ activity.activityType + ' · ' + tb.timeAgo(activity.created_at) }}</p>
141141 </div>
142142 </div>
143143 <div class="grid grid-cols-4 gap-4 text-center shrink-0">
144 <div><p class="font-bold text-emerald-600" :text="activity.distance.toFixed(1)"></p><p class="metric-label">mi</p></div>
145 <div><p class="font-bold dark:text-white text-sm" :text="activity.duration"></p><p class="metric-label">time</p></div>
146 <div><p class="font-bold dark:text-white text-sm" :text="activity.pace"></p><p class="metric-label">pace</p></div>
147 <div><p class="font-bold dark:text-white text-sm" :text="activity.kudos_count"></p><p class="metric-label">kudos</p></div>
144 <div><p class="font-bold text-emerald-600">{{ activity.distance.toFixed(1) }}</p><p class="metric-label">mi</p></div>
145 <div><p class="font-bold dark:text-white text-sm">{{ activity.duration }}</p><p class="metric-label">time</p></div>
146 <div><p class="font-bold dark:text-white text-sm">{{ activity.pace }}</p><p class="metric-label">pace</p></div>
147 <div><p class="font-bold dark:text-white text-sm">{{ activity.kudos_count }}</p><p class="metric-label">kudos</p></div>
148148 </div>
149149 </StxLink>
150150 </template>
@@ -171,10 +171,10 @@
171171 <template :for="t in myTerritories">
172172 <StxLink x-to="'/territory/' + t.id" class="card block hover:border-emerald-400 transition">
173173 <div class="flex justify-between items-start mb-2">
174 <h3 class="font-bold dark:text-white" :text="t.name"></h3>
175 <span class="px-2 py-0.5 text-xs font-semibold text-emerald-700 bg-emerald-100 dark:bg-emerald-900/40 rounded-full" :text="t.status"></span>
174 <h3 class="font-bold dark:text-white">{{ t.name }}</h3>
175 <span class="px-2 py-0.5 text-xs font-semibold text-emerald-700 bg-emerald-100 dark:bg-emerald-900/40 rounded-full">{{ t.status }}</span>
176176 </div>
177 <p class="text-sm text-gray-500 dark:text-slate-400" :text="(t.areaSize / 1000).toFixed(1) + ' km² · ' + t.defendCount + ' defenses'"></p>
177 <p class="text-sm text-gray-500 dark:text-slate-400">{{ (t.areaSize / 1000).toFixed(1) + ' km² · ' + t.defendCount + ' defenses' }}</p>
178178 </StxLink>
179179 </template>
180180 </div>
resources/views/leaderboard.stxmodified+15-15
Changes to resources/views/leaderboard.stx
@@ -73,23 +73,23 @@
7373 <!-- Top 3 podium -->
7474 <div :if="leaders.length >= 3" class="grid gap-4 grid-cols-3 mb-8">
7575 <div class="pt-8 text-center card">
76 <div class="flex items-center justify-center mb-2 mx-auto h-14 w-14 font-bold text-gray-600 text-xl dark:text-slate-300 bg-gray-200 dark:bg-slate-700 rounded-full" :text="(leaders[1]?.userName || 'U').charAt(0)"></div>
77 <p class="font-semibold text-sm truncate dark:text-white" :text="leaders[1]?.userName || 'Athlete'"></p>
76 <div class="flex items-center justify-center mb-2 mx-auto h-14 w-14 font-bold text-gray-600 text-xl dark:text-slate-300 bg-gray-200 dark:bg-slate-700 rounded-full">{{ (leaders[1]?.userName || 'U').charAt(0) }}</div>
77 <p class="font-semibold text-sm truncate dark:text-white">{{ leaders[1]?.userName || 'Athlete' }}</p>
7878 <p class="mt-1 font-bold text-gray-400 text-lg">2nd</p>
79 <p class="font-semibold text-emerald-600 text-sm" :text="metricText(leaders[1])"></p>
79 <p class="font-semibold text-emerald-600 text-sm">{{ metricText(leaders[1]) }}</p>
8080 </div>
8181 <div class="ring-2 ring-yellow-400 pt-4 text-center card">
8282 <div class="mb-1 text-2xl">��</div>
83 <div class="flex items-center justify-center mb-2 mx-auto h-16 w-16 font-bold text-2xl text-yellow-600 bg-yellow-100 dark:bg-yellow-900/30 rounded-full" :text="(leaders[0]?.userName || 'U').charAt(0)"></div>
84 <p class="font-bold text-sm truncate dark:text-white" :text="leaders[0]?.userName || 'Athlete'"></p>
83 <div class="flex items-center justify-center mb-2 mx-auto h-16 w-16 font-bold text-2xl text-yellow-600 bg-yellow-100 dark:bg-yellow-900/30 rounded-full">{{ (leaders[0]?.userName || 'U').charAt(0) }}</div>
84 <p class="font-bold text-sm truncate dark:text-white">{{ leaders[0]?.userName || 'Athlete' }}</p>
8585 <p class="mt-1 font-bold text-lg text-yellow-500">1st</p>
86 <p class="font-semibold text-emerald-600 text-sm" :text="metricText(leaders[0])"></p>
86 <p class="font-semibold text-emerald-600 text-sm">{{ metricText(leaders[0]) }}</p>
8787 </div>
8888 <div class="pt-8 text-center card">
89 <div class="flex items-center justify-center mb-2 mx-auto h-14 w-14 font-bold text-orange-600 text-xl bg-orange-100 dark:bg-orange-900/30 rounded-full" :text="(leaders[2]?.userName || 'U').charAt(0)"></div>
90 <p class="font-semibold text-sm truncate dark:text-white" :text="leaders[2]?.userName || 'Athlete'"></p>
89 <div class="flex items-center justify-center mb-2 mx-auto h-14 w-14 font-bold text-orange-600 text-xl bg-orange-100 dark:bg-orange-900/30 rounded-full">{{ (leaders[2]?.userName || 'U').charAt(0) }}</div>
90 <p class="font-semibold text-sm truncate dark:text-white">{{ leaders[2]?.userName || 'Athlete' }}</p>
9191 <p class="mt-1 font-bold text-lg text-orange-400">3rd</p>
92 <p class="font-semibold text-emerald-600 text-sm" :text="metricText(leaders[2])"></p>
92 <p class="font-semibold text-emerald-600 text-sm">{{ metricText(leaders[2]) }}</p>
9393 </div>
9494 </div>
9595
@@ -97,15 +97,15 @@
9797 <div class="space-y-2">
9898 <template :for="(leader, index) in leaders">
9999 <div class="flex gap-4 items-center py-3 card">
100 <span class="w-8 font-bold text-center text-sm" x-class="index < 3 ? 'text-emerald-600' : 'text-gray-400 dark:text-slate-500'" :text="index + 1"></span>
101 <div class="flex items-center justify-center shrink-0 h-10 w-10 font-bold text-gray-600 text-sm dark:text-slate-300 bg-gray-100 dark:bg-slate-700 rounded-full" :text="leader.userName.charAt(0)"></div>
100 <span class="w-8 font-bold text-center text-sm" x-class="index < 3 ? 'text-emerald-600' : 'text-gray-400 dark:text-slate-500'">{{ index + 1 }}</span>
101 <div class="flex items-center justify-center shrink-0 h-10 w-10 font-bold text-gray-600 text-sm dark:text-slate-300 bg-gray-100 dark:bg-slate-700 rounded-full">{{ leader.userName.charAt(0) }}</div>
102102 <div class="flex-1 min-w-0">
103 <StxLink x-to="'/athlete/' + leader.userId" class="font-semibold text-sm truncate dark:text-white hover:text-emerald-600 transition block" :text="leader.userName"></StxLink>
104 <p class="text-gray-500 text-xs dark:text-slate-400" :text="leader.trailsCompleted + ' trails completed'"></p>
103 <StxLink x-to="'/athlete/' + leader.userId" class="font-semibold text-sm truncate dark:text-white hover:text-emerald-600 transition block">{{ leader.userName }}</StxLink>
104 <p class="text-gray-500 text-xs dark:text-slate-400">{{ leader.trailsCompleted + ' trails completed' }}</p>
105105 </div>
106106 <div class="shrink-0 text-right">
107 <p class="font-bold text-emerald-600 text-sm" :text="metricText(leader)"></p>
108 <p class="text-gray-400 text-xs dark:text-slate-500" :text="leader.totalElevation.toLocaleString() + ' ft'"></p>
107 <p class="font-bold text-emerald-600 text-sm">{{ metricText(leader) }}</p>
108 <p class="text-gray-400 text-xs dark:text-slate-500">{{ leader.totalElevation.toLocaleString() + ' ft' }}</p>
109109 </div>
110110 </div>
111111 </template>
resources/views/login.stxmodified+2-2
Changes to resources/views/login.stx
@@ -50,10 +50,10 @@
5050 </div>
5151
5252 <!-- Error Alert -->
53 <div :if="error" class="mb-6 p-4 text-red-700 text-sm dark:text-red-400 bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-xl dark:border-red-800" :text="error"></div>
53 <div :if="error" class="mb-6 p-4 text-red-700 text-sm dark:text-red-400 bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-xl dark:border-red-800">{{ error }}</div>
5454
5555 <!-- Success Alert -->
56 <div :if="success" class="mb-6 p-4 text-emerald-700 text-sm dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 rounded-xl dark:border-emerald-800" :text="success"></div>
56 <div :if="success" class="mb-6 p-4 text-emerald-700 text-sm dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 rounded-xl dark:border-emerald-800">{{ success }}</div>
5757
5858 <!-- Login Form -->
5959 <form @submit="handleLogin" class="space-y-5">
resources/views/notifications.stxmodified+12-12
Changes to resources/views/notifications.stx
@@ -77,7 +77,7 @@
7777 <div class="flex items-center justify-between mb-6">
7878 <div class="flex items-center gap-3">
7979 <h1 class="text-2xl font-bold dark:text-white">Notifications</h1>
80 <span :if="unreadCount > 0" class="px-2 py-0.5 rounded-full text-xs font-semibold bg-red-500 text-white" x-text="unreadCount"></span>
80 <span :if="unreadCount > 0" class="px-2 py-0.5 rounded-full text-xs font-semibold bg-red-500 text-white">{{ unreadCount }}</span>
8181 </div>
8282 <button :if="unreadCount > 0" @click="markAll()" class="text-sm text-emerald-600 hover:text-emerald-700 font-medium transition flex items-center gap-1.5">
8383 <Icon name="check-check" size="16" />
@@ -89,23 +89,23 @@
8989 <div class="flex gap-2 mb-6 overflow-x-auto pb-2 -mx-4 px-4 sm:mx-0 sm:px-0">
9090 <button @click="filter.set('all')" x-class="filter === 'all' ? 'bg-emerald-600 text-white shadow-sm' : 'bg-gray-100 dark:bg-slate-800 text-gray-700 dark:text-slate-300 hover:bg-gray-200 dark:hover:bg-slate-700'" class="px-4 py-2 rounded-full text-sm font-medium whitespace-nowrap transition flex items-center gap-1.5">
9191 All
92 <span x-class="filter === 'all' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center" x-text="filterCount('all')"></span>
92 <span x-class="filter === 'all' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center">{{ filterCount('all') }}</span>
9393 </button>
9494 <button @click="filter.set('unread')" x-class="filter === 'unread' ? 'bg-emerald-600 text-white shadow-sm' : 'bg-gray-100 dark:bg-slate-800 text-gray-700 dark:text-slate-300 hover:bg-gray-200 dark:hover:bg-slate-700'" class="px-4 py-2 rounded-full text-sm font-medium whitespace-nowrap transition flex items-center gap-1.5">
9595 Unread
96 <span x-class="filter === 'unread' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center" x-text="filterCount('unread')"></span>
96 <span x-class="filter === 'unread' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center">{{ filterCount('unread') }}</span>
9797 </button>
9898 <button @click="filter.set('conquest')" x-class="filter === 'conquest' ? 'bg-emerald-600 text-white shadow-sm' : 'bg-gray-100 dark:bg-slate-800 text-gray-700 dark:text-slate-300 hover:bg-gray-200 dark:hover:bg-slate-700'" class="px-4 py-2 rounded-full text-sm font-medium whitespace-nowrap transition flex items-center gap-1.5">
9999 Conquests
100 <span x-class="filter === 'conquest' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center" x-text="filterCount('conquest')"></span>
100 <span x-class="filter === 'conquest' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center">{{ filterCount('conquest') }}</span>
101101 </button>
102102 <button @click="filter.set('kudos')" x-class="filter === 'kudos' ? 'bg-emerald-600 text-white shadow-sm' : 'bg-gray-100 dark:bg-slate-800 text-gray-700 dark:text-slate-300 hover:bg-gray-200 dark:hover:bg-slate-700'" class="px-4 py-2 rounded-full text-sm font-medium whitespace-nowrap transition flex items-center gap-1.5">
103103 Kudos
104 <span x-class="filter === 'kudos' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center" x-text="filterCount('kudos')"></span>
104 <span x-class="filter === 'kudos' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center">{{ filterCount('kudos') }}</span>
105105 </button>
106106 <button @click="filter.set('challenge')" x-class="filter === 'challenge' ? 'bg-emerald-600 text-white shadow-sm' : 'bg-gray-100 dark:bg-slate-800 text-gray-700 dark:text-slate-300 hover:bg-gray-200 dark:hover:bg-slate-700'" class="px-4 py-2 rounded-full text-sm font-medium whitespace-nowrap transition flex items-center gap-1.5">
107107 Challenges
108 <span x-class="filter === 'challenge' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center" x-text="filterCount('challenge')"></span>
108 <span x-class="filter === 'challenge' ? 'bg-white/20' : 'bg-gray-200 dark:bg-slate-700'" class="px-1.5 py-0.5 text-xs rounded-full min-w-[1.5rem] text-center">{{ filterCount('challenge') }}</span>
109109 </button>
110110 </div>
111111
@@ -113,7 +113,7 @@
113113 <div :if="filtered.length > 0" class="space-y-8">
114114 <template :for="group in groupedByDay">
115115 <div>
116 <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-slate-500 mb-3 px-1" x-text="group.label"></h2>
116 <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-slate-500 mb-3 px-1">{{ group.label }}</h2>
117117 <div class="space-y-2">
118118 <template :for="notif in group.items">
119119 <StxLink x-to="notif.link || '#'" @click="markOne(notif.id)" class="group relative flex items-start gap-4 p-4 rounded-xl border transition-all block" x-class="!notif.read ? 'bg-emerald-50/50 dark:bg-emerald-900/10 border-emerald-200 dark:border-emerald-800/40 hover:border-emerald-300 dark:hover:border-emerald-700/60' : 'bg-white dark:bg-slate-800/50 border-gray-200 dark:border-slate-700/50 hover:bg-gray-50 dark:hover:bg-slate-800 hover:border-gray-300 dark:hover:border-slate-600'">
@@ -135,10 +135,10 @@
135135 <!-- Body -->
136136 <div class="flex-1 min-w-0">
137137 <div class="flex items-start justify-between gap-3">
138 <p class="text-sm font-semibold text-gray-900 dark:text-white leading-tight" x-text="notif.title"></p>
139 <p class="text-xs text-gray-400 dark:text-slate-500 whitespace-nowrap shrink-0 mt-0.5" x-text="tb.timeAgo(notif.created_at)"></p>
138 <p class="text-sm font-semibold text-gray-900 dark:text-white leading-tight">{{ notif.title }}</p>
139 <p class="text-xs text-gray-400 dark:text-slate-500 whitespace-nowrap shrink-0 mt-0.5">{{ tb.timeAgo(notif.created_at) }}</p>
140140 </div>
141 <p class="text-sm text-gray-600 dark:text-slate-400 mt-1" x-text="notif.message"></p>
141 <p class="text-sm text-gray-600 dark:text-slate-400 mt-1">{{ notif.message }}</p>
142142 </div>
143143
144144 <!-- Chevron on hover -->
@@ -157,8 +157,8 @@
157157 <div class="w-20 h-20 mx-auto mb-6 bg-emerald-100 dark:bg-emerald-900/20 rounded-2xl flex items-center justify-center">
158158 <Icon name="check-circle" size="40" class="text-emerald-500" />
159159 </div>
160 <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2" x-text="filter === 'unread' ? 'All caught up!' : filter === 'all' ? 'No notifications yet' : 'Nothing here'"></h3>
161 <p class="text-gray-500 dark:text-slate-400 max-w-sm mx-auto" x-text="filter === 'unread' ? 'You\'ve read everything. Nice work.' : filter === 'all' ? 'When people give kudos, challenge you, or conquer your territory, it\'ll show up here.' : 'Try a different filter above.'"></p>
160 <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">{{ filter === 'unread' ? 'All caught up!' : filter === 'all' ? 'No notifications yet' : 'Nothing here' }}</h3>
161 <p class="text-gray-500 dark:text-slate-400 max-w-sm mx-auto">{{ filter === 'unread' ? 'You\'ve read everything. Nice work.' : filter === 'all' ? 'When people give kudos, challenge you, or conquer your territory, it\'ll show up here.' : 'Try a different filter above.' }}</p>
162162 </div>
163163 </div>
164164@endsection
resources/views/profile.stxmodified+22-22
Changes to resources/views/profile.stx
@@ -44,33 +44,33 @@
4444 <!-- Profile header -->
4545 <div class="mb-6 card">
4646 <div class="flex flex-col gap-6 items-center sm:flex-row">
47 <div class="flex items-center justify-center h-24 w-24 font-bold text-3xl text-emerald-600 bg-emerald-100 dark:bg-emerald-900/30 rounded-full" :text="(currentUser?.name || 'U').charAt(0)"></div>
47 <div class="flex items-center justify-center h-24 w-24 font-bold text-3xl text-emerald-600 bg-emerald-100 dark:bg-emerald-900/30 rounded-full">{{ (currentUser?.name || 'U').charAt(0) }}</div>
4848 <div class="flex-1 text-center sm:text-left">
49 <h1 class="font-bold text-2xl dark:text-white" :text="currentUser?.name || 'Athlete'"></h1>
50 <p class="mt-1 text-gray-500 text-sm dark:text-slate-400" :text="currentUser?.email || ''"></p>
49 <h1 class="font-bold text-2xl dark:text-white">{{ currentUser?.name || 'Athlete' }}</h1>
50 <p class="mt-1 text-gray-500 text-sm dark:text-slate-400">{{ currentUser?.email || '' }}</p>
5151 <div class="flex flex-wrap gap-4 justify-center sm:justify-start mt-3">
5252 <div class="text-center">
53 <p class="text-lg font-bold text-emerald-600" :text="userStats?.totalDistance.toFixed(1) || '0'"></p>
53 <p class="text-lg font-bold text-emerald-600">{{ userStats?.totalDistance.toFixed(1) || '0' }}</p>
5454 <p class="metric-label">Miles</p>
5555 </div>
5656 <div class="text-center">
57 <p class="text-lg font-bold text-emerald-600" :text="userStats?.trailsCompleted || 0"></p>
57 <p class="text-lg font-bold text-emerald-600">{{ userStats?.trailsCompleted || 0 }}</p>
5858 <p class="metric-label">Trails</p>
5959 </div>
6060 <div class="text-center">
61 <p class="text-lg font-bold text-emerald-600" :text="(userStats?.totalElevation || 0).toLocaleString()"></p>
61 <p class="text-lg font-bold text-emerald-600">{{ (userStats?.totalElevation || 0).toLocaleString() }}</p>
6262 <p class="metric-label">Elev (ft)</p>
6363 </div>
6464 <div class="text-center">
65 <p class="text-lg font-bold text-emerald-600" :text="userStats?.currentStreak || 0"></p>
65 <p class="text-lg font-bold text-emerald-600">{{ userStats?.currentStreak || 0 }}</p>
6666 <p class="metric-label">Streak</p>
6767 </div>
6868 <div class="text-center">
69 <p class="text-lg font-bold text-emerald-600" :text="followerCount"></p>
69 <p class="text-lg font-bold text-emerald-600">{{ followerCount }}</p>
7070 <p class="metric-label">Followers</p>
7171 </div>
7272 <div class="text-center">
73 <p class="text-lg font-bold text-emerald-600" :text="followingCount"></p>
73 <p class="text-lg font-bold text-emerald-600">{{ followingCount }}</p>
7474 <p class="metric-label">Following</p>
7575 </div>
7676 </div>
@@ -93,16 +93,16 @@
9393 <Icon name="zap" size="24" class="text-emerald-600" />
9494 </div>
9595 <div class="flex-1 min-w-0">
96 <p class="font-semibold text-sm truncate dark:text-white" :text="activity.title"></p>
97 <p class="text-gray-500 text-xs dark:text-slate-400" :text="activity.activityType + ' · ' + tb.timeAgo(activity.created_at)"></p>
96 <p class="font-semibold text-sm truncate dark:text-white">{{ activity.title }}</p>
97 <p class="text-gray-500 text-xs dark:text-slate-400">{{ activity.activityType + ' · ' + tb.timeAgo(activity.created_at) }}</p>
9898 </div>
9999 <div class="flex gap-4 shrink-0 text-right">
100100 <div>
101 <p class="font-bold text-sm dark:text-white" :text="activity.distance.toFixed(1) + ' mi'"></p>
101 <p class="font-bold text-sm dark:text-white">{{ activity.distance.toFixed(1) + ' mi' }}</p>
102102 <p class="metric-label">Distance</p>
103103 </div>
104104 <div>
105 <p class="font-bold text-sm dark:text-white" :text="activity.duration"></p>
105 <p class="font-bold text-sm dark:text-white">{{ activity.duration }}</p>
106106 <p class="metric-label">Time</p>
107107 </div>
108108 </div>
@@ -123,15 +123,15 @@
123123 <div :if="activeTab === 'achievements' && achievements.length > 0" class="grid gap-4 grid-cols-1 sm:grid-cols-2">
124124 <template :for="achievement in achievements">
125125 <div class="flex gap-4 items-center card">
126 <span class="text-3xl" :text="achievement.icon"></span>
126 <span class="text-3xl">{{ achievement.icon }}</span>
127127 <div class="flex-1">
128 <p class="font-semibold text-sm dark:text-white" :text="achievement.name"></p>
129 <p class="text-gray-500 text-xs dark:text-slate-400" :text="achievement.description"></p>
128 <p class="font-semibold text-sm dark:text-white">{{ achievement.name }}</p>
129 <p class="text-gray-500 text-xs dark:text-slate-400">{{ achievement.description }}</p>
130130 <div class="overflow-hidden mt-2 h-1.5 bg-gray-200 dark:bg-slate-600 rounded-full">
131131 <div class="h-full bg-emerald-500 rounded-full" x-style="'width: ' + (achievement.progress / achievement.total * 100) + '%'"></div>
132132 </div>
133133 </div>
134 <p class="shrink-0 text-gray-400 text-xs dark:text-slate-500" :text="achievement.progress + '/' + achievement.total"></p>
134 <p class="shrink-0 text-gray-400 text-xs dark:text-slate-500">{{ achievement.progress + '/' + achievement.total }}</p>
135135 </div>
136136 </template>
137137 </div>
@@ -150,14 +150,14 @@
150150 <template :for="trail in savedTrails">
151151 <div class="card flex items-center gap-4 hover:shadow-lg transition">
152152 <StxLink x-to="'/trail/' + trail.id" class="flex-1 min-w-0 group">
153 <h3 class="font-bold text-gray-900 dark:text-white group-hover:text-emerald-600 transition truncate" :text="trail.name"></h3>
154 <p class="text-sm text-gray-500 dark:text-slate-400 truncate" :text="trail.location"></p>
153 <h3 class="font-bold text-gray-900 dark:text-white group-hover:text-emerald-600 transition truncate">{{ trail.name }}</h3>
154 <p class="text-sm text-gray-500 dark:text-slate-400 truncate">{{ trail.location }}</p>
155155 <div class="flex gap-3 items-center mt-2 text-xs text-gray-500 dark:text-slate-400">
156 <span class="capitalize" x-class="'px-2 py-0.5 font-semibold rounded-full difficulty-' + trail.difficulty" :text="trail.difficulty"></span>
157 <span :text="trail.distance + ' mi'"></span>
156 <span class="capitalize" x-class="'px-2 py-0.5 font-semibold rounded-full difficulty-' + trail.difficulty">{{ trail.difficulty }}</span>
157 <span>{{ trail.distance + ' mi' }}</span>
158158 <span class="flex items-center gap-1">
159159 <Icon name="star" size="12" class="text-yellow-400" />
160 <span :text="trail.rating || '—'"></span>
160 <span>{{ trail.rating || '—' }}</span>
161161 </span>
162162 </div>
163163 </StxLink>
resources/views/record.stxmodified+15-15
Changes to resources/views/record.stx
@@ -68,11 +68,11 @@
6868 <div class="flex flex-wrap gap-2 items-center">
6969 <button @click="targetTerritoryId.set(null)" x-class="!targetTerritoryId ? 'ring-2 ring-emerald-500 bg-emerald-50 dark:bg-emerald-900/20' : 'bg-gray-100 dark:bg-slate-700'" class="px-3 py-1.5 text-xs font-medium rounded-lg transition">Any nearby</button>
7070 <template :for="zone in attackable">
71 <button @click="targetTerritoryId.set(zone.id)" x-class="targetTerritoryId === zone.id ? 'ring-2 ring-orange-500 bg-orange-50 dark:bg-orange-900/20' : 'bg-gray-100 dark:bg-slate-700'" class="px-3 py-1.5 text-xs font-medium rounded-lg transition" x-text="zone.name"></button>
71 <button @click="targetTerritoryId.set(zone.id)" x-class="targetTerritoryId === zone.id ? 'ring-2 ring-orange-500 bg-orange-50 dark:bg-orange-900/20' : 'bg-gray-100 dark:bg-slate-700'" class="px-3 py-1.5 text-xs font-medium rounded-lg transition">{{ zone.name }}</button>
7272 </template>
7373 <span :if="attackable.length === 0" class="text-xs text-gray-500 dark:text-slate-400">No enemy zones in range yet — start running near orange turf.</span>
7474 </div>
75 <p :if="targetZone" class="mt-2 text-orange-600 text-xs">Focus run: cross <span class="font-semibold" :text="targetZone.name"></span> to fill the capture meter</p>
75 <p :if="targetZone" class="mt-2 text-orange-600 text-xs">Focus run: cross <span class="font-semibold">{{ targetZone.name }}</span> to fill the capture meter</p>
7676 </div>
7777
7878 <!-- Map -->
@@ -81,7 +81,7 @@
8181
8282 <div class="flex absolute right-3 top-3 z-[1000] gap-1.5 items-center px-2.5 py-1 font-medium text-xs rounded-full bg-white/90 backdrop-blur-sm shadow-sm" x-class="gpsStatus === 'active' ? 'text-emerald-700' : gpsStatus === 'searching' ? 'text-yellow-700' : 'text-gray-600'">
8383 <div class="h-2 w-2 rounded-full" x-class="gpsStatus === 'active' ? 'bg-emerald-500 animate-pulse' : gpsStatus === 'searching' ? 'bg-yellow-500 animate-pulse' : 'bg-gray-400'"></div>
84 <span x-text="gpsStatus === 'active' ? (mode === 'simulated' ? 'Simulating' : 'GPS Active') : gpsStatus === 'searching' ? 'Searching...' : 'GPS Off'"></span>
84 <span>{{ gpsStatus === 'active' ? (mode === 'simulated' ? 'Simulating' : 'GPS Active') : gpsStatus === 'searching' ? 'Searching...' : 'GPS Off' }}</span>
8585 </div>
8686
8787 <div class="absolute bottom-3 left-3 z-[1000] px-3 py-2 bg-white/90 backdrop-blur-sm rounded-lg shadow-sm">
@@ -93,7 +93,7 @@
9393 </div>
9494 </div>
9595
96 <div :if="conquestToast" class="capture-toast absolute top-3 left-1/2 -translate-x-1/2 z-[1000] px-5 py-2.5 font-bold text-white bg-emerald-600 rounded-full shadow-xl" x-text="conquestToast"></div>
96 <div :if="conquestToast" class="capture-toast absolute top-3 left-1/2 -translate-x-1/2 z-[1000] px-5 py-2.5 font-bold text-white bg-emerald-600 rounded-full shadow-xl">{{ conquestToast }}</div>
9797 </div>
9898
9999 <!-- Live capture meters -->
@@ -101,8 +101,8 @@
101101 <template :for="cap in activeCaptures">
102102 <div class="card py-3">
103103 <div class="flex justify-between mb-1 text-xs">
104 <span class="font-semibold dark:text-white" :text="'Capturing ' + cap.name"></span>
105 <span class="text-purple-600 font-bold" :text="cap.pct + '%'"></span>
104 <span class="font-semibold dark:text-white">{{ 'Capturing ' + cap.name }}</span>
105 <span class="text-purple-600 font-bold">{{ cap.pct + '%' }}</span>
106106 </div>
107107 <div class="capture-bar"><div class="capture-bar-fill" :style="'width:' + cap.pct + '%'"></div></div>
108108 </div>
@@ -112,28 +112,28 @@
112112 <!-- Strava-style stats -->
113113 <div class="mb-6 card">
114114 <div class="mb-4 text-center">
115 <p class="font-bold font-mono text-5xl sm:text-6xl tracking-tight dark:text-white" x-text="fmtDuration(elapsed)"></p>
115 <p class="font-bold font-mono text-5xl sm:text-6xl tracking-tight dark:text-white">{{ fmtDuration(elapsed) }}</p>
116116 <p class="mt-1 metric-label">Elapsed</p>
117117 </div>
118118 <div class="grid gap-4 grid-cols-2 sm:grid-cols-5">
119119 <div class="strava-stat">
120 <p class="font-bold text-2xl dark:text-white" x-text="distance.toFixed(2)"></p>
120 <p class="font-bold text-2xl dark:text-white">{{ distance.toFixed(2) }}</p>
121121 <p class="metric-label">Miles</p>
122122 </div>
123123 <div class="strava-stat">
124 <p class="font-bold text-2xl dark:text-white" x-text="distance > 0.01 ? fmtDuration(Math.round(elapsed / distance)) : '--:--'"></p>
124 <p class="font-bold text-2xl dark:text-white">{{ distance > 0.01 ? fmtDuration(Math.round(elapsed / distance)) : '--:--' }}</p>
125125 <p class="metric-label">Pace</p>
126126 </div>
127127 <div class="strava-stat">
128 <p class="font-bold text-2xl dark:text-white" x-text="elevation.toLocaleString()"></p>
128 <p class="font-bold text-2xl dark:text-white">{{ elevation.toLocaleString() }}</p>
129129 <p class="metric-label">Elev ft</p>
130130 </div>
131131 <div class="strava-stat">
132 <p class="font-bold text-2xl text-emerald-600" x-text="conqueredIds.length"></p>
132 <p class="font-bold text-2xl text-emerald-600">{{ conqueredIds.length }}</p>
133133 <p class="metric-label">Captured</p>
134134 </div>
135135 <div class="strava-stat">
136 <p class="font-bold text-2xl text-purple-600" x-text="sessionXp"></p>
136 <p class="font-bold text-2xl text-purple-600">{{ sessionXp }}</p>
137137 <p class="metric-label">XP</p>
138138 </div>
139139 </div>
@@ -149,7 +149,7 @@
149149 <select @change="selectedTrailId.set(Number($event.target.value))" x-disabled="trailOptions.length === 0" class="flex-1 px-3 py-2 text-sm bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg disabled:opacity-50">
150150 <option :if="trailOptions.length === 0" disabled>No trails available yet</option>
151151 <template :for="opt in trailOptions">
152 <option x-value="opt.id" x-selected="opt.id === selectedTrailId" x-text="opt.name"></option>
152 <option x-value="opt.id" x-selected="opt.id === selectedTrailId" :text="opt.name"></option>
153153 </template>
154154 </select>
155155 <button @click="simulate()" x-disabled="trailOptions.length === 0" class="px-4 py-2 font-semibold text-sm text-white bg-sky-600 hover:bg-sky-700 rounded-lg transition whitespace-nowrap disabled:opacity-50 disabled:cursor-not-allowed">Simulate capture</button>
@@ -159,13 +159,13 @@
159159 <!-- Controls -->
160160 <div class="flex gap-4 items-center justify-center">
161161 <button @click="startManual()" x-class="recording ? 'hidden' : 'record-btn-start'">▶</button>
162 <button @click="togglePause()" x-class="!recording ? 'hidden' : paused ? 'record-btn-resume' : 'record-btn-pause'" x-text="paused ? '▶' : '⏸'"></button>
162 <button @click="togglePause()" x-class="!recording ? 'hidden' : paused ? 'record-btn-resume' : 'record-btn-pause'">{{ paused ? '▶' : '⏸' }}</button>
163163 <button @click="stop()" x-class="recording ? 'record-btn-stop' : 'hidden'">⏹</button>
164164 </div>
165165
166166 <p class="mt-4 text-center text-gray-500 text-sm dark:text-slate-400">
167167 <span :if="!recording">Hit ▶ to start GPS capture, or simulate a trail route</span>
168 <span :if="recording" x-text="runMode === 'capture' ? 'Stay inside enemy zones — capture bar fills as you run' : 'Free run — no territory capture'"></span>
168 <span :if="recording">{{ runMode === 'capture' ? 'Stay inside enemy zones — capture bar fills as you run' : 'Free run — no territory capture' }}</span>
169169 </p>
170170 </div>
171171@endsection
resources/views/register.stxmodified+3-3
Changes to resources/views/register.stx
@@ -83,10 +83,10 @@
8383 </div>
8484
8585 <!-- Error Alert -->
86 <div :if="error" class="mb-6 p-4 text-red-700 text-sm dark:text-red-400 bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-xl dark:border-red-800" :text="error"></div>
86 <div :if="error" class="mb-6 p-4 text-red-700 text-sm dark:text-red-400 bg-red-50 dark:bg-red-900/20 border border-red-200 rounded-xl dark:border-red-800">{{ error }}</div>
8787
8888 <!-- Success Alert -->
89 <div :if="success" class="mb-6 p-4 text-emerald-700 text-sm dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 rounded-xl dark:border-emerald-800" :text="success"></div>
89 <div :if="success" class="mb-6 p-4 text-emerald-700 text-sm dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 rounded-xl dark:border-emerald-800">{{ success }}</div>
9090
9191 <!-- Register Form -->
9292 <form @submit="handleRegister" class="space-y-5">
@@ -140,7 +140,7 @@
140140 <div class="overflow-hidden h-1 bg-gray-200 dark:bg-slate-600 rounded-full">
141141 <div x-class="passwordStrength().level" class="h-full rounded-full transition-all" x-style="'width: ' + passwordStrength().width"></div>
142142 </div>
143 <p x-class="passwordStrength().color" class="mt-1 text-xs" :text="passwordStrength().text"></p>
143 <p x-class="passwordStrength().color" class="mt-1 text-xs">{{ passwordStrength().text }}</p>
144144 </div>
145145 </div>
146146
resources/views/stats.stxmodified+18-18
Changes to resources/views/stats.stx
@@ -35,21 +35,21 @@
3535 <!-- Summary cards -->
3636 <div class="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8">
3737 <div class="card text-center">
38 <p class="text-3xl font-bold text-emerald-600" :text="period === 'weekly' ? userStats.weeklyDistance : period === 'monthly' ? userStats.monthlyDistance : userStats.totalDistance"></p>
38 <p class="text-3xl font-bold text-emerald-600">{{ period === 'weekly' ? userStats.weeklyDistance : period === 'monthly' ? userStats.monthlyDistance : userStats.totalDistance }}</p>
3939 <p class="metric-label">Miles</p>
4040 </div>
4141 <div class="card text-center">
42 <p class="text-3xl font-bold text-blue-600" :text="(period === 'weekly' ? userStats.weeklyElevation : period === 'monthly' ? userStats.monthlyElevation : userStats.totalElevation).toLocaleString()"></p>
42 <p class="text-3xl font-bold text-blue-600">{{ (period === 'weekly' ? userStats.weeklyElevation : period === 'monthly' ? userStats.monthlyElevation : userStats.totalElevation).toLocaleString() }}</p>
4343 <p class="metric-label">Elev (ft)</p>
4444 </div>
4545 <div class="card text-center">
46 <p class="text-3xl font-bold text-orange-600" :text="period === 'weekly' ? userStats.weeklyActivities : userStats.totalActivities"></p>
46 <p class="text-3xl font-bold text-orange-600">{{ period === 'weekly' ? userStats.weeklyActivities : userStats.totalActivities }}</p>
4747 <p class="metric-label">Activities</p>
4848 </div>
4949 <div class="card text-center">
50 <p class="text-3xl font-bold text-purple-600" :text="userStats.currentStreak"></p>
50 <p class="text-3xl font-bold text-purple-600">{{ userStats.currentStreak }}</p>
5151 <p class="metric-label">Day Streak</p>
52 <p class="text-xs text-purple-500 mt-1" :text="'Best: ' + userStats.longestStreak + ' days'"></p>
52 <p class="text-xs text-purple-500 mt-1">{{ 'Best: ' + userStats.longestStreak + ' days' }}</p>
5353 </div>
5454 </div>
5555
@@ -59,9 +59,9 @@
5959 <div :if="userStats.weeklyHistory.length > 0" class="flex items-end gap-3 h-40">
6060 <template :for="(week, i) in userStats.weeklyHistory">
6161 <div class="flex-1 flex flex-col items-center gap-2">
62 <span class="text-xs font-semibold dark:text-white" :text="week.distance.toFixed(1)"></span>
62 <span class="text-xs font-semibold dark:text-white">{{ week.distance.toFixed(1) }}</span>
6363 <div class="w-full rounded-t-lg transition-all duration-500" x-class="i === userStats.weeklyHistory.length - 1 ? 'bg-emerald-500' : 'bg-emerald-200 dark:bg-emerald-800'" x-style="'height: ' + (week.distance / maxWeeklyDistance * 100) + '%'"></div>
64 <span class="text-xs text-gray-500 dark:text-slate-400 whitespace-nowrap" :text="week.week"></span>
64 <span class="text-xs text-gray-500 dark:text-slate-400 whitespace-nowrap">{{ week.week }}</span>
6565 </div>
6666 </template>
6767 </div>
@@ -84,28 +84,28 @@
8484 <Icon name="zap" size="16" class="text-yellow-500" />
8585 Fastest Mile
8686 </span>
87 <span class="font-bold text-gray-900 dark:text-white" x-text="userStats.personalRecords.fastestMile + ' /mi'"></span>
87 <span class="font-bold text-gray-900 dark:text-white">{{ userStats.personalRecords.fastestMile + ' /mi' }}</span>
8888 </div>
8989 <div class="flex items-center justify-between p-3 bg-emerald-50 dark:bg-emerald-900/10 rounded-xl">
9090 <span class="flex items-center gap-2 text-sm text-gray-600 dark:text-slate-400">
9191 <Icon name="footprints" size="16" class="text-emerald-500" />
9292 Longest Run
9393 </span>
94 <span class="font-bold text-gray-900 dark:text-white" x-text="userStats.personalRecords.longestRun + ' mi'"></span>
94 <span class="font-bold text-gray-900 dark:text-white">{{ userStats.personalRecords.longestRun + ' mi' }}</span>
9595 </div>
9696 <div class="flex items-center justify-between p-3 bg-blue-50 dark:bg-blue-900/10 rounded-xl">
9797 <span class="flex items-center gap-2 text-sm text-gray-600 dark:text-slate-400">
9898 <Icon name="mountain" size="16" class="text-blue-500" />
9999 Biggest Climb
100100 </span>
101 <span class="font-bold text-gray-900 dark:text-white" x-text="userStats.personalRecords.biggestClimb.toLocaleString() + ' ft'"></span>
101 <span class="font-bold text-gray-900 dark:text-white">{{ userStats.personalRecords.biggestClimb.toLocaleString() + ' ft' }}</span>
102102 </div>
103103 <div class="flex items-center justify-between p-3 bg-orange-50 dark:bg-orange-900/10 rounded-xl">
104104 <span class="flex items-center gap-2 text-sm text-gray-600 dark:text-slate-400">
105105 <Icon name="flame" size="16" class="text-orange-500" />
106106 Longest Streak
107107 </span>
108 <span class="font-bold text-gray-900 dark:text-white" x-text="userStats.personalRecords.longestStreak + ' days'"></span>
108 <span class="font-bold text-gray-900 dark:text-white">{{ userStats.personalRecords.longestStreak + ' days' }}</span>
109109 </div>
110110 </div>
111111 </div>
@@ -117,7 +117,7 @@
117117 <div>
118118 <div class="flex items-center justify-between mb-1">
119119 <span class="text-sm text-gray-600 dark:text-slate-400">Trail Runs</span>
120 <span class="text-sm font-medium dark:text-white" :text="myActivities.filter((a) => a.activityType === 'Trail Run').length + ' activities'"></span>
120 <span class="text-sm font-medium dark:text-white">{{ myActivities.filter((a) => a.activityType === 'Trail Run').length + ' activities' }}</span>
121121 </div>
122122 <div class="h-2 bg-gray-200 dark:bg-slate-700 rounded-full overflow-hidden">
123123 <div class="h-full bg-emerald-500 rounded-full" x-style="'width: ' + (myActivities.filter((a) => a.activityType === 'Trail Run').length / Math.max(myActivities.length, 1) * 100) + '%'"></div>
@@ -126,7 +126,7 @@
126126 <div>
127127 <div class="flex items-center justify-between mb-1">
128128 <span class="text-sm text-gray-600 dark:text-slate-400">Hikes</span>
129 <span class="text-sm font-medium dark:text-white" :text="myActivities.filter((a) => a.activityType === 'Hike').length + ' activities'"></span>
129 <span class="text-sm font-medium dark:text-white">{{ myActivities.filter((a) => a.activityType === 'Hike').length + ' activities' }}</span>
130130 </div>
131131 <div class="h-2 bg-gray-200 dark:bg-slate-700 rounded-full overflow-hidden">
132132 <div class="h-full bg-blue-500 rounded-full" x-style="'width: ' + (myActivities.filter((a) => a.activityType === 'Hike').length / Math.max(myActivities.length, 1) * 100) + '%'"></div>
@@ -135,7 +135,7 @@
135135 <div>
136136 <div class="flex items-center justify-between mb-1">
137137 <span class="text-sm text-gray-600 dark:text-slate-400">Walks</span>
138 <span class="text-sm font-medium dark:text-white" :text="myActivities.filter((a) => a.activityType === 'Walk').length + ' activities'"></span>
138 <span class="text-sm font-medium dark:text-white">{{ myActivities.filter((a) => a.activityType === 'Walk').length + ' activities' }}</span>
139139 </div>
140140 <div class="h-2 bg-gray-200 dark:bg-slate-700 rounded-full overflow-hidden">
141141 <div class="h-full bg-purple-500 rounded-full" x-style="'width: ' + (myActivities.filter((a) => a.activityType === 'Walk').length / Math.max(myActivities.length, 1) * 100) + '%'"></div>
@@ -144,7 +144,7 @@
144144 <div>
145145 <div class="flex items-center justify-between mb-1">
146146 <span class="text-sm text-gray-600 dark:text-slate-400">Bike Rides</span>
147 <span class="text-sm font-medium dark:text-white" :text="myActivities.filter((a) => a.activityType === 'Bike').length + ' activities'"></span>
147 <span class="text-sm font-medium dark:text-white">{{ myActivities.filter((a) => a.activityType === 'Bike').length + ' activities' }}</span>
148148 </div>
149149 <div class="h-2 bg-gray-200 dark:bg-slate-700 rounded-full overflow-hidden">
150150 <div class="h-full bg-orange-500 rounded-full" x-style="'width: ' + (myActivities.filter((a) => a.activityType === 'Bike').length / Math.max(myActivities.length, 1) * 100) + '%'"></div>
@@ -153,9 +153,9 @@
153153 </div>
154154
155155 <div class="mt-6 pt-4 border-t border-gray-100 dark:border-slate-700 space-y-2 text-sm">
156 <div class="flex justify-between"><span class="text-gray-500 dark:text-slate-400">Total Time</span><span class="font-bold dark:text-white" :text="userStats.totalTime"></span></div>
157 <div class="flex justify-between"><span class="text-gray-500 dark:text-slate-400">Avg Pace</span><span class="font-bold dark:text-white" :text="userStats.avgPace"></span></div>
158 <div class="flex justify-between"><span class="text-gray-500 dark:text-slate-400">Total Calories</span><span class="font-bold dark:text-white" :text="userStats.totalCalories.toLocaleString()"></span></div>
156 <div class="flex justify-between"><span class="text-gray-500 dark:text-slate-400">Total Time</span><span class="font-bold dark:text-white">{{ userStats.totalTime }}</span></div>
157 <div class="flex justify-between"><span class="text-gray-500 dark:text-slate-400">Avg Pace</span><span class="font-bold dark:text-white">{{ userStats.avgPace }}</span></div>
158 <div class="flex justify-between"><span class="text-gray-500 dark:text-slate-400">Total Calories</span><span class="font-bold dark:text-white">{{ userStats.totalCalories.toLocaleString() }}</span></div>
159159 </div>
160160 </div>
161161 </div>
resources/views/territories.stxmodified+9-9
Changes to resources/views/territories.stx
@@ -66,20 +66,20 @@
6666 <template :for="territory in filtered">
6767 <StxLink x-to="'/territory/' + territory.id" class="card block hover:shadow-md transition">
6868 <div class="flex items-center justify-between mb-3">
69 <h3 class="font-bold dark:text-white" :text="territory.name"></h3>
70 <span class="px-2 py-0.5 text-xs rounded-full font-medium" x-class="territory.user_id === currentUserId() ? 'bg-emerald-100 text-emerald-700' : territory.status === 'contested' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700'" :text="territory.user_id === currentUserId() ? 'Yours' : territory.status === 'contested' ? 'Contested' : 'Enemy'"></span>
69 <h3 class="font-bold dark:text-white">{{ territory.name }}</h3>
70 <span class="px-2 py-0.5 text-xs rounded-full font-medium" x-class="territory.user_id === currentUserId() ? 'bg-emerald-100 text-emerald-700' : territory.status === 'contested' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-700'">{{ territory.user_id === currentUserId() ? 'Yours' : territory.status === 'contested' ? 'Contested' : 'Enemy' }}</span>
7171 </div>
7272 <div class="grid grid-cols-3 gap-2 text-center pt-3 border-t border-gray-100 dark:border-slate-700">
7373 <div>
74 <p class="font-bold text-sm text-emerald-600" :text="formatArea(territory.areaSize)"></p>
74 <p class="font-bold text-sm text-emerald-600">{{ formatArea(territory.areaSize) }}</p>
7575 <p class="metric-label">Size</p>
7676 </div>
7777 <div>
78 <p class="font-bold text-sm dark:text-white" :text="territory.conquestCount"></p>
78 <p class="font-bold text-sm dark:text-white">{{ territory.conquestCount }}</p>
7979 <p class="metric-label">Conquests</p>
8080 </div>
8181 <div>
82 <p class="font-bold text-sm dark:text-white" :text="territory.defendCount"></p>
82 <p class="font-bold text-sm dark:text-white">{{ territory.defendCount }}</p>
8383 <p class="metric-label">Defenses</p>
8484 </div>
8585 </div>
@@ -107,12 +107,12 @@
107107 <div class="space-y-2">
108108 <template :for="entry in rankedLeaderboard">
109109 <div class="flex items-center gap-3 p-2 rounded-lg" x-class="entry.user_id === currentUserId() ? 'bg-emerald-50 dark:bg-emerald-900/10' : ''">
110 <span class="w-6 text-center font-bold" x-class="entry.rank <= 3 ? 'text-yellow-500' : 'text-gray-400'" :text="entry.rank"></span>
110 <span class="w-6 text-center font-bold" x-class="entry.rank <= 3 ? 'text-yellow-500' : 'text-gray-400'">{{ entry.rank }}</span>
111111 <div class="flex-1 min-w-0">
112 <p class="font-medium text-sm dark:text-white truncate" :text="entry.user_id === currentUserId() ? 'You' : entry.userName"></p>
113 <p class="text-xs text-gray-500" :text="formatArea(entry.totalAreaOwned)"></p>
112 <p class="font-medium text-sm dark:text-white truncate">{{ entry.user_id === currentUserId() ? 'You' : entry.userName }}</p>
113 <p class="text-xs text-gray-500">{{ formatArea(entry.totalAreaOwned) }}</p>
114114 </div>
115 <span class="text-xs font-semibold text-gray-600 dark:text-slate-400" :text="entry.totalTerritoriesOwned + ' owned'"></span>
115 <span class="text-xs font-semibold text-gray-600 dark:text-slate-400">{{ entry.totalTerritoriesOwned + ' owned' }}</span>
116116 </div>
117117 </template>
118118 <div :if="rankedLeaderboard().length === 0" class="py-6 text-center text-sm text-gray-500 dark:text-slate-400">
resources/views/territory/[id].stxmodified+17-17
Changes to resources/views/territory/[id].stx
@@ -56,13 +56,13 @@
5656 <div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
5757 <div>
5858 <div class="flex gap-3 items-center mb-3">
59 <span class="px-3 py-1 font-semibold text-sm rounded-full" x-class="isMine ? 'bg-white/20 text-white' : 'bg-orange-500 text-white'" :text="isMine ? 'Your Territory' : 'Enemy Territory'"></span>
60 <span class="px-3 py-1 font-semibold text-sm rounded-full" x-class="territory.status === 'contested' ? 'bg-red-500 text-white' : 'bg-emerald-300/30 text-white'" :text="territory.status === 'contested' ? 'Contested' : 'Secure'"></span>
59 <span class="px-3 py-1 font-semibold text-sm rounded-full" x-class="isMine ? 'bg-white/20 text-white' : 'bg-orange-500 text-white'">{{ isMine ? 'Your Territory' : 'Enemy Territory' }}</span>
60 <span class="px-3 py-1 font-semibold text-sm rounded-full" x-class="territory.status === 'contested' ? 'bg-red-500 text-white' : 'bg-emerald-300/30 text-white'">{{ territory.status === 'contested' ? 'Contested' : 'Secure' }}</span>
6161 </div>
62 <h1 class="font-bold font-display text-3xl md:text-4xl text-white mb-2" :text="territory.name"></h1>
63 <p class="text-lg text-white/70">Owned by <span class="font-semibold text-white" :text="isMine ? 'You' : (owner?.name || 'Unknown')"></span></p>
62 <h1 class="font-bold font-display text-3xl md:text-4xl text-white mb-2">{{ territory.name }}</h1>
63 <p class="text-lg text-white/70">Owned by <span class="font-semibold text-white">{{ isMine ? 'You' : (owner?.name || 'Unknown') }}</span></p>
6464 </div>
65 <StxLink to="/record" class="px-6 py-3 font-semibold text-sm rounded-xl transition" x-class="isMine ? 'bg-white/20 text-white hover:bg-white/30' : 'bg-white text-orange-700 hover:bg-gray-100 shadow-lg'" :text="isMine ? 'Defend' : 'Conquer This'"></StxLink>
65 <StxLink to="/record" class="px-6 py-3 font-semibold text-sm rounded-xl transition" x-class="isMine ? 'bg-white/20 text-white hover:bg-white/30' : 'bg-white text-orange-700 hover:bg-gray-100 shadow-lg'">{{ isMine ? 'Defend' : 'Conquer This' }}</StxLink>
6666 </div>
6767 </div>
6868 </section>
@@ -76,19 +76,19 @@
7676
7777 <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
7878 <div class="card text-center">
79 <p class="text-2xl font-bold text-emerald-600" :text="(territory.areaSize / 1000).toFixed(1)"></p>
79 <p class="text-2xl font-bold text-emerald-600">{{ (territory.areaSize / 1000).toFixed(1) }}</p>
8080 <p class="metric-label">km²</p>
8181 </div>
8282 <div class="card text-center">
83 <p class="text-2xl font-bold text-blue-600" :text="territory.conquestCount"></p>
83 <p class="text-2xl font-bold text-blue-600">{{ territory.conquestCount }}</p>
8484 <p class="metric-label">Times Conquered</p>
8585 </div>
8686 <div class="card text-center">
87 <p class="text-2xl font-bold text-orange-600" :text="territory.defendCount"></p>
87 <p class="text-2xl font-bold text-orange-600">{{ territory.defendCount }}</p>
8888 <p class="metric-label">Defenses</p>
8989 </div>
9090 <div class="card text-center">
91 <p class="text-2xl font-bold text-purple-600" :text="territory.totalRunners"></p>
91 <p class="text-2xl font-bold text-purple-600">{{ territory.totalRunners }}</p>
9292 <p class="metric-label">Runners</p>
9393 </div>
9494 </div>
@@ -98,15 +98,15 @@
9898 <div class="space-y-3">
9999 <template :for="event in history">
100100 <div class="card flex items-center gap-4">
101 <div class="w-10 h-10 rounded-full flex items-center justify-center shrink-0 font-bold" x-class="event.status === 'conquered' ? 'bg-orange-100 dark:bg-orange-900/20 text-orange-600' : 'bg-emerald-100 dark:bg-emerald-900/20 text-emerald-600'" :text="event.attackerName.charAt(0)"></div>
101 <div class="w-10 h-10 rounded-full flex items-center justify-center shrink-0 font-bold" x-class="event.status === 'conquered' ? 'bg-orange-100 dark:bg-orange-900/20 text-orange-600' : 'bg-emerald-100 dark:bg-emerald-900/20 text-emerald-600'">{{ event.attackerName.charAt(0) }}</div>
102102 <div class="flex-1">
103103 <p class="text-sm font-semibold dark:text-white">
104 <span :text="event.attackerName"></span>
105 <span :text="event.status === 'conquered' ? ' conquered this' : ' failed to conquer'"></span>
104 <span>{{ event.attackerName }}</span>
105 <span>{{ event.status === 'conquered' ? ' conquered this' : ' failed to conquer' }}</span>
106106 </p>
107 <p class="text-xs text-gray-500" :text="tb.timeAgo(event.created_at)"></p>
107 <p class="text-xs text-gray-500">{{ tb.timeAgo(event.created_at) }}</p>
108108 </div>
109 <span class="text-xs px-2 py-1 rounded-full font-medium" x-class="event.status === 'conquered' ? 'bg-orange-100 text-orange-700' : 'bg-emerald-100 text-emerald-700'" :text="event.status === 'conquered' ? 'Conquered' : 'Defended'"></span>
109 <span class="text-xs px-2 py-1 rounded-full font-medium" x-class="event.status === 'conquered' ? 'bg-orange-100 text-orange-700' : 'bg-emerald-100 text-emerald-700'">{{ event.status === 'conquered' ? 'Conquered' : 'Defended' }}</span>
110110 </div>
111111 </template>
112112 <div :if="history.length === 0" class="text-center py-8 text-gray-400 text-sm">
@@ -120,10 +120,10 @@
120120 <div class="card">
121121 <h3 class="font-bold text-lg dark:text-white mb-4">Current Owner</h3>
122122 <div class="flex items-center gap-4">
123 <div class="w-14 h-14 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 text-xl font-bold" :text="(owner?.name || 'U').charAt(0)"></div>
123 <div class="w-14 h-14 rounded-full bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center text-emerald-600 text-xl font-bold">{{ (owner?.name || 'U').charAt(0) }}</div>
124124 <div>
125 <p class="font-bold dark:text-white" :text="isMine ? 'You' : (owner?.name || 'Unknown')"></p>
126 <p class="text-sm text-gray-500" :text="'Claimed ' + tb.timeAgo(territory.claimedAt)"></p>
125 <p class="font-bold dark:text-white">{{ isMine ? 'You' : (owner?.name || 'Unknown') }}</p>
126 <p class="text-sm text-gray-500">{{ 'Claimed ' + tb.timeAgo(territory.claimedAt) }}</p>
127127 </div>
128128 </div>
129129 </div>
resources/views/trail/[id].stxmodified+32-32
Changes to resources/views/trail/[id].stx
@@ -76,11 +76,11 @@
7676 <div class="relative h-full flex items-end px-4 mx-auto max-w-7xl sm:px-6 lg:px-8 pb-8">
7777 <div class="flex-1">
7878 <div class="flex items-center gap-2 mb-3">
79 <span class="px-2.5 py-1 rounded-full text-xs font-semibold capitalize" x-class="'difficulty-' + trail.difficulty" x-text="trail.difficulty"></span>
80 <span class="px-2.5 py-1 rounded-full text-xs font-semibold bg-white/20 text-white" x-text="trail.distance + ' mi'"></span>
79 <span class="px-2.5 py-1 rounded-full text-xs font-semibold capitalize" x-class="'difficulty-' + trail.difficulty">{{ trail.difficulty }}</span>
80 <span class="px-2.5 py-1 rounded-full text-xs font-semibold bg-white/20 text-white">{{ trail.distance + ' mi' }}</span>
8181 </div>
82 <h1 class="font-display text-3xl md:text-4xl font-bold text-white mb-2" x-text="trail.name"></h1>
83 <p class="text-white/70" x-text="trail.location"></p>
82 <h1 class="font-display text-3xl md:text-4xl font-bold text-white mb-2">{{ trail.name }}</h1>
83 <p class="text-white/70">{{ trail.location }}</p>
8484 </div>
8585 <div class="flex gap-2">
8686 <button @click="onToggleSave(trailId())" class="p-3 rounded-xl backdrop-blur-sm transition" x-class="saved ? 'bg-red-500 text-white' : 'bg-white/20 text-white hover:bg-white/30'" x-aria-label="saved ? 'Unsave trail' : 'Save trail'">
@@ -97,30 +97,30 @@
9797 <!-- Stats -->
9898 <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
9999 <div class="card text-center">
100 <p class="text-2xl font-bold text-emerald-600" x-text="trail.distance"></p>
100 <p class="text-2xl font-bold text-emerald-600">{{ trail.distance }}</p>
101101 <p class="metric-label">Miles</p>
102102 </div>
103103 <div class="card text-center">
104 <p class="text-2xl font-bold text-blue-600" x-text="trail.elevation.toLocaleString()"></p>
104 <p class="text-2xl font-bold text-blue-600">{{ trail.elevation.toLocaleString() }}</p>
105105 <p class="metric-label">Elev (ft)</p>
106106 </div>
107107 <div class="card text-center">
108 <p class="text-2xl font-bold text-orange-600" x-text="trail.estimatedTime"></p>
108 <p class="text-2xl font-bold text-orange-600">{{ trail.estimatedTime }}</p>
109109 <p class="metric-label">Est. Time</p>
110110 </div>
111111 <div class="card text-center">
112 <p class="text-2xl font-bold" x-text="trail.rating || '—'"></p>
113 <p class="metric-label" x-text="trail.reviewCount + ' reviews'"></p>
112 <p class="text-2xl font-bold">{{ trail.rating || '—' }}</p>
113 <p class="metric-label">{{ trail.reviewCount + ' reviews' }}</p>
114114 </div>
115115 </div>
116116
117117 <!-- Description -->
118118 <div class="card">
119119 <h2 class="font-bold text-lg dark:text-white mb-3">About This Trail</h2>
120 <p class="text-gray-600 dark:text-slate-400 leading-relaxed" x-text="trail.description"></p>
120 <p class="text-gray-600 dark:text-slate-400 leading-relaxed">{{ trail.description }}</p>
121121 <div class="flex flex-wrap gap-2 mt-4">
122122 <template :for="tag in trail.tags">
123 <span class="px-2.5 py-1 bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 text-xs font-medium rounded-full" x-text="tag"></span>
123 <span class="px-2.5 py-1 bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 text-xs font-medium rounded-full">{{ tag }}</span>
124124 </template>
125125 </div>
126126 </div>
@@ -137,16 +137,16 @@
137137 <template :for="seg in segments">
138138 <div class="card flex items-center gap-4">
139139 <div class="flex-1 min-w-0">
140 <p class="font-semibold text-sm dark:text-white" x-text="seg.name"></p>
141 <p class="text-xs text-gray-500 dark:text-slate-400" x-text="seg.distance + ' mi · ' + Math.abs(seg.elevation) + ' ft'"></p>
140 <p class="font-semibold text-sm dark:text-white">{{ seg.name }}</p>
141 <p class="text-xs text-gray-500 dark:text-slate-400">{{ seg.distance + ' mi · ' + Math.abs(seg.elevation) + ' ft' }}</p>
142142 </div>
143143 <div class="text-right">
144 <p class="text-sm font-bold dark:text-white" x-text="seg.yourTime || '—'"></p>
145 <p class="text-xs text-gray-500" x-text="seg.yourRank ? '#' + seg.yourRank : 'Not attempted'"></p>
144 <p class="text-sm font-bold dark:text-white">{{ seg.yourTime || '—' }}</p>
145 <p class="text-xs text-gray-500">{{ seg.yourRank ? '#' + seg.yourRank : 'Not attempted' }}</p>
146146 </div>
147147 <div class="text-right">
148148 <p class="text-xs text-yellow-600 font-medium">CR</p>
149 <p class="text-xs text-gray-500" x-text="seg.leaderTime + ' · ' + seg.leaderName"></p>
149 <p class="text-xs text-gray-500">{{ seg.leaderTime + ' · ' + seg.leaderName }}</p>
150150 </div>
151151 </div>
152152 </template>
@@ -156,7 +156,7 @@
156156 <!-- Reviews -->
157157 <div>
158158 <div class="flex gap-1 mb-4 border-b border-gray-200 dark:border-slate-700">
159 <button @click="reviewTab.set('reviews')" x-class="reviewTab === 'reviews' ? 'border-emerald-600 text-emerald-600' : 'border-transparent text-gray-500'" class="px-4 py-3 text-sm font-medium border-b-2 transition" x-text="'Reviews (' + reviews.length + ')'"></button>
159 <button @click="reviewTab.set('reviews')" x-class="reviewTab === 'reviews' ? 'border-emerald-600 text-emerald-600' : 'border-transparent text-gray-500'" class="px-4 py-3 text-sm font-medium border-b-2 transition">{{ 'Reviews (' + reviews.length + ')' }}</button>
160160 <button @click="reviewTab.set('conditions')" x-class="reviewTab === 'conditions' ? 'border-emerald-600 text-emerald-600' : 'border-transparent text-gray-500'" class="px-4 py-3 text-sm font-medium border-b-2 transition">Conditions</button>
161161 </div>
162162
@@ -172,7 +172,7 @@
172172 x-class="star <= formRating ? 'text-yellow-400' : 'text-gray-300 dark:text-slate-600 hover:text-yellow-300'"
173173 x-aria-label="'Rate ' + star + ' stars'">★</button>
174174 </template>
175 <span :if="formRating > 0" class="ml-2 text-xs text-gray-500" :text="formRating + '/5'"></span>
175 <span :if="formRating > 0" class="ml-2 text-xs text-gray-500">{{ formRating + '/5' }}</span>
176176 </div>
177177 <div class="flex flex-col sm:flex-row gap-3 mb-3">
178178 <textarea
@@ -184,13 +184,13 @@
184184 <select @change="formConditions.set($event.target.value)" class="sm:w-40 px-3 py-2 border border-gray-200 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-sm h-fit">
185185 <option value="">Conditions…</option>
186186 <template :for="opt in conditionOptions">
187 <option x-value="opt" x-selected="opt === formConditions" x-text="opt"></option>
187 <option x-value="opt" x-selected="opt === formConditions" :text="opt"></option>
188188 </template>
189189 </select>
190190 </div>
191 <p :if="reviewError" class="text-sm text-red-500 mb-2" :text="reviewError"></p>
191 <p :if="reviewError" class="text-sm text-red-500 mb-2">{{ reviewError }}</p>
192192 <div class="flex items-center gap-3">
193 <button @click="submitReview()" class="px-4 py-2 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition" :text="submittingReview ? 'Saving…' : 'Post review'"></button>
193 <button @click="submitReview()" class="px-4 py-2 bg-emerald-600 text-white text-sm font-semibold rounded-lg hover:bg-emerald-700 transition">{{ submittingReview ? 'Saving…' : 'Post review' }}</button>
194194 <span :if="reviewSaved" class="text-sm text-emerald-600 font-medium">Saved — thanks!</span>
195195 </div>
196196 </div>
@@ -199,16 +199,16 @@
199199 <div class="card">
200200 <div class="flex items-center justify-between mb-2">
201201 <div class="flex items-center gap-3">
202 <div class="w-9 h-9 rounded-full bg-emerald-100 dark:bg-emerald-900/20 flex items-center justify-center text-emerald-600 text-sm font-bold" x-text="review.userName.charAt(0)"></div>
202 <div class="w-9 h-9 rounded-full bg-emerald-100 dark:bg-emerald-900/20 flex items-center justify-center text-emerald-600 text-sm font-bold">{{ review.userName.charAt(0) }}</div>
203203 <div>
204 <p class="font-semibold text-sm dark:text-white" x-text="review.userName"></p>
205 <p class="text-xs text-gray-500" x-text="tb.timeAgo(review.created_at)"></p>
204 <p class="font-semibold text-sm dark:text-white">{{ review.userName }}</p>
205 <p class="text-xs text-gray-500">{{ tb.timeAgo(review.created_at) }}</p>
206206 </div>
207207 </div>
208 <span class="text-yellow-400" x-text="'★'.repeat(review.rating)"></span>
208 <span class="text-yellow-400">{{ '★'.repeat(review.rating) }}</span>
209209 </div>
210 <p class="text-sm text-gray-600 dark:text-slate-400" x-text="review.text"></p>
211 <p :if="review.conditions" class="mt-2 text-xs text-gray-400 capitalize" x-text="'Conditions: ' + review.conditions"></p>
210 <p class="text-sm text-gray-600 dark:text-slate-400">{{ review.text }}</p>
211 <p :if="review.conditions" class="mt-2 text-xs text-gray-400 capitalize">{{ 'Conditions: ' + review.conditions }}</p>
212212 </div>
213213 </template>
214214 <div :if="reviews.length === 0" class="text-center py-6 text-gray-400 text-sm">
@@ -218,7 +218,7 @@
218218
219219 <div :if="reviewTab === 'conditions'" class="card">
220220 <h3 class="font-semibold text-sm dark:text-white mb-2">Current Conditions</h3>
221 <p class="text-sm text-gray-600 dark:text-slate-400" x-text="trail.conditions"></p>
221 <p class="text-sm text-gray-600 dark:text-slate-400">{{ trail.conditions }}</p>
222222 </div>
223223 </div>
224224 </div>
@@ -228,10 +228,10 @@
228228 <div class="card">
229229 <h3 class="font-bold text-lg dark:text-white mb-4">Trail Info</h3>
230230 <div class="space-y-3">
231 <div class="flex justify-between"><span class="text-sm text-gray-500">Difficulty</span><span class="text-sm font-medium capitalize" x-class="'difficulty-' + trail.difficulty" x-text="trail.difficulty"></span></div>
232 <div class="flex justify-between"><span class="text-sm text-gray-500">Distance</span><span class="text-sm font-medium dark:text-white" x-text="trail.distance + ' mi'"></span></div>
233 <div class="flex justify-between"><span class="text-sm text-gray-500">Elevation</span><span class="text-sm font-medium dark:text-white" x-text="trail.elevation.toLocaleString() + ' ft'"></span></div>
234 <div class="flex justify-between"><span class="text-sm text-gray-500">Time</span><span class="text-sm font-medium dark:text-white" x-text="trail.estimatedTime"></span></div>
231 <div class="flex justify-between"><span class="text-sm text-gray-500">Difficulty</span><span class="text-sm font-medium capitalize" x-class="'difficulty-' + trail.difficulty">{{ trail.difficulty }}</span></div>
232 <div class="flex justify-between"><span class="text-sm text-gray-500">Distance</span><span class="text-sm font-medium dark:text-white">{{ trail.distance + ' mi' }}</span></div>
233 <div class="flex justify-between"><span class="text-sm text-gray-500">Elevation</span><span class="text-sm font-medium dark:text-white">{{ trail.elevation.toLocaleString() + ' ft' }}</span></div>
234 <div class="flex justify-between"><span class="text-sm text-gray-500">Time</span><span class="text-sm font-medium dark:text-white">{{ trail.estimatedTime }}</span></div>
235235 </div>
236236 </div>
237237
resources/views/trails.stxmodified+14-14
Changes to resources/views/trails.stx
@@ -94,8 +94,8 @@
9494 <div>
9595 <h1 class="font-bold text-2xl dark:text-white">Explore Trails</h1>
9696 <p class="text-sm text-gray-500 dark:text-slate-400 mt-1">
97 <span :text="trailCount() + ' trails'"></span>
98 <span :if="routeCount() > 0"> · <span :text="routeCount() + ' with GPS paths'"></span></span>
97 <span>{{ trailCount() + ' trails' }}</span>
98 <span :if="routeCount() > 0"> · <span>{{ routeCount() + ' with GPS paths' }}</span></span>
9999 <span :if="catalogSource() === 'api'" class="text-emerald-600 dark:text-emerald-400"> · live data</span>
100100 </p>
101101 </div>
@@ -109,7 +109,7 @@
109109 </div>
110110
111111 <div :if="catalogLoading()" class="mb-4 text-sm text-emerald-600 dark:text-emerald-400">Loading trail database…</div>
112 <div :if="catalogError()" class="mb-4 text-sm text-amber-600 dark:text-amber-400" :text="'Using demo trails — ' + catalogError()"></div>
112 <div :if="catalogError()" class="mb-4 text-sm text-amber-600 dark:text-amber-400">{{ 'Using demo trails — ' + catalogError() }}</div>
113113
114114 <!-- Split: trail list + map (Strava-style) -->
115115 <div class="grid gap-6 mb-6 lg:grid-cols-5">
@@ -131,12 +131,12 @@
131131 <img x-src="trail.image || 'https://images.unsplash.com/photo-1448375240586-882707db888b?w=200&h=200&fit=crop'" x-alt="trail.name" class="object-cover h-full w-full" />
132132 </div>
133133 <div class="min-w-0 flex-1">
134 <p class="font-semibold text-sm dark:text-white truncate" :text="trail.name"></p>
135 <p class="text-xs text-gray-500 dark:text-slate-400 truncate" :text="trail.location"></p>
134 <p class="font-semibold text-sm dark:text-white truncate">{{ trail.name }}</p>
135 <p class="text-xs text-gray-500 dark:text-slate-400 truncate">{{ trail.location }}</p>
136136 <p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">
137 <span :text="trail.distance + ' mi'"></span>
137 <span>{{ trail.distance + ' mi' }}</span>
138138 <span class="mx-1">·</span>
139 <span x-class="'capitalize difficulty-' + trail.difficulty" :text="trail.difficulty"></span>
139 <span x-class="'capitalize difficulty-' + trail.difficulty">{{ trail.difficulty }}</span>
140140 </p>
141141 </div>
142142 </StxLink>
@@ -166,7 +166,7 @@
166166 <div class="overflow-hidden relative h-48">
167167 <img x-src="trail.image || 'https://images.unsplash.com/photo-1448375240586-882707db888b?w=800&h=600&fit=crop'" x-alt="trail.name" class="object-cover h-full w-full duration-300 transition-transform group-hover:scale-105" />
168168 <div class="absolute left-3 top-3">
169 <span x-class="'px-2 py-1 capitalize font-semibold text-xs rounded-full difficulty-' + trail.difficulty" :text="trail.difficulty"></span>
169 <span x-class="'px-2 py-1 capitalize font-semibold text-xs rounded-full difficulty-' + trail.difficulty">{{ trail.difficulty }}</span>
170170 </div>
171171 <button
172172 @click.stop.prevent="onToggleSave(trail.id)"
@@ -178,17 +178,17 @@
178178 </div>
179179 <div class="p-4">
180180 <div class="flex items-start justify-between mb-1">
181 <h3 class="font-bold text-gray-900 dark:text-white group-hover:text-emerald-600 transition" :text="trail.name"></h3>
181 <h3 class="font-bold text-gray-900 dark:text-white group-hover:text-emerald-600 transition">{{ trail.name }}</h3>
182182 <div class="flex gap-1 items-center shrink-0 text-sm">
183183 <Icon name="star" size="16" class="text-yellow-400" />
184 <span class="font-semibold" :text="trail.rating || '—'"></span>
184 <span class="font-semibold">{{ trail.rating || '—' }}</span>
185185 </div>
186186 </div>
187 <p class="mb-3 text-gray-500 text-sm dark:text-slate-400" :text="trail.location"></p>
187 <p class="mb-3 text-gray-500 text-sm dark:text-slate-400">{{ trail.location }}</p>
188188 <div class="flex gap-4 items-center pt-3 text-gray-500 text-xs dark:text-slate-400 border-gray-100 border-t dark:border-slate-700">
189 <span :text="trail.distance + ' mi'"></span>
190 <span :text="trail.elevation.toLocaleString() + ' ft elev'"></span>
191 <span :text="'~' + trail.estimatedTime"></span>
189 <span>{{ trail.distance + ' mi' }}</span>
190 <span>{{ trail.elevation.toLocaleString() + ' ft elev' }}</span>
191 <span>{{ '~' + trail.estimatedTime }}</span>
192192 </div>
193193 </div>
194194 </StxLink>