summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--web/frontend/ride-detail-canvas.ts19
-rw-r--r--web/frontend/ride-detail-logic.test.ts3
-rw-r--r--web/frontend/ride-detail-logic.ts5
-rw-r--r--web/frontend/ride-detail.ts2
-rw-r--r--web/frontend/types.d.ts2
-rw-r--r--web/static/style.css4
6 files changed, 27 insertions, 8 deletions
diff --git a/web/frontend/ride-detail-canvas.ts b/web/frontend/ride-detail-canvas.ts
index c61afee..23f9621 100644
--- a/web/frontend/ride-detail-canvas.ts
+++ b/web/frontend/ride-detail-canvas.ts
@@ -137,11 +137,20 @@ export class RideProfileCanvas {
const endX = clamp(this.xForDistance(metrics.end.distanceKm), plot.left, plot.right);
this.context.fillStyle = climbIndex === this.focusedClimbIndex ? this.colors.climbFocusFill : this.colors.accentFill;
this.context.fillRect(startX, plot.top, Math.max(endX - startX, 1), plot.bottom - plot.top);
- const label = formatClimbLabel(climb.name, metrics.category, metrics.cotacol);
+ const label = formatClimbLabel(climb.name, metrics.category, metrics.cotacol, climb.officialClimbId !== undefined);
this.context.fillStyle = this.colors.climbLabel;
this.context.textAlign = "center";
- this.context.textBaseline = "top";
- this.context.fillText(label, clamp((startX + endX) / 2, plot.left + 24, plot.right - 24), plot.top + 4);
+ this.context.textBaseline = "middle";
+ const labelWidth = this.context.measureText(label).width;
+ const labelX = clamp((startX + endX) / 2, plot.left + 8, plot.right - 8);
+ const labelAngle = -Math.PI / 3;
+ const labelHeight = Math.abs(Math.sin(labelAngle)) * labelWidth + Math.abs(Math.cos(labelAngle)) * 14;
+ const labelY = clamp(plot.top + labelHeight / 2 + 4, plot.top + labelHeight / 2, plot.bottom - labelHeight / 2);
+ this.context.save();
+ this.context.translate(labelX, labelY);
+ this.context.rotate(labelAngle);
+ this.context.fillText(label, 0, 0);
+ this.context.restore();
}
for (const crossing of this.profile.crossings || []) {
@@ -161,7 +170,7 @@ export class RideProfileCanvas {
for (const point of this.points) this.context.lineTo(this.xForDistance(point.distanceKm), this.yForElevation(point.elevationM));
this.context.lineTo(this.xForDistance(this.points[this.points.length - 1].distanceKm), plot.bottom);
this.context.closePath();
- this.context.fillStyle = this.colors.accentFill;
+ this.context.fillStyle = this.colors.profileFill;
this.context.fill();
this.context.beginPath();
for (let index = 0; index < this.points.length; index++) {
@@ -169,7 +178,7 @@ export class RideProfileCanvas {
if (index === 0) this.context.moveTo(this.xForDistance(point.distanceKm), this.yForElevation(point.elevationM));
else this.context.lineTo(this.xForDistance(point.distanceKm), this.yForElevation(point.elevationM));
}
- this.context.strokeStyle = this.colors.accent;
+ this.context.strokeStyle = this.colors.profileLine;
this.context.lineWidth = 2.5;
this.context.stroke();
for (const crossing of this.profile.crossings || []) {
diff --git a/web/frontend/ride-detail-logic.test.ts b/web/frontend/ride-detail-logic.test.ts
index b848f7e..6f27191 100644
--- a/web/frontend/ride-detail-logic.test.ts
+++ b/web/frontend/ride-detail-logic.test.ts
@@ -40,9 +40,10 @@ test("calculates climb metrics with Cotacol", () => {
assert.equal(metrics.category, categoryForCotacol(metrics.cotacol));
});
-test("formats profile climb labels with Cotacol", () => {
+test("formats profile climb labels with category", () => {
assert.equal(formatClimbLabel("", "Cat 4", 45.9), "Cat 4 45.9");
assert.equal(formatClimbLabel("Col de Test", "Cat 4", 45.9), "Col de Test");
+ assert.equal(formatClimbLabel("Ventoux", "HC", 600, true), "Ventoux (HC)");
});
test("formats profile labels consistently", () => {
diff --git a/web/frontend/ride-detail-logic.ts b/web/frontend/ride-detail-logic.ts
index a2ba80a..f8eb040 100644
--- a/web/frontend/ride-detail-logic.ts
+++ b/web/frontend/ride-detail-logic.ts
@@ -28,7 +28,10 @@ export const categoryForCotacol = (cotacol: number): string => {
return "HC";
};
-export const formatClimbLabel = (name: string, category: string, cotacol: number): string => name || `${category} ${cotacol.toFixed(1)}`;
+export const formatClimbLabel = (name: string, category: string, cotacol: number, officialClimb = false): string => {
+ if (name) return officialClimb ? `${name} (${category})` : name;
+ return `${category} ${cotacol.toFixed(1)}`;
+};
export const elevationAtDistance = (points: ProfilePoint[], index: number, distanceM: number): number => {
if (index + 1 >= points.length) return points[index].elevationM;
diff --git a/web/frontend/ride-detail.ts b/web/frontend/ride-detail.ts
index fb75c19..d59cbe9 100644
--- a/web/frontend/ride-detail.ts
+++ b/web/frontend/ride-detail.ts
@@ -33,6 +33,8 @@ export const mountRideDetail = (): void => {
plotSurfaceOverlay: resolveColor("--color-plot-surface-overlay"),
grid: resolveColor("--color-plot-grid"),
accentFill: resolveColor("--color-accent-fill"),
+ profileFill: resolveColor("--color-profile-fill"),
+ profileLine: resolveColor("--color-profile-line"),
climbLabel: resolveColor("--color-climb-label"),
crossing: resolveColor("--color-crossing"),
crossingLabel: resolveColor("--color-crossing-label"),
diff --git a/web/frontend/types.d.ts b/web/frontend/types.d.ts
index 6fa9976..a5e83b8 100644
--- a/web/frontend/types.d.ts
+++ b/web/frontend/types.d.ts
@@ -92,6 +92,8 @@ export interface RideDetailColors {
forest: string;
plotSurfaceOverlay: string;
accentFill: string;
+ profileFill: string;
+ profileLine: string;
climbLabel: string;
crossing: string;
crossingLabel: string;
diff --git a/web/static/style.css b/web/static/style.css
index ab6c1ed..e32525d 100644
--- a/web/static/style.css
+++ b/web/static/style.css
@@ -32,7 +32,9 @@
--color-plot-surface-overlay: oklch(from var(--color-plot-surface) l c h / 90%);
--color-plot-grid: var(--color-border-subtle);
--color-accent-fill: oklch(from var(--palette-coral) l c h / 12%);
- --color-climb-label: oklch(from var(--palette-coral) calc(l - 0.21) calc(c * 0.72) h);
+ --color-profile-fill: oklch(from var(--palette-leaf) l c h / 16%);
+ --color-profile-line: var(--color-forest);
+ --color-climb-label: oklch(40% 0.12 35deg);
--color-crossing: oklch(from var(--palette-leaf) l c h / 60%);
--color-crossing-label: var(--color-notice-text);
--color-hover-line: oklch(from var(--palette-forest) l c h / 60%);