package web templ Layout(title string, content templ.Component) { { title } · biking_home
@content
} templ RidesPage(items []RideView, headers []RideSortHeader) { @Layout("Rides", RidesContent(items, headers)) } templ RidesContent(items []RideView, headers []RideSortHeader) {

Ride library

All rides

Your imported rides, ready for climb analysis.

Sync Strava
if len(items) == 0 {
No rides stored yet. Import your first Strava rides.
} else { for _, header := range headers { } for _, item := range items { }
{ header.Label }
{ item.Name }
{ item.Type }
{ formatRideDate(item.StartDate) }{ formatDistance(item.DistanceM) }{ formatDuration(item.MovingTimeS) }{ formatElevation(item.TotalElevationGainM) }{ item.Cotacol }{ item.CotacolPer100Km }
}
} templ RideDetailPage(data RideDetailView) { @Layout("Ride detail", RideDetailContent(data)) } templ RideDetailContent(data RideDetailView) {

{ data.Ride.Name }

{ data.Ride.Type } · { formatRideDate(data.Ride.StartDate) }

Back to rides
if data.RouteError != "" {
{ data.RouteError }
} if data.Notice != "" {
{ data.Notice }
} if data.ActionError != "" {
{ data.ActionError }
}
Distance
{ formatDistance(data.Ride.DistanceM) }
Moving time
{ formatDuration(data.Ride.MovingTimeS) }
Elevation
{ formatElevation(data.Ride.TotalElevationGainM) }
if data.HasRoute { if len(data.Profile.OfficialClimbs()) > 0 {

Official climbs

for _, climb := range data.Profile.OfficialClimbs() {
Climb { formatClimbNumber(climb.Index) }{ climb.OfficialName }
Length
{ formatProfileDistance(climb.DistanceKm) }
Cotacol
{ formatCotacol(climb.Cotacol) }
Avg slope
{ formatSlope(climb.SlopePercent) }
<=0% 0–3% 3–6% 6–9% 9%+
}
}
if len(data.Profile.UnmatchedClimbs()) > 0 {

Next steps

Climbs to match

{ formatClimbCount(len(data.Profile.UnmatchedClimbs())) }
for _, climb := range data.Profile.UnmatchedClimbs() {
Climb { formatClimbNumber(climb.Index) }{ climb.Name } { formatProfileDistance(climb.StartKm) }–{ formatProfileDistance(climb.EndKm) }

{ climb.Category } · { formatProfileDistance(climb.DistanceKm) } at { formatSlope(climb.SlopePercent) } · Cotacol { formatCotacol(climb.Cotacol) }

Waiting for an official climb match.

Start{ formatProfileDistance(climb.StartKm) }
End{ formatProfileDistance(climb.EndKm) }
Preview the corrected climb boundaries.
}
}

Elevation profile

Hover or focus the profile to inspect the ride at that distance.

Hover or focus the profile to inspect elevation.
}
if data.HasRoute { @templ.JSONScript("ride-route", data.Route) @templ.JSONScript("ride-profile", data.Profile) @templ.JSONScript("ride-passes", data.Passes) } } templ SyncPage(data SyncPageData) { @Layout("Sync Strava", SyncContent(data)) } templ SyncContent(data SyncPageData) {

Data import

Sync Strava rides

Choose a date range. New rides are downloaded as GPX files and recorded in the local library.

if data.Notice != "" {
{ data.Notice }
} else { } if data.Error != "" {
{ data.Error }
} else { }
if !data.HasAuth {

Strava authorization is required before the first sync.

Authorize Strava } else {
}
}