Skip to content

Commit f031f7d

Browse files
ISSUE #1610: Radar chart indicating athlete profile
1 parent f6a2cb3 commit f031f7d

File tree

15 files changed

+356
-112
lines changed

15 files changed

+356
-112
lines changed

config/reference.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,17 +1098,6 @@
10981098
* doctrine?: DoctrineConfig,
10991099
* doctrine_migrations?: DoctrineMigrationsConfig,
11001100
* twig?: TwigConfig,
1101-
* "when@dev"?: array{
1102-
* imports?: ImportsConfig,
1103-
* parameters?: ParametersConfig,
1104-
* services?: ServicesConfig,
1105-
* framework?: FrameworkConfig,
1106-
* flysystem?: FlysystemConfig,
1107-
* monolog?: MonologConfig,
1108-
* doctrine?: DoctrineConfig,
1109-
* doctrine_migrations?: DoctrineMigrationsConfig,
1110-
* twig?: TwigConfig,
1111-
* },
11121101
* "when@prod"?: array{
11131102
* imports?: ImportsConfig,
11141103
* parameters?: ParametersConfig,
@@ -1211,7 +1200,6 @@ public static function config(array $config): array
12111200
* deprecated?: array{package:string, version:string, message?:string},
12121201
* }
12131202
* @psalm-type RoutesConfig = array{
1214-
* "when@dev"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
12151203
* "when@prod"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
12161204
* "when@test"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
12171205
* ...<string, RouteConfig|ImportConfig|AliasConfig>

public/css/tailwind.output.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,9 @@
10961096
.me-3 {
10971097
margin-inline-end: calc(var(--spacing) * 3);
10981098
}
1099+
.-mt-0\.5 {
1100+
margin-top: calc(var(--spacing) * -0.5);
1101+
}
10991102
.mt-1 {
11001103
margin-top: calc(var(--spacing) * 1);
11011104
}
@@ -2239,6 +2242,9 @@
22392242
border-color: var(--color-gray-200);
22402243
}
22412244
}
2245+
.self-start {
2246+
align-self: flex-start;
2247+
}
22422248
.truncate {
22432249
overflow: hidden;
22442250
text-overflow: ellipsis;
@@ -3228,6 +3234,10 @@
32283234
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
32293235
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
32303236
}
3237+
.shadow-md {
3238+
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
3239+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
3240+
}
32313241
.shadow-sm {
32323242
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
32333243
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -3399,6 +3409,16 @@
33993409
color: var(--color-strava-orange);
34003410
}
34013411
}
3412+
.group-\[\.fullscreen-is-enabled\]\:h-full {
3413+
&:is(:where(.group):is(.fullscreen-is-enabled) *) {
3414+
height: 100%;
3415+
}
3416+
}
3417+
.group-\[\.fullscreen-is-enabled\]\:grow {
3418+
&:is(:where(.group):is(.fullscreen-is-enabled) *) {
3419+
flex-grow: 1;
3420+
}
3421+
}
34023422
.group-\[\.sidebar-is-collapsed\]\:block {
34033423
&:is(:where(.group):is(.sidebar-is-collapsed) *) {
34043424
display: block;
@@ -4378,6 +4398,11 @@
43784398
background-color: var(--color-gray-100);
43794399
}
43804400
}
4401+
.\[\&\.full-screen-enabled\]\:hidden {
4402+
&.full-screen-enabled {
4403+
display: none;
4404+
}
4405+
}
43814406
.\[\&\.sidebar-is-collapsed\]\:w-32 {
43824407
&.sidebar-is-collapsed {
43834408
width: calc(var(--spacing) * 32);

public/js/app.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,37 @@ document.addEventListener('pageWasLoaded', (e) => {
6060
// Open modal.
6161
modalManager.open(e.detail.modalId);
6262
}
63+
64+
document.querySelectorAll('[data-fullscreen-trigger]').forEach((el) => {
65+
el.addEventListener('click', (e) => {
66+
e.preventDefault();
67+
68+
if (document.fullscreenElement) {
69+
return;
70+
}
71+
72+
const fullScreenContent = el.closest('[data-fullscreen-content]');
73+
fullScreenContent.requestFullscreen().then(() => {
74+
chartManager.resizeAll();
75+
});
76+
77+
fullScreenContent.addEventListener('fullscreenchange', () => {
78+
el.classList.toggle(
79+
'hidden',
80+
Boolean(document.fullscreenElement)
81+
);
82+
fullScreenContent.classList.toggle(
83+
'fullscreen-is-enabled',
84+
Boolean(document.fullscreenElement)
85+
);
86+
fullScreenContent.classList.toggle(
87+
'group',
88+
Boolean(document.fullscreenElement)
89+
);
90+
});
91+
92+
});
93+
});
6394
});
6495
document.addEventListener('pageWasLoaded.heatmap', () => {
6596
const $heatmapWrapper = document.querySelector('.heatmap-wrapper');

public/manifest.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"id": "http://localhost:8080/",
3-
"name": "Statistics for Strava | Malte-Christian Rösch",
2+
"id": "[APP_HOST]",
3+
"name": "[APP_NAME]",
44
"short_name": "Statistics for Strava",
55
"description": "Statistics for Strava is a self-hosted web app designed to provide you with better stats.",
66
"categories": [
@@ -9,7 +9,7 @@
99
"utilities"
1010
],
1111
"start_url": "/",
12-
"scope": "http://localhost:8080/",
12+
"scope": "[APP_HOST]",
1313
"display": "standalone",
1414
"display_override": [
1515
"fullscreen",
@@ -20,49 +20,49 @@
2020
"background_color": "#f9fafb",
2121
"icons": [
2222
{
23-
"src": "/assets/images/manifest/icon-192.png",
23+
"src": "[APP_BASE_PATH]/assets/images/manifest/icon-192.png",
2424
"sizes": "192x192",
2525
"type": "image/png"
2626
},
2727
{
28-
"src": "/assets/images/manifest/icon-192.maskable.png",
28+
"src": "[APP_BASE_PATH]/assets/images/manifest/icon-192.maskable.png",
2929
"sizes": "192x192",
3030
"type": "image/png",
3131
"purpose": "maskable"
3232
},
3333
{
34-
"src": "/assets/images/manifest/icon-512.png",
34+
"src": "[APP_BASE_PATH]/assets/images/manifest/icon-512.png",
3535
"sizes": "512x512",
3636
"type": "image/png"
3737
},
3838
{
39-
"src": "/assets/images/manifest/icon-512.maskable.png",
39+
"src": "[APP_BASE_PATH]/assets/images/manifest/icon-512.maskable.png",
4040
"sizes": "512x512",
4141
"type": "image/png",
4242
"purpose": "maskable"
4343
},
4444
{
45-
"src": "/assets/images/manifest/icon-512.png",
45+
"src": "[APP_BASE_PATH]/assets/images/manifest/icon-512.png",
4646
"sizes": "any",
4747
"type": "image/png"
4848
},
4949
{
50-
"src": "/assets/images/manifest/icon-512.maskable.png",
50+
"src": "[APP_BASE_PATH]/assets/images/manifest/icon-512.maskable.png",
5151
"sizes": "any",
5252
"type": "image/png",
5353
"purpose": "maskable"
5454
}
5555
],
5656
"screenshots": [
5757
{
58-
"src": "/assets/images/manifest/screenshots/dashboard.jpeg",
58+
"src": "[APP_BASE_PATH]/assets/images/manifest/screenshots/dashboard.jpeg",
5959
"sizes": "750x1600",
6060
"type": "image/jpeg",
6161
"form_factor": "narrow",
6262
"label": "Dashboard"
6363
},
6464
{
65-
"src": "/assets/images/manifest/screenshots/heatmap.jpeg",
65+
"src": "[APP_BASE_PATH]/assets/images/manifest/screenshots/heatmap.jpeg",
6666
"sizes": "750x1600",
6767
"type": "image/jpeg",
6868
"form_factor": "narrow",

src/Domain/Dashboard/Widget/AthleteProfile/AthleteProfileWidget.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,35 @@ public function guardValidConfiguration(WidgetConfiguration $configuration): voi
2828

2929
public function render(SerializableDateTime $now, WidgetConfiguration $configuration): string
3030
{
31+
// 1. VOLUME - “How much do you train?”
32+
// weekly_hours = total_training_hours_in_period / (total_days_in_period / 7)
33+
// score = min(100, weekly_hours / 10 * 100)
34+
// 10 h/week = very active amateur (works across sports)
35+
36+
// 2. CONSISTENCY - “How often do you train?”
37+
// consistency = active_days_in_period / total_days_in_period
38+
// score = min(100, consistency / 0.7 * 100)
39+
// 5 days/week ≈ excellent consistency
40+
41+
// 3. INTENSITY - “How hard do you train?”
42+
// intensity = activities_with_high_effort / total_activities
43+
// score = min(100, intensity / 0.25 * 100)
44+
// 0.25 = realistic upper bound for sustainable hard training
45+
46+
// 4. DURATION - “How long are your sessions?”
47+
// score = min(100, median_duration_minutes / 90 * 100)
48+
// Median > 90 min = endurance-leaning athlete
49+
50+
// 5. DENSITY - “How packed is your training?”
51+
// density = training_hours / active_days
52+
// score = min(100, hours_per_active_day / 2 * 100)
53+
// 2h per training day = high density
54+
55+
// 6. VARIETY - “How diverse is your training?”
56+
// dominant_sport_fraction = max(activities_per_sport_type) / total_activities
57+
// variety = 1 - dominant_sport_fraction
58+
// score = min(100, variety / 0.5 * 100)
59+
// 0.5 is the anchor that defines “max meaningful variety”
3160
return $this->twig->load('html/dashboard/widget/widget--athlete-profile.html.twig')->render([
3261
'athleteProfileChart' => Json::encode(
3362
AthleteProfileChart::create($this->translator)->build()

src/Infrastructure/Twig/SvgsTwigExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public function svg(string $name, ?string $classes = null): string
1919
'delta' => '<svg class="w-5 h-5" fill="#F26722" viewBox="0 0 24 24" id="delta" xmlns="http://www.w3.org/2000/svg"><g stroke-width="0"></g><g stroke-linecap="round" stroke-linejoin="round"></g><g><path id="primary" d="M19,21,11.5,4.13M20,21,12,3,4,21Z" style="fill: none; stroke: #F26722; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></g></svg>',
2020
'distance' => '<svg class="w-7 h-7" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 21C7.9 21 6.95833 20.6083 6.175 19.825C5.39167 19.0417 5 18.1 5 17V8.825C4.41667 8.60833 3.9375 8.24583 3.5625 7.7375C3.1875 7.22917 3 6.65 3 6C3 5.16667 3.29167 4.45833 3.875 3.875C4.45833 3.29167 5.16667 3 6 3C6.83333 3 7.54167 3.29167 8.125 3.875C8.70833 4.45833 9 5.16667 9 6C9 6.65 8.8125 7.22917 8.4375 7.7375C8.0625 8.24583 7.58333 8.60833 7 8.825V17C7 17.55 7.19583 18.0208 7.5875 18.4125C7.97917 18.8042 8.45 19 9 19C9.55 19 10.0208 18.8042 10.4125 18.4125C10.8042 18.0208 11 17.55 11 17V7C11 5.9 11.3917 4.95833 12.175 4.175C12.9583 3.39167 13.9 3 15 3C16.1 3 17.0417 3.39167 17.825 4.175C18.6083 4.95833 19 5.9 19 7V15.175C19.5833 15.3917 20.0625 15.7542 20.4375 16.2625C20.8125 16.7708 21 17.35 21 18C21 18.8333 20.7083 19.5417 20.125 20.125C19.5417 20.7083 18.8333 21 18 21C17.1667 21 16.4583 20.7083 15.875 20.125C15.2917 19.5417 15 18.8333 15 18C15 17.35 15.1875 16.7667 15.5625 16.25C15.9375 15.7333 16.4167 15.375 17 15.175V7C17 6.45 16.8042 5.97917 16.4125 5.5875C16.0208 5.19583 15.55 5 15 5C14.45 5 13.9792 5.19583 13.5875 5.5875C13.1958 5.97917 13 6.45 13 7V17C13 18.1 12.6083 19.0417 11.825 19.825C11.0417 20.6083 10.1 21 9 21ZM6 7C6.28333 7 6.52083 6.90417 6.7125 6.7125C6.90417 6.52083 7 6.28333 7 6C7 5.71667 6.90417 5.47917 6.7125 5.2875C6.52083 5.09583 6.28333 5 6 5C5.71667 5 5.47917 5.09583 5.2875 5.2875C5.09583 5.47917 5 5.71667 5 6C5 6.28333 5.09583 6.52083 5.2875 6.7125C5.47917 6.90417 5.71667 7 6 7ZM18 19C18.2833 19 18.5208 18.9042 18.7125 18.7125C18.9042 18.5208 19 18.2833 19 18C19 17.7167 18.9042 17.4792 18.7125 17.2875C18.5208 17.0958 18.2833 17 18 17C17.7167 17 17.4792 17.0958 17.2875 17.2875C17.0958 17.4792 17 17.7167 17 18C17 18.2833 17.0958 18.5208 17.2875 18.7125C17.4792 18.9042 17.7167 19 18 19Z" fill="#F26722"/></svg>',
2121
'elevation' => '<svg class="w-7 h-7" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#F26722" d="M21.85,17.47l-5-8a1,1,0,0,0-1.7,0l-1,1.63L10.86,5.5a1,1,0,0,0-1.72,0l-7,12A1,1,0,0,0,3,19H21a1,1,0,0,0,.85-1.53ZM10.45,17H4.74L10,8l2.93,5Zm2.35,0L15,13.57h0L16,11.89,19.2,17Z"></path></svg>',
22+
'expand' => '<svg class="size-4 text-gray-400 hover:text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 4H4m0 0v4m0-4 5 5m7-5h4m0 0v4m0-4-5 5M8 20H4m0 0v-4m0 4 5-5m7 5h4m0 0v-4m0 4-5-5"/></svg>',
2223
'hashtag' => '<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke-width="0"></g><g stroke-linecap="round" stroke-linejoin="round"></g><g> <path d="M10 4L7 20M17 4L14 20M5 8H20M4 16H19" stroke="#F26722" stroke-width="2" stroke-linecap="round"></path> </g></svg>',
2324
'heart-rate' => '<svg class="w-7 h-7" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><g><path d="M19.2373 6.23731C20.7839 7.78395 20.8432 10.2727 19.3718 11.8911L11.9995 20.0001L4.62812 11.8911C3.15679 10.2727 3.21605 7.7839 4.76269 6.23726C6.48961 4.51034 9.33372 4.66814 10.8594 6.5752L12 8.00045L13.1396 6.57504C14.6653 4.66798 17.5104 4.51039 19.2373 6.23731Z" stroke="#F26722" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></svg>',
2425
'rocket' => ' <svg class="w-7 h-7" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path stroke="#F26722" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m10.051 8.102-3.778.322-1.994 1.994a.94.94 0 0 0 .533 1.6l2.698.316m8.39 1.617-.322 3.78-1.994 1.994a.94.94 0 0 1-1.595-.533l-.4-2.652m8.166-11.174a1.366 1.366 0 0 0-1.12-1.12c-1.616-.279-4.906-.623-6.38.853-1.671 1.672-5.211 8.015-6.31 10.023a.932.932 0 0 0 .162 1.111l.828.835.833.832a.932.932 0 0 0 1.111.163c2.008-1.102 8.35-4.642 10.021-6.312 1.475-1.478 1.133-4.77.855-6.385Zm-2.961 3.722a1.88 1.88 0 1 1-3.76 0 1.88 1.88 0 0 1 3.76 0Z"/></svg>',

templates/html/dashboard/dashboard.html.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{% autoescape false %}
99
<div class="grid grid-cols-1 xl:grid-cols-6 gap-4 mb-4">
1010
{% for renderedWidget in widgets %}
11-
<div class="{{ colSpanMap[renderedWidget.getWidth()] }} flex flex-col p-4 bg-white border border-gray-200 rounded-lg shadow-xs">
11+
<div data-fullscreen-content class="{{ colSpanMap[renderedWidget.getWidth()] }} flex flex-col p-4 bg-white border border-gray-200 rounded-lg shadow-xs">
1212
{{ renderedWidget.getRenderedHtml() }}
1313
</div>
1414
{% endfor %}

templates/html/dashboard/widget/widget--activity-grid.html.twig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{% autoescape false %}
33
<div class="flex items-center mb-4">
44
<h3 class="font-semibold text-lg">{{ "Activity heatmap"|trans }}</h3>
5-
<button class="hidden lg:block" data-popover-target="popover-description" data-popover-placement="right"
5+
<button class="hidden lg:block" data-popover-target="popover-description-activity-grid" data-popover-placement="right"
66
type="button">
77
<svg class="w-4 h-4 ml-1 text-gray-400 hover:text-gray-500" aria-hidden="true" fill="currentColor"
88
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
@@ -12,8 +12,8 @@
1212
</svg>
1313
<span class="sr-only">{{ "Show information"|trans }}</span>
1414
</button>
15-
<div data-popover id="popover-description" role="tooltip"
16-
class="hidden lg:block lg:w-[450px] xl:w-[650px] absolute z-100 invisible text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-xs opacity-0">
15+
<div data-popover id="popover-description-activity-grid" role="tooltip"
16+
class="hidden lg:block lg:w-[450px] xl:w-[650px] absolute z-100 invisible text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-md opacity-0">
1717
<div class="p-3 space-y-2">
1818
<p>
1919
{% trans %}This map shows the daily training intensity for the last year. The training intensity for an activity is calculated based on several metrics.{% endtrans %}
Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,72 @@
11
{% autoescape false %}
2-
<h3 class="mb-4 text-lg font-semibold">
3-
{{ "Athlete profile"|trans }}
4-
</h3>
2+
<div class="flex items-center mb-4">
3+
<h3 class="text-lg font-semibold">
4+
{{ "Athlete profile"|trans }}
5+
</h3>
6+
<button class="hidden lg:block" data-popover-target="popover-description-athlete-profile" data-popover-placement="right"
7+
type="button">
8+
<svg class="w-4 h-4 ml-1 text-gray-400 hover:text-gray-500" aria-hidden="true" fill="currentColor"
9+
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
10+
<path fill-rule="evenodd"
11+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z"
12+
clip-rule="evenodd"></path>
13+
</svg>
14+
<span class="sr-only">{{ "Show information"|trans }}</span>
15+
</button>
16+
<div data-popover id="popover-description-athlete-profile" role="tooltip"
17+
class="hidden lg:block lg:w-[450px] xl:w-[650px] absolute z-100 invisible text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-md opacity-0">
18+
<div class="p-3 space-y-2">
19+
<p>
20+
{% trans %}This chart summarizes how you train. Each axis reflects a different normalized training habit, so higher values mean stronger emphasis.{% endtrans %}
21+
</p>
22+
<div class="flex gap-x-2">
23+
<h3 class="font-semibold text-gray-900">{{ "Volume"|trans }}</h3>
24+
<span>-</span>
25+
<p>
26+
{% trans %}How much do you train{% endtrans %}
27+
</p>
28+
</div>
29+
<div class="flex gap-x-2">
30+
<h3 class="font-semibold text-gray-900">{{ "Consistency"|trans }}</h3>
31+
<span>-</span>
32+
<p>
33+
{% trans %}How often do you train{% endtrans %}
34+
</p>
35+
</div>
36+
<div class="flex gap-x-2">
37+
<h3 class="font-semibold text-gray-900">{{ "Intensity"|trans }}</h3>
38+
<span>-</span>
39+
<p>
40+
{% trans %}How hard do you train{% endtrans %}
41+
</p>
42+
</div>
43+
<div class="flex gap-x-2">
44+
<h3 class="font-semibold text-gray-900">{{ "Duration"|trans }}</h3>
45+
<span>-</span>
46+
<p>
47+
{% trans %}How long are your sessions{% endtrans %}
48+
</p>
49+
</div>
50+
<div class="flex gap-x-2">
51+
<h3 class="font-semibold text-gray-900">{{ "Density"|trans }}</h3>
52+
<span>-</span>
53+
<p>
54+
{% trans %}How packed is your training{% endtrans %}
55+
</p>
56+
</div>
57+
<div class="flex gap-x-2">
58+
<h3 class="font-semibold text-gray-900">{{ "Variety"|trans }}</h3>
59+
<span>-</span>
60+
<p>
61+
{% trans %}How diverse is your training{% endtrans %}
62+
</p>
63+
</div>
64+
</div>
65+
</div>
66+
<button type="button" data-fullscreen-trigger class="[&.full-screen-enabled]:hidden ml-auto self-start cursor-pointer">
67+
{{ svg('expand') }}
68+
<span class="sr-only">{{ "Enter fullscreen mode"|trans }}</span>
69+
</button>
70+
</div>
571
<div class="h-full min-h-[16rem]" data-echarts-options='{{ athleteProfileChart }}'></div>
672
{% endautoescape %}

0 commit comments

Comments
 (0)