summaryrefslogtreecommitdiff
path: root/web/templates.templ
diff options
context:
space:
mode:
Diffstat (limited to 'web/templates.templ')
-rw-r--r--web/templates.templ120
1 files changed, 73 insertions, 47 deletions
diff --git a/web/templates.templ b/web/templates.templ
index 9ebeedb..c640171 100644
--- a/web/templates.templ
+++ b/web/templates.templ
@@ -71,60 +71,85 @@ templ RideDetailContent(data RideDetailView) {
<div class="error">{ data.ActionError }</div>
}
<div class="ride-detail-grid">
- <div class="ride-detail-sidebar">
- <section class="panel summary-panel">
- <dl>
- <div><dt>Distance</dt><dd>{ formatDistance(data.Ride.DistanceM) }</dd></div>
- <div><dt>Moving time</dt><dd>{ formatDuration(data.Ride.MovingTimeS) }</dd></div>
- <div><dt>Elevation</dt><dd>{ formatElevation(data.Ride.TotalElevationGainM) }</dd></div>
- </dl>
- </section>
- if data.HasRoute {
+ <section class="panel summary-panel">
+ <dl>
+ <div><dt>Distance</dt><dd>{ formatDistance(data.Ride.DistanceM) }</dd></div>
+ <div><dt>Moving time</dt><dd>{ formatDuration(data.Ride.MovingTimeS) }</dd></div>
+ <div><dt>Elevation</dt><dd>{ formatElevation(data.Ride.TotalElevationGainM) }</dd></div>
+ </dl>
+ </section>
+ if data.HasRoute {
+ if len(data.Profile.OfficialClimbs) > 0 {
+ <section class="panel official-climbs-panel">
+ <div class="section-heading">
+ <p class="eyebrow">Official climbs</p>
+ </div>
+ <div class="official-climb-list">
+ for _, climb := range data.Profile.OfficialClimbs {
+ <details class="official-climb-card" data-official-climb-card>
+ <summary>
+ <span class="official-climb-card-title"><span class="climb-number">Climb { formatClimbNumber(climb.Index) }</span><strong>{ climb.OfficialName }</strong></span>
+ <dl class="official-climb-summary-stats">
+ <div><dt>Length</dt><dd>{ formatProfileDistance(climb.DistanceKm) }</dd></div>
+ <div><dt>Cotacol</dt><dd>{ formatCotacol(climb.Cotacol) }</dd></div>
+ <div><dt>Avg slope</dt><dd>{ formatSlope(climb.SlopePercent) }</dd></div>
+ </dl>
+ <span class="official-climb-chevron" aria-hidden="true">⌄</span>
+ </summary>
+ <div class="official-climb-card-body">
+ <canvas class="official-climb-profile" data-official-profile data-profile-start={ formatRouteIndex(climb.StartIndex) } data-profile-end={ formatRouteIndex(climb.EndIndex) } tabindex="0" role="img" aria-label={ officialProfileLabel(climb.OfficialName) }></canvas>
+ <div class="official-climb-profile-legend" role="list" aria-label="Average slope color scale">
+ <span role="listitem"><i class="profile-band-swatch profile-band-downhill" aria-hidden="true"></i>Downhill</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-0-3" aria-hidden="true"></i>0–3%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-3-6" aria-hidden="true"></i>3–6%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-6-9" aria-hidden="true"></i>6–9%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-9-12" aria-hidden="true"></i>9–12%</span>
+ <span role="listitem"><i class="profile-band-swatch profile-band-12-plus" aria-hidden="true"></i>12%+</span>
+ </div>
+ </div>
+ </details>
+ }
+ </div>
+ </section>
+ }
+ <div class={ rideDetailColumnsClass(len(data.Profile.UnmatchedClimbs)) }>
+ if len(data.Profile.UnmatchedClimbs) > 0 {
<section class="panel climbs-panel">
- if len(data.Profile.Climbs) > 0 {
- <nav class="climb-navigation" aria-label="Climb navigation">
- <button class="button secondary" type="button" data-climb-previous aria-label="Previous climb">←</button>
- <output data-climb-position>Climb 1 of { formatClimbNumber(len(data.Profile.Climbs) - 1) }</output>
- <button class="button secondary" type="button" data-climb-next aria-label="Next climb">→</button>
- </nav>
- }
- if len(data.Profile.Climbs) == 0 {
- <p class="empty">No climbs detected in this ride.</p>
- }
- if len(data.Profile.Climbs) > 0 {
- <div class="climb-list" data-climb-list>
- for index, climb := range data.Profile.Climbs {
- <article class="climb-item" data-climb-item data-climb-index={ formatRouteIndex(index) }>
+ <div class="section-heading">
+ <div><p class="eyebrow">Next steps</p><h2>Climbs to match</h2></div>
+ <span class="section-count">{ formatClimbCount(len(data.Profile.UnmatchedClimbs)) }</span>
+ </div>
+ <nav class="climb-navigation" aria-label="Climb navigation">
+ <button class="button secondary" type="button" data-climb-previous aria-label="Previous climb">←</button>
+ <output data-climb-position>Climb 1 of { formatClimbCount(len(data.Profile.UnmatchedClimbs)) }</output>
+ <button class="button secondary" type="button" data-climb-next aria-label="Next climb">→</button>
+ </nav>
+ <div class="climb-list" data-climb-list>
+ for _, climb := range data.Profile.UnmatchedClimbs {
+ <article class="climb-item todo-item" data-climb-item data-climb-index={ formatRouteIndex(climb.Index) }>
<div class="climb-item-heading">
- <strong><span class="climb-number">Climb { formatClimbNumber(index) }</span>{ climb.Name }</strong>
+ <strong><span class="todo-check" aria-hidden="true"></span><span class="climb-number">Climb { formatClimbNumber(climb.Index) }</span>{ climb.Name }</strong>
<span class="climb-range" data-climb-summary>{ formatProfileDistance(climb.StartKm) }–{ formatProfileDistance(climb.EndKm) }</span>
</div>
<p class="climb-metrics" data-climb-metrics>{ climb.Category } · { formatProfileDistance(climb.DistanceKm) } at { formatSlope(climb.SlopePercent) } · Cotacol { formatCotacol(climb.Cotacol) }</p>
- if climb.OfficialClimbID > 0 {
- <p class="climb-status matched">Official climb matched: { climb.OfficialName }</p>
- } else {
- <p class="climb-status">No official climb matched yet.</p>
- <form class="official-climb-form" data-official-climb-form method="post" action={ templ.URL(officialClimbCreateURL(data.Ride.ID)) }>
- <label>Name<input type="text" name="name" required placeholder="Official climb name"/></label>
- <input type="hidden" name="start_index" value={ formatRouteIndex(climb.StartIndex) } data-boundary-input="start"/>
- <input type="hidden" name="end_index" value={ formatRouteIndex(climb.EndIndex) } data-boundary-input="end"/>
- <div class="boundary-controls">
- <div><strong>Start</strong><div><button class="button secondary" type="button" data-boundary-button="start" data-boundary-source="profile">Select on profile</button><button class="button secondary" type="button" data-boundary-button="start" data-boundary-source="map">Select on map</button></div><output data-boundary-output="start">{ formatProfileDistance(climb.StartKm) }</output></div>
- <div><strong>End</strong><div><button class="button secondary" type="button" data-boundary-button="end" data-boundary-source="profile">Select on profile</button><button class="button secondary" type="button" data-boundary-button="end" data-boundary-source="map">Select on map</button></div><output data-boundary-output="end">{ formatProfileDistance(climb.EndKm) }</output></div>
- </div>
- <output class="boundary-preview" data-boundary-preview>Preview the corrected climb boundaries.</output>
- <button class="button" type="submit">Save official climb</button>
- </form>
- }
+ <p class="climb-status">Waiting for an official climb match.</p>
+ <form class="official-climb-form" data-official-climb-form method="post" action={ templ.URL(officialClimbCreateURL(data.Ride.ID)) }>
+ <label>Name<input type="text" name="name" required placeholder="Official climb name"/></label>
+ <input type="hidden" name="start_index" value={ formatRouteIndex(climb.StartIndex) } data-boundary-input="start"/>
+ <input type="hidden" name="end_index" value={ formatRouteIndex(climb.EndIndex) } data-boundary-input="end"/>
+ <div class="boundary-controls">
+ <div><strong>Start</strong><div><button class="button secondary" type="button" data-boundary-button="start" data-boundary-source="profile">Select on profile</button><button class="button secondary" type="button" data-boundary-button="start" data-boundary-source="map">Select on map</button></div><output data-boundary-output="start">{ formatProfileDistance(climb.StartKm) }</output></div>
+ <div><strong>End</strong><div><button class="button secondary" type="button" data-boundary-button="end" data-boundary-source="profile">Select on profile</button><button class="button secondary" type="button" data-boundary-button="end" data-boundary-source="map">Select on map</button></div><output data-boundary-output="end">{ formatProfileDistance(climb.EndKm) }</output></div>
+ </div>
+ <output class="boundary-preview" data-boundary-preview>Preview the corrected climb boundaries.</output>
+ <button class="button" type="submit">Save official climb</button>
+ </form>
</article>
- }
- </div>
- }
+ }
+ </div>
</section>
- }
- </div>
- if data.HasRoute {
- <div class="ride-detail-main">
+ }
+ <div class="ride-detail-main">
<section class="panel profile-panel">
<div class="profile-heading">
<h2>Elevation profile</h2>
@@ -138,6 +163,7 @@ templ RideDetailContent(data RideDetailView) {
<section class="panel map-panel">
<div id="ride-map" class="ride-map" role="img" aria-label="Recorded ride route map"></div>
</section>
+ </div>
</div>
}
</div>