summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/frontend/official-climb-profile.ts61
-rw-r--r--web/frontend/types.d.ts2
-rw-r--r--web/static/style.css4
3 files changed, 64 insertions, 3 deletions
diff --git a/web/frontend/official-climb-profile.ts b/web/frontend/official-climb-profile.ts
index b60a1fa..bc851f5 100644
--- a/web/frontend/official-climb-profile.ts
+++ b/web/frontend/official-climb-profile.ts
@@ -38,6 +38,8 @@ export class OfficialClimbProfileController {
"9-plus": resolveColor("--color-profile-9-plus"),
plotSurface: resolveColor("--color-plot-surface"),
grid: resolveColor("--color-plot-grid"),
+ xGrid: resolveColor("--color-profile-grid-x"),
+ yGrid: resolveColor("--color-profile-grid-y"),
subtle: resolveColor("--color-subtle"),
accent: resolveColor("--color-accent"),
};
@@ -65,7 +67,7 @@ export class OfficialClimbProfileController {
profileCanvas.width = Math.floor(rect.width * ratio);
profileCanvas.height = Math.floor(rect.height * ratio);
context.setTransform(ratio, 0, 0, ratio, 0, 0);
- const plot = { left: 10, right: rect.width - 10, top: 16, bottom: rect.height - 34 };
+ const plot = { left: 10, right: Math.max(11, rect.width - 44), top: 16, bottom: rect.height - 34 };
const climbLengthM = (points[endIndex].distanceKm - points[startIndex].distanceKm) * 1000;
const sections = officialProfileSections(points, startIndex, endIndex, displayStepForLength(climbLengthM));
if (sections.length === 0) return;
@@ -87,8 +89,40 @@ export class OfficialClimbProfileController {
context.clearRect(0, 0, rect.width, rect.height);
context.fillStyle = colors.plotSurface;
context.fillRect(0, 0, rect.width, rect.height);
- context.strokeStyle = colors.grid;
+ context.font = "9px system-ui, sans-serif";
context.lineWidth = 1;
+ context.setLineDash([4, 4]);
+ context.strokeStyle = colors.yGrid;
+ const yGridElevations: number[] = [];
+ for (let elevationM = Math.ceil(minElevation / 100) * 100; elevationM <= maxElevation; elevationM += 100) {
+ yGridElevations.push(elevationM);
+ }
+ context.save();
+ context.beginPath();
+ context.moveTo(plot.left, plot.bottom);
+ context.lineTo(plot.left, yForElevation(sections[0].startElevation));
+ for (const section of sections) {
+ context.lineTo(xForDistance(section.endDistanceKm), yForElevation(section.endElevation));
+ }
+ context.lineTo(plot.right, plot.bottom);
+ context.closePath();
+ context.clip();
+ for (const elevationM of yGridElevations) {
+ const y = yForElevation(elevationM);
+ context.beginPath();
+ context.moveTo(plot.left, y);
+ context.lineTo(plot.right, y);
+ context.stroke();
+ }
+ context.restore();
+ context.fillStyle = colors.yGrid;
+ context.textAlign = "left";
+ context.textBaseline = "middle";
+ for (const elevationM of yGridElevations) {
+ context.fillText(`${elevationM} m`, plot.right + 6, yForElevation(elevationM));
+ }
+ context.setLineDash([]);
+ context.strokeStyle = colors.grid;
context.beginPath();
context.moveTo(plot.left, plot.bottom);
context.lineTo(plot.right, plot.bottom);
@@ -113,6 +147,29 @@ export class OfficialClimbProfileController {
context.lineWidth = 2.5;
context.stroke();
}
+ context.save();
+ context.beginPath();
+ context.moveTo(plot.left, plot.bottom);
+ context.lineTo(plot.left, yForElevation(sections[0].startElevation));
+ for (const section of sections) {
+ context.lineTo(xForDistance(section.endDistanceKm), yForElevation(section.endElevation));
+ }
+ context.lineTo(plot.right, plot.bottom);
+ context.closePath();
+ context.clip();
+ context.strokeStyle = colors.xGrid;
+ context.lineWidth = 1;
+ context.setLineDash([4, 4]);
+ for (let sectionIndex = 0; sectionIndex <= sections.length; sectionIndex++) {
+ const distanceKm = sectionIndex === sections.length ? maxDistance : sections[sectionIndex].startDistanceKm;
+ const x = xForDistance(distanceKm);
+ context.beginPath();
+ context.moveTo(x, plot.top);
+ context.lineTo(x, plot.bottom);
+ context.stroke();
+ }
+ context.restore();
+ context.setLineDash([]);
context.font = "9px system-ui, sans-serif";
context.fillStyle = colors.subtle;
context.textBaseline = "bottom";
diff --git a/web/frontend/types.d.ts b/web/frontend/types.d.ts
index c53b7e5..0b59dac 100644
--- a/web/frontend/types.d.ts
+++ b/web/frontend/types.d.ts
@@ -80,6 +80,8 @@ export interface OfficialProfileColors {
"9-plus": string;
plotSurface: string;
grid: string;
+ xGrid: string;
+ yGrid: string;
subtle: string;
accent: string;
}
diff --git a/web/static/style.css b/web/static/style.css
index 8c1d39f..8c4f20c 100644
--- a/web/static/style.css
+++ b/web/static/style.css
@@ -31,6 +31,8 @@
--color-plot-surface: oklch(from var(--palette-paper) calc(l + 0.03) calc(c * 0.35) h);
--color-plot-surface-overlay: oklch(from var(--color-plot-surface) l c h / 90%);
--color-plot-grid: var(--color-border-subtle);
+ --color-profile-grid-x: var(--palette-white);
+ --color-profile-grid-y: oklch(0% 0 0deg);
--color-accent-fill: oklch(from var(--palette-coral) l c h / 12%);
--color-profile-fill: oklch(from var(--palette-leaf) l c h / 16%);
--color-profile-line: var(--color-forest);
@@ -116,7 +118,7 @@ th { color: var(--color-subtle); font-size: .78rem; letter-spacing: .08em; text-
.official-climb-chevron { color: var(--color-accent); font-size: 1.5rem; font-weight: 500; line-height: 1; }
.official-climb-card[open] .official-climb-chevron { transform: rotate(180deg); }
.official-climb-card-body { padding: .9rem 1rem 1rem; border-top: 1px solid var(--color-border-subtle); background: var(--color-surface); }
-.official-climb-profile { display: block; width: 100%; height: 10rem; margin-top: .75rem; border-radius: .55rem; background: var(--color-plot-surface); outline: none; }
+.official-climb-profile { display: block; width: 100%; height: auto; aspect-ratio: 3 / 1; margin-top: .75rem; border-radius: .55rem; background: var(--color-plot-surface); outline: none; }
.official-climb-profile:focus-visible { box-shadow: 0 0 0 .2rem var(--color-focus); }
.official-climb-profile-legend { display: flex; flex-wrap: wrap; gap: .45rem .8rem; margin-top: .65rem; color: var(--color-muted); font-size: .72rem; }
.official-climb-profile-legend > span { display: inline-flex; align-items: center; gap: .3rem; }