{% extends 'base.html' %} {% block title %}{{ activity.title }} · Stride{% endblock %} {% block breadcrumb %}ACTIVITY / {{ activity.sport|upper }}{% endblock %} {% block head %}{% endblock %} {% macro metric(label, value, unit='', note='') %}
{{ label }}{{ value }}{{ unit }}{% if note %}

{{ note }}

{% endif %}
{% endmacro %} {% block content %} {% if not public_view %}← {{ 'Your activities' if owner_view else activity.owner.display_name }}{% endif %}
{{ activity.sport|upper }} · {{ activity.started_at|localdate(activity.owner.timezone, '%A, %d %B %Y') }}

{{ activity.title }}

By {% if public_view %}{{ activity.owner.display_name }}{% else %}{{ activity.owner.display_name }}{% endif %}·{{ activity.started_at|localdate(activity.owner.timezone, '%H:%M') }}·{{ '{:,}'.format(analysis.summary.samples) }} recorded samples

{{ 'Shared via link' if public_view else activity.visibility|title }}{% if owner_view %}{% endif %}
{% if owner_view %}

{% endif %} {% if efforts %}

Best efforts on this run

Estimated · elapsed time
{% for effort in efforts %}
{{ effort.label }} {% if effort.pr %}★ PR{% endif %}{{ effort.seconds|duration }}{{ '%.2f'|format(effort.start_m/1000) }}–{{ '%.2f'|format(effort.end_m/1000) }} km into the run
{% endfor %}

Based on recorded distance and timestamps, with interpolated boundaries. Stops count toward elapsed time. Missing or reset data breaks an effort. PR means a record set at the time of this run, including your first recorded effort.

{% endif %}
TIMER TIME
{{ activity.duration_s|duration }}

Elapsed {{ activity.metrics.get('total_elapsed_time')|duration if activity.metrics.get('total_elapsed_time') else '—' }}

AVERAGE PACE
{{ activity.pace }}/km

GAP estimate {{ pace_text(analysis.summary.gap) }}/km

AVERAGE HEART RATE
{{ activity.avg_hr|int if activity.avg_hr else '—' }}bpm

Peak {{ analysis.summary.max_hr|int if analysis.summary.max_hr else '—' }} bpm

FOLLOW YOUR FOOTSTEPS

Your run, in colour.

Loading your route…

Slower
FasterS Start · F Finish · Grey = no measurement

A CLOSER LOOK

The numbers behind the run.

Hover over a chart to locate that part of the route.

{{ metric('GRADE-ADJUSTED PACE', pace_text(analysis.summary.gap), '/km', 'Model estimate · %.0f%% distance coverage'|format(analysis.summary.gap_coverage)) }} {{ metric('ELEVATION GAIN', activity.elevation_m|int, 'm', 'Descent %s m'|format(activity.metrics.get('total_descent')|int if activity.metrics.get('total_descent') is not none else '—')) }} {{ metric('AVERAGE CADENCE', '%.0f'|format(analysis.summary.cadence) if analysis.summary.cadence else '—', 'spm', 'Both feet · sampled average') }} {{ metric('AVERAGE POWER', activity.metrics.get('avg_power')|int if activity.metrics.get('avg_power') is not none else '—', 'W', 'Recorded running power') }} {{ metric('ENERGY', activity.metrics.get('total_calories')|int if activity.metrics.get('total_calories') is not none else '—', 'kcal', 'Watch estimate') }} {{ metric('TRAINING EFFECT', activity.metrics.get('total_training_effect') if activity.metrics.get('total_training_effect') is not none else '—', '', 'Aerobic · watch estimate') }}

Pace & grade-adjusted pace

min/km · lower is faster

Heart rate

Recorded · bpm

Elevation & gradient

metres / % grade

Cadence & step length

steps/min / metres

Running power

Recorded · watts

Running dynamics

vertical movement / contact time
HILLS CHANGE THE EFFORT

Heart rate by terrain

Measured HR · not adjusted

See how your recorded heart rate and pace vary uphill, downhill and on the flat. Heart rate responds with a delay, so this is context rather than a correction.

{% for t in analysis.terrain %}
{{ t.label }}{{ '%.0f'|format(t.heart_rate) if t.heart_rate else '—' }}bpm
{{ '%.2f'|format(t.distance/1000) }} km · {{ pace_text(t.pace) }}/km
{% endfor %}

Laps & splits

{{ analysis.laps|length }} recorded laps
{% for lap in analysis.laps %}{% else %}{% endfor %}
LAPDISTANCETIMEPACEGAP EST.HR
{{ loop.index }}{{ '%.2f'|format(lap.distance / 1000) }} km{{ lap.duration|duration }}{{ pace_text(lap.pace) }}{{ pace_text(lap.gap) }}{{ lap.heart_rate|int if lap.heart_rate else '—' }}
No laps recorded.
{% if owner_view %}
YOUR RUN, YOUR CHOICE

Sharing settings

Accepted friends see the full route and measurements of shared activities. Public link access is separate: anyone with a copied link can view this run, including its full route and measurements, until you disable the link.

{% endif %}
About these numbers Recorded data vs. estimates +

GAP is an estimate, not a Garmin or Strava value. We use the Minetti running energy-cost model to estimate an equivalent flat pace from the recorded slope. Elevation uses a five-sample median and a roughly 60 m distance window. Grades outside ±30%, missing data, and recording gaps over 10 seconds are excluded. GAP is only calculated for running.

Average GAP divides time by equivalent flat distance over eligible moving samples. Coverage indicates how much recorded distance contributed; lap GAP uses the same method. Stops and missing data can make its sample coverage differ from timer pace. Wind, surface, heat, fatigue and individual running economy are not modelled.

Heart rate stays measured. A universal grade correction would imply precision we don't have. Terrain summaries show time-weighted recorded HR, without trying to remove the effect of hills. Cadence is converted from running cycles/min to steps/min. Missing measurements are shown as unavailable, never zero-filled.

Map colours use this activity’s 10th–90th percentile range, with extremes saturated. Direction arrows follow recording order; they are not navigation instructions. Routes are simplified for display, preserving segment endpoints and GPS gaps. Map measurements and chart traces are sampled for display; computations use all available records.

{% endblock %} {% block scripts %}{% endblock %}