@rmdes/indiekit-endpoint-funkwhale 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import express from "express";
2
+ import { fileURLToPath } from "node:url";
3
+ import path from "node:path";
2
4
 
3
5
  import { dashboardController } from "./lib/controllers/dashboard.js";
4
6
  import { listeningsController } from "./lib/controllers/listenings.js";
@@ -7,6 +9,8 @@ import { statsController } from "./lib/controllers/stats.js";
7
9
  import { nowPlayingController } from "./lib/controllers/now-playing.js";
8
10
  import { startSync } from "./lib/sync.js";
9
11
 
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+
10
14
  const protectedRouter = express.Router();
11
15
  const publicRouter = express.Router();
12
16
 
@@ -37,6 +41,14 @@ export default class FunkwhaleEndpoint {
37
41
  return ["FUNKWHALE_TOKEN", "FUNKWHALE_INSTANCE", "FUNKWHALE_USERNAME"];
38
42
  }
39
43
 
44
+ get localesDirectory() {
45
+ return path.join(__dirname, "locales");
46
+ }
47
+
48
+ get styles() {
49
+ return path.join(__dirname, "assets", "styles.css");
50
+ }
51
+
40
52
  get navigationItems() {
41
53
  return {
42
54
  href: this.options.mountPath,
package/locales/en.json CHANGED
@@ -1,29 +1,37 @@
1
1
  {
2
- "funkwhale.title": "Funkwhale",
3
- "funkwhale.listenings": "Listening History",
4
- "funkwhale.favorites": "Favorites",
5
- "funkwhale.stats": "Statistics",
6
- "funkwhale.nowPlaying": "Now Playing",
7
- "funkwhale.recentlyPlayed": "Recently Played",
8
- "funkwhale.lastPlayed": "Last Played",
9
- "funkwhale.allTime": "All Time",
10
- "funkwhale.thisWeek": "This Week",
11
- "funkwhale.thisMonth": "This Month",
12
- "funkwhale.trends": "Trends",
13
- "funkwhale.listeningTrend": "Listening Trend (30 days)",
14
- "funkwhale.topArtists": "Top Artists",
15
- "funkwhale.topAlbums": "Top Albums",
16
- "funkwhale.plays": "plays",
17
- "funkwhale.tracks": "tracks",
18
- "funkwhale.artists": "artists",
19
- "funkwhale.albums": "albums",
20
- "funkwhale.listeningTime": "Listening Time",
21
- "funkwhale.noRecentPlays": "No music played recently",
22
- "funkwhale.noFavorites": "No favorite tracks yet",
23
- "funkwhale.viewAll": "View All",
24
- "funkwhale.viewStats": "View Statistics",
25
- "funkwhale.error.connection": "Could not connect to Funkwhale",
26
- "funkwhale.error.noConfig": "Funkwhale endpoint not configured correctly",
27
- "funkwhale.widget.description": "View your Funkwhale listening activity",
28
- "funkwhale.widget.view": "View Activity"
2
+ "funkwhale": {
3
+ "title": "Funkwhale",
4
+ "listenings": "Listening History",
5
+ "favorites": "Favorites",
6
+ "stats": "Statistics",
7
+ "nowPlaying": "Now Playing",
8
+ "recentlyPlayed": "Recently Played",
9
+ "lastPlayed": "Last Played",
10
+ "allTime": "All Time",
11
+ "thisWeek": "This Week",
12
+ "thisMonth": "This Month",
13
+ "trends": "Trends",
14
+ "listeningTrend": "Listening Trend (30 days)",
15
+ "topArtists": "Top Artists",
16
+ "topAlbums": "Top Albums",
17
+ "plays": "plays",
18
+ "tracks": "tracks",
19
+ "artists": "artists",
20
+ "albums": "albums",
21
+ "listeningTime": "Listening Time",
22
+ "noRecentPlays": "No music played recently",
23
+ "noFavorites": "No favorite tracks yet",
24
+ "viewAll": "View All",
25
+ "viewStats": "View Statistics",
26
+ "sync": "Sync Now",
27
+ "lastSync": "Last sync",
28
+ "error": {
29
+ "connection": "Could not connect to Funkwhale",
30
+ "noConfig": "Funkwhale endpoint not configured correctly"
31
+ },
32
+ "widget": {
33
+ "description": "View your Funkwhale listening activity",
34
+ "view": "View Activity"
35
+ }
36
+ }
29
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-funkwhale",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Funkwhale listening activity endpoint for Indiekit. Display listening history, favorites, and statistics.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -1,27 +1,93 @@
1
1
  {% extends "document.njk" %}
2
2
 
3
3
  {% block content %}
4
+ <style>
5
+ .fw-section { margin-bottom: 2rem; }
6
+ .fw-list {
7
+ list-style: none;
8
+ padding: 0;
9
+ margin: 0 0 1rem 0;
10
+ }
11
+ .fw-list-item {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: 0.75rem;
15
+ padding: 0.75rem 0;
16
+ border-bottom: 1px solid var(--color-border, #eee);
17
+ }
18
+ .fw-list-item:last-child { border-bottom: none; }
19
+ .fw-list-item img {
20
+ width: 48px;
21
+ height: 48px;
22
+ object-fit: cover;
23
+ border-radius: 0.25rem;
24
+ flex-shrink: 0;
25
+ }
26
+ .fw-list-item-placeholder {
27
+ width: 48px;
28
+ height: 48px;
29
+ background: var(--color-border, #ddd);
30
+ border-radius: 0.25rem;
31
+ flex-shrink: 0;
32
+ }
33
+ .fw-list-info {
34
+ flex: 1;
35
+ min-width: 0;
36
+ }
37
+ .fw-list-title {
38
+ display: block;
39
+ font-weight: 500;
40
+ text-decoration: none;
41
+ color: inherit;
42
+ white-space: nowrap;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ }
46
+ .fw-list-title:hover { text-decoration: underline; }
47
+ .fw-list-album {
48
+ margin: 0.25rem 0;
49
+ color: var(--color-text-secondary, #666);
50
+ font-size: 0.875rem;
51
+ }
52
+ .fw-meta {
53
+ display: flex;
54
+ gap: 0.5rem;
55
+ color: var(--color-text-secondary, #666);
56
+ font-size: 0.75rem;
57
+ }
58
+ .fw-pagination {
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ gap: 1rem;
63
+ margin-top: 1.5rem;
64
+ }
65
+ .fw-pagination-info {
66
+ color: var(--color-text-secondary, #666);
67
+ }
68
+ </style>
69
+
4
70
  {% if error %}
5
71
  {{ prose({ text: error.message }) }}
6
72
  {% else %}
7
- <section class="funkwhale-section">
73
+ <section class="fw-section">
8
74
  {% if favorites and favorites.length > 0 %}
9
- <ul class="funkwhale-list funkwhale-list--full">
75
+ <ul class="fw-list">
10
76
  {% for favorite in favorites %}
11
- <li class="funkwhale-list__item">
77
+ <li class="fw-list-item">
12
78
  {% if favorite.coverUrl %}
13
- <img src="{{ favorite.coverUrl }}" alt="" class="funkwhale-list__cover" loading="lazy">
79
+ <img src="{{ favorite.coverUrl }}" alt="" loading="lazy">
14
80
  {% else %}
15
- <div class="funkwhale-list__cover funkwhale-list__cover--placeholder"></div>
81
+ <div class="fw-list-item-placeholder"></div>
16
82
  {% endif %}
17
- <div class="funkwhale-list__info">
18
- <a href="{{ favorite.trackUrl }}" class="funkwhale-list__title" target="_blank" rel="noopener">
83
+ <div class="fw-list-info">
84
+ <a href="{{ favorite.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
19
85
  {{ favorite.artist }} - {{ favorite.track }}
20
86
  </a>
21
87
  {% if favorite.album %}
22
- <p class="funkwhale-list__album">{{ favorite.album }}</p>
88
+ <p class="fw-list-album">{{ favorite.album }}</p>
23
89
  {% endif %}
24
- <small class="funkwhale-meta">
90
+ <small class="fw-meta">
25
91
  <span>{{ favorite.duration }}</span>
26
92
  <span>Favorited {{ favorite.relativeTime }}</span>
27
93
  </small>
@@ -32,11 +98,11 @@
32
98
 
33
99
  {# Pagination #}
34
100
  {% if pagination %}
35
- <nav class="funkwhale-pagination">
101
+ <nav class="fw-pagination">
36
102
  {% if pagination.hasPrev %}
37
103
  <a href="?page={{ pagination.current - 1 }}" class="button button--secondary">Previous</a>
38
104
  {% endif %}
39
- <span class="funkwhale-pagination__info">
105
+ <span class="fw-pagination-info">
40
106
  Page {{ pagination.current }} of {{ pagination.total }}
41
107
  </span>
42
108
  {% if pagination.hasNext %}
@@ -49,12 +115,10 @@
49
115
  {% endif %}
50
116
  </section>
51
117
 
52
- <div class="button-grid">
53
- {{ button({
54
- classes: "button--secondary",
55
- href: mountPath,
56
- text: "Back to Dashboard"
57
- }) }}
58
- </div>
118
+ {{ button({
119
+ classes: "button--secondary",
120
+ href: mountPath,
121
+ text: "Back to Dashboard"
122
+ }) }}
59
123
  {% endif %}
60
124
  {% endblock %}
@@ -1,16 +1,154 @@
1
1
  {% extends "document.njk" %}
2
2
 
3
3
  {% block content %}
4
+ <style>
5
+ /* Inline styles to ensure proper rendering */
6
+ .fw-section { margin-bottom: 2rem; }
7
+ .fw-section h2 { margin-bottom: 1rem; }
8
+
9
+ /* Stats grid */
10
+ .fw-stats-grid {
11
+ display: grid;
12
+ grid-template-columns: repeat(3, 1fr);
13
+ gap: 1rem;
14
+ margin-bottom: 1rem;
15
+ }
16
+ .fw-stat {
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ padding: 1rem;
21
+ background: var(--color-offset, #f5f5f5);
22
+ border-radius: 0.5rem;
23
+ text-align: center;
24
+ }
25
+ .fw-stat-value {
26
+ font-size: 1.5rem;
27
+ font-weight: 700;
28
+ color: var(--color-accent, #3b82f6);
29
+ }
30
+ .fw-stat-label {
31
+ font-size: 0.75rem;
32
+ color: var(--color-text-secondary, #666);
33
+ text-transform: uppercase;
34
+ letter-spacing: 0.05em;
35
+ }
36
+
37
+ /* Featured track */
38
+ .fw-featured {
39
+ display: flex;
40
+ gap: 1rem;
41
+ padding: 1rem;
42
+ background: var(--color-offset, #f5f5f5);
43
+ border-radius: 0.5rem;
44
+ align-items: flex-start;
45
+ }
46
+ .fw-featured img {
47
+ width: 80px;
48
+ height: 80px;
49
+ object-fit: cover;
50
+ border-radius: 0.25rem;
51
+ flex-shrink: 0;
52
+ }
53
+ .fw-featured-info { flex: 1; }
54
+ .fw-featured-title {
55
+ font-weight: 600;
56
+ text-decoration: none;
57
+ color: inherit;
58
+ display: block;
59
+ }
60
+ .fw-featured-title:hover { text-decoration: underline; }
61
+ .fw-featured-album {
62
+ margin: 0.25rem 0;
63
+ color: var(--color-text-secondary, #666);
64
+ font-size: 0.875rem;
65
+ }
66
+ .fw-meta {
67
+ display: flex;
68
+ gap: 0.5rem;
69
+ color: var(--color-text-secondary, #666);
70
+ font-size: 0.75rem;
71
+ }
72
+
73
+ /* Now playing animation */
74
+ .fw-playing-status {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ gap: 0.5rem;
78
+ color: #22c55e;
79
+ }
80
+ .fw-bars {
81
+ display: flex;
82
+ gap: 2px;
83
+ align-items: flex-end;
84
+ height: 16px;
85
+ }
86
+ .fw-bars span {
87
+ width: 3px;
88
+ background: currentColor;
89
+ animation: fw-bar 0.5s infinite ease-in-out alternate;
90
+ }
91
+ .fw-bars span:nth-child(2) { animation-delay: 0.2s; }
92
+ .fw-bars span:nth-child(3) { animation-delay: 0.4s; }
93
+ @keyframes fw-bar {
94
+ from { height: 4px; }
95
+ to { height: 16px; }
96
+ }
97
+
98
+ /* List */
99
+ .fw-list {
100
+ list-style: none;
101
+ padding: 0;
102
+ margin: 0 0 1rem 0;
103
+ }
104
+ .fw-list-item {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 0.75rem;
108
+ padding: 0.75rem 0;
109
+ border-bottom: 1px solid var(--color-border, #eee);
110
+ }
111
+ .fw-list-item:last-child { border-bottom: none; }
112
+ .fw-list-item img {
113
+ width: 48px;
114
+ height: 48px;
115
+ object-fit: cover;
116
+ border-radius: 0.25rem;
117
+ flex-shrink: 0;
118
+ }
119
+ .fw-list-item-placeholder {
120
+ width: 48px;
121
+ height: 48px;
122
+ background: var(--color-border, #ddd);
123
+ border-radius: 0.25rem;
124
+ flex-shrink: 0;
125
+ }
126
+ .fw-list-info {
127
+ flex: 1;
128
+ min-width: 0;
129
+ }
130
+ .fw-list-title {
131
+ display: block;
132
+ font-weight: 500;
133
+ text-decoration: none;
134
+ color: inherit;
135
+ white-space: nowrap;
136
+ overflow: hidden;
137
+ text-overflow: ellipsis;
138
+ }
139
+ .fw-list-title:hover { text-decoration: underline; }
140
+ </style>
141
+
4
142
  {% if error %}
5
143
  {{ prose({ text: error.message }) }}
6
144
  {% else %}
7
145
  {# Now Playing / Recently Played #}
8
146
  {% if nowPlaying %}
9
- <section class="funkwhale-section funkwhale-now-playing">
147
+ <section class="fw-section">
10
148
  <h2>
11
149
  {% if nowPlaying.status == "now-playing" %}
12
- <span class="funkwhale-status funkwhale-status--playing">
13
- <span class="funkwhale-bars">
150
+ <span class="fw-playing-status">
151
+ <span class="fw-bars">
14
152
  <span></span><span></span><span></span>
15
153
  </span>
16
154
  {{ __("funkwhale.nowPlaying") }}
@@ -19,20 +157,18 @@
19
157
  {{ __("funkwhale.recentlyPlayed") }}
20
158
  {% endif %}
21
159
  </h2>
22
- <article class="funkwhale-track funkwhale-track--featured">
160
+ <article class="fw-featured">
23
161
  {% if nowPlaying.coverUrl %}
24
- <img src="{{ nowPlaying.coverUrl }}" alt="" class="funkwhale-track__cover" loading="lazy">
25
- {% else %}
26
- <div class="funkwhale-track__cover funkwhale-track__cover--placeholder"></div>
162
+ <img src="{{ nowPlaying.coverUrl }}" alt="" loading="lazy">
27
163
  {% endif %}
28
- <div class="funkwhale-track__info">
29
- <a href="{{ nowPlaying.trackUrl }}" class="funkwhale-track__title" target="_blank" rel="noopener">
164
+ <div class="fw-featured-info">
165
+ <a href="{{ nowPlaying.trackUrl }}" class="fw-featured-title" target="_blank" rel="noopener">
30
166
  {{ nowPlaying.artist }} - {{ nowPlaying.track }}
31
167
  </a>
32
168
  {% if nowPlaying.album %}
33
- <p class="funkwhale-track__album">{{ nowPlaying.album }}</p>
169
+ <p class="fw-featured-album">{{ nowPlaying.album }}</p>
34
170
  {% endif %}
35
- <small class="funkwhale-meta">
171
+ <small class="fw-meta">
36
172
  <span>{{ nowPlaying.duration }}</span>
37
173
  <span>{{ nowPlaying.relativeTime }}</span>
38
174
  </small>
@@ -43,49 +179,47 @@
43
179
 
44
180
  {# Quick Stats Summary #}
45
181
  {% if summary %}
46
- <section class="funkwhale-section funkwhale-summary">
182
+ <section class="fw-section">
47
183
  <h2>{{ __("funkwhale.stats") }}</h2>
48
- <div class="funkwhale-stats-grid">
49
- <div class="funkwhale-stat">
50
- <span class="funkwhale-stat__value">{{ summary.totalPlays }}</span>
51
- <span class="funkwhale-stat__label">{{ __("funkwhale.plays") }}</span>
184
+ <div class="fw-stats-grid">
185
+ <div class="fw-stat">
186
+ <span class="fw-stat-value">{{ summary.totalPlays | default(0) }}</span>
187
+ <span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
52
188
  </div>
53
- <div class="funkwhale-stat">
54
- <span class="funkwhale-stat__value">{{ summary.uniqueTracks }}</span>
55
- <span class="funkwhale-stat__label">{{ __("funkwhale.tracks") }}</span>
189
+ <div class="fw-stat">
190
+ <span class="fw-stat-value">{{ summary.uniqueTracks | default(0) }}</span>
191
+ <span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
56
192
  </div>
57
- <div class="funkwhale-stat">
58
- <span class="funkwhale-stat__value">{{ summary.uniqueArtists }}</span>
59
- <span class="funkwhale-stat__label">{{ __("funkwhale.artists") }}</span>
193
+ <div class="fw-stat">
194
+ <span class="fw-stat-value">{{ summary.uniqueArtists | default(0) }}</span>
195
+ <span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
60
196
  </div>
61
197
  </div>
62
- <div class="button-grid">
63
- {{ button({
64
- classes: "button--secondary",
65
- href: mountPath + "/stats",
66
- text: __("funkwhale.viewStats")
67
- }) }}
68
- </div>
198
+ {{ button({
199
+ classes: "button--secondary",
200
+ href: mountPath + "/stats",
201
+ text: __("funkwhale.viewStats")
202
+ }) }}
69
203
  </section>
70
204
  {% endif %}
71
205
 
72
206
  {# Recent Listenings #}
73
- <section class="funkwhale-section">
207
+ <section class="fw-section">
74
208
  <h2>{{ __("funkwhale.listenings") }}</h2>
75
209
  {% if listenings and listenings.length > 0 %}
76
- <ul class="funkwhale-list">
210
+ <ul class="fw-list">
77
211
  {% for listening in listenings %}
78
- <li class="funkwhale-list__item">
212
+ <li class="fw-list-item">
79
213
  {% if listening.coverUrl %}
80
- <img src="{{ listening.coverUrl }}" alt="" class="funkwhale-list__cover" loading="lazy">
214
+ <img src="{{ listening.coverUrl }}" alt="" loading="lazy">
81
215
  {% else %}
82
- <div class="funkwhale-list__cover funkwhale-list__cover--placeholder"></div>
216
+ <div class="fw-list-item-placeholder"></div>
83
217
  {% endif %}
84
- <div class="funkwhale-list__info">
85
- <a href="{{ listening.trackUrl }}" class="funkwhale-list__title" target="_blank" rel="noopener">
218
+ <div class="fw-list-info">
219
+ <a href="{{ listening.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
86
220
  {{ listening.artist }} - {{ listening.track }}
87
221
  </a>
88
- <small class="funkwhale-meta">{{ listening.relativeTime }}</small>
222
+ <small class="fw-meta">{{ listening.relativeTime }}</small>
89
223
  </div>
90
224
  {% if listening.status == "now-playing" %}
91
225
  {{ badge({ color: "green", text: __("funkwhale.nowPlaying") }) }}
@@ -95,48 +229,44 @@
95
229
  </li>
96
230
  {% endfor %}
97
231
  </ul>
98
- <div class="button-grid">
99
- {{ button({
100
- classes: "button--secondary",
101
- href: mountPath + "/listenings",
102
- text: __("funkwhale.viewAll")
103
- }) }}
104
- </div>
232
+ {{ button({
233
+ classes: "button--secondary",
234
+ href: mountPath + "/listenings",
235
+ text: __("funkwhale.viewAll")
236
+ }) }}
105
237
  {% else %}
106
238
  {{ prose({ text: __("funkwhale.noRecentPlays") }) }}
107
239
  {% endif %}
108
240
  </section>
109
241
 
110
242
  {# Favorites #}
111
- <section class="funkwhale-section">
243
+ <section class="fw-section">
112
244
  <h2>{{ __("funkwhale.favorites") }}</h2>
113
245
  {% if favorites and favorites.length > 0 %}
114
- <ul class="funkwhale-list">
246
+ <ul class="fw-list">
115
247
  {% for favorite in favorites %}
116
- <li class="funkwhale-list__item">
248
+ <li class="fw-list-item">
117
249
  {% if favorite.coverUrl %}
118
- <img src="{{ favorite.coverUrl }}" alt="" class="funkwhale-list__cover" loading="lazy">
250
+ <img src="{{ favorite.coverUrl }}" alt="" loading="lazy">
119
251
  {% else %}
120
- <div class="funkwhale-list__cover funkwhale-list__cover--placeholder"></div>
252
+ <div class="fw-list-item-placeholder"></div>
121
253
  {% endif %}
122
- <div class="funkwhale-list__info">
123
- <a href="{{ favorite.trackUrl }}" class="funkwhale-list__title" target="_blank" rel="noopener">
254
+ <div class="fw-list-info">
255
+ <a href="{{ favorite.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
124
256
  {{ favorite.artist }} - {{ favorite.track }}
125
257
  </a>
126
258
  {% if favorite.album %}
127
- <small class="funkwhale-meta">{{ favorite.album }}</small>
259
+ <small class="fw-meta">{{ favorite.album }}</small>
128
260
  {% endif %}
129
261
  </div>
130
262
  </li>
131
263
  {% endfor %}
132
264
  </ul>
133
- <div class="button-grid">
134
- {{ button({
135
- classes: "button--secondary",
136
- href: mountPath + "/favorites",
137
- text: __("funkwhale.viewAll")
138
- }) }}
139
- </div>
265
+ {{ button({
266
+ classes: "button--secondary",
267
+ href: mountPath + "/favorites",
268
+ text: __("funkwhale.viewAll")
269
+ }) }}
140
270
  {% else %}
141
271
  {{ prose({ text: __("funkwhale.noFavorites") }) }}
142
272
  {% endif %}
@@ -1,27 +1,93 @@
1
1
  {% extends "document.njk" %}
2
2
 
3
3
  {% block content %}
4
+ <style>
5
+ .fw-section { margin-bottom: 2rem; }
6
+ .fw-list {
7
+ list-style: none;
8
+ padding: 0;
9
+ margin: 0 0 1rem 0;
10
+ }
11
+ .fw-list-item {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: 0.75rem;
15
+ padding: 0.75rem 0;
16
+ border-bottom: 1px solid var(--color-border, #eee);
17
+ }
18
+ .fw-list-item:last-child { border-bottom: none; }
19
+ .fw-list-item img {
20
+ width: 48px;
21
+ height: 48px;
22
+ object-fit: cover;
23
+ border-radius: 0.25rem;
24
+ flex-shrink: 0;
25
+ }
26
+ .fw-list-item-placeholder {
27
+ width: 48px;
28
+ height: 48px;
29
+ background: var(--color-border, #ddd);
30
+ border-radius: 0.25rem;
31
+ flex-shrink: 0;
32
+ }
33
+ .fw-list-info {
34
+ flex: 1;
35
+ min-width: 0;
36
+ }
37
+ .fw-list-title {
38
+ display: block;
39
+ font-weight: 500;
40
+ text-decoration: none;
41
+ color: inherit;
42
+ white-space: nowrap;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ }
46
+ .fw-list-title:hover { text-decoration: underline; }
47
+ .fw-list-album {
48
+ margin: 0.25rem 0;
49
+ color: var(--color-text-secondary, #666);
50
+ font-size: 0.875rem;
51
+ }
52
+ .fw-meta {
53
+ display: flex;
54
+ gap: 0.5rem;
55
+ color: var(--color-text-secondary, #666);
56
+ font-size: 0.75rem;
57
+ }
58
+ .fw-pagination {
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ gap: 1rem;
63
+ margin-top: 1.5rem;
64
+ }
65
+ .fw-pagination-info {
66
+ color: var(--color-text-secondary, #666);
67
+ }
68
+ </style>
69
+
4
70
  {% if error %}
5
71
  {{ prose({ text: error.message }) }}
6
72
  {% else %}
7
- <section class="funkwhale-section">
73
+ <section class="fw-section">
8
74
  {% if listenings and listenings.length > 0 %}
9
- <ul class="funkwhale-list funkwhale-list--full">
75
+ <ul class="fw-list">
10
76
  {% for listening in listenings %}
11
- <li class="funkwhale-list__item">
77
+ <li class="fw-list-item">
12
78
  {% if listening.coverUrl %}
13
- <img src="{{ listening.coverUrl }}" alt="" class="funkwhale-list__cover" loading="lazy">
79
+ <img src="{{ listening.coverUrl }}" alt="" loading="lazy">
14
80
  {% else %}
15
- <div class="funkwhale-list__cover funkwhale-list__cover--placeholder"></div>
81
+ <div class="fw-list-item-placeholder"></div>
16
82
  {% endif %}
17
- <div class="funkwhale-list__info">
18
- <a href="{{ listening.trackUrl }}" class="funkwhale-list__title" target="_blank" rel="noopener">
83
+ <div class="fw-list-info">
84
+ <a href="{{ listening.trackUrl }}" class="fw-list-title" target="_blank" rel="noopener">
19
85
  {{ listening.artist }} - {{ listening.track }}
20
86
  </a>
21
87
  {% if listening.album %}
22
- <p class="funkwhale-list__album">{{ listening.album }}</p>
88
+ <p class="fw-list-album">{{ listening.album }}</p>
23
89
  {% endif %}
24
- <small class="funkwhale-meta">
90
+ <small class="fw-meta">
25
91
  <span>{{ listening.duration }}</span>
26
92
  <span>{{ listening.relativeTime }}</span>
27
93
  </small>
@@ -37,11 +103,11 @@
37
103
 
38
104
  {# Pagination #}
39
105
  {% if pagination %}
40
- <nav class="funkwhale-pagination">
106
+ <nav class="fw-pagination">
41
107
  {% if pagination.hasPrev %}
42
108
  <a href="?page={{ pagination.current - 1 }}" class="button button--secondary">Previous</a>
43
109
  {% endif %}
44
- <span class="funkwhale-pagination__info">
110
+ <span class="fw-pagination-info">
45
111
  Page {{ pagination.current }} of {{ pagination.total }}
46
112
  </span>
47
113
  {% if pagination.hasNext %}
@@ -54,12 +120,10 @@
54
120
  {% endif %}
55
121
  </section>
56
122
 
57
- <div class="button-grid">
58
- {{ button({
59
- classes: "button--secondary",
60
- href: mountPath,
61
- text: "Back to Dashboard"
62
- }) }}
63
- </div>
123
+ {{ button({
124
+ classes: "button--secondary",
125
+ href: mountPath,
126
+ text: "Back to Dashboard"
127
+ }) }}
64
128
  {% endif %}
65
129
  {% endblock %}
package/views/stats.njk CHANGED
@@ -1,87 +1,391 @@
1
1
  {% extends "document.njk" %}
2
2
 
3
3
  {% block content %}
4
+ <style>
5
+ .fw-section { margin-bottom: 2rem; }
6
+
7
+ /* Stats grid */
8
+ .fw-stats-grid {
9
+ display: grid;
10
+ grid-template-columns: repeat(4, 1fr);
11
+ gap: 1rem;
12
+ margin-bottom: 1.5rem;
13
+ }
14
+ @media (max-width: 600px) {
15
+ .fw-stats-grid { grid-template-columns: repeat(2, 1fr); }
16
+ }
17
+ .fw-stat {
18
+ display: flex;
19
+ flex-direction: column;
20
+ align-items: center;
21
+ padding: 1rem;
22
+ background: var(--color-offset, #f5f5f5);
23
+ border-radius: 0.5rem;
24
+ text-align: center;
25
+ }
26
+ .fw-stat-value {
27
+ font-size: 1.5rem;
28
+ font-weight: 700;
29
+ color: var(--color-accent, #3b82f6);
30
+ }
31
+ .fw-stat-label {
32
+ font-size: 0.75rem;
33
+ color: var(--color-text-secondary, #666);
34
+ text-transform: uppercase;
35
+ letter-spacing: 0.05em;
36
+ }
37
+
38
+ /* Tabs */
39
+ .fw-tabs {
40
+ display: flex;
41
+ gap: 0.25rem;
42
+ margin-bottom: 1.5rem;
43
+ border-bottom: 1px solid var(--color-border, #ddd);
44
+ }
45
+ .fw-tab {
46
+ padding: 0.75rem 1rem;
47
+ border: none;
48
+ background: none;
49
+ color: var(--color-text-secondary, #666);
50
+ cursor: pointer;
51
+ border-bottom: 2px solid transparent;
52
+ margin-bottom: -1px;
53
+ font-size: 0.875rem;
54
+ font-weight: 500;
55
+ }
56
+ .fw-tab:hover { color: inherit; }
57
+ .fw-tab--active {
58
+ color: var(--color-accent, #3b82f6);
59
+ border-bottom-color: var(--color-accent, #3b82f6);
60
+ }
61
+ .fw-tab-content { display: none; }
62
+ .fw-tab-content--active { display: block; }
63
+
64
+ /* Top list */
65
+ .fw-top-list {
66
+ list-style: none;
67
+ padding: 0;
68
+ margin: 0 0 1.5rem 0;
69
+ }
70
+ .fw-top-list li {
71
+ display: flex;
72
+ align-items: center;
73
+ gap: 0.75rem;
74
+ padding: 0.5rem 0;
75
+ border-bottom: 1px solid var(--color-border, #eee);
76
+ }
77
+ .fw-top-list li:last-child { border-bottom: none; }
78
+ .fw-rank {
79
+ width: 1.5rem;
80
+ height: 1.5rem;
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ font-size: 0.75rem;
85
+ font-weight: 600;
86
+ background: var(--color-offset, #f5f5f5);
87
+ border-radius: 50%;
88
+ color: var(--color-text-secondary, #666);
89
+ flex-shrink: 0;
90
+ }
91
+ .fw-name { flex: 1; font-weight: 500; }
92
+ .fw-count {
93
+ font-size: 0.875rem;
94
+ color: var(--color-text-secondary, #666);
95
+ }
96
+
97
+ /* Album grid */
98
+ .fw-album-grid {
99
+ display: grid;
100
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
101
+ gap: 1rem;
102
+ list-style: none;
103
+ padding: 0;
104
+ margin: 0 0 1.5rem 0;
105
+ }
106
+ .fw-album {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 0.5rem;
110
+ }
111
+ .fw-album img {
112
+ width: 100%;
113
+ aspect-ratio: 1;
114
+ object-fit: cover;
115
+ border-radius: 0.25rem;
116
+ }
117
+ .fw-album-placeholder {
118
+ width: 100%;
119
+ aspect-ratio: 1;
120
+ background: var(--color-border, #ddd);
121
+ border-radius: 0.25rem;
122
+ }
123
+ .fw-album-title {
124
+ font-weight: 500;
125
+ font-size: 0.875rem;
126
+ white-space: nowrap;
127
+ overflow: hidden;
128
+ text-overflow: ellipsis;
129
+ }
130
+ .fw-album-artist {
131
+ font-size: 0.75rem;
132
+ color: var(--color-text-secondary, #666);
133
+ white-space: nowrap;
134
+ overflow: hidden;
135
+ text-overflow: ellipsis;
136
+ }
137
+ .fw-album-plays {
138
+ font-size: 0.75rem;
139
+ color: var(--color-text-secondary, #666);
140
+ }
141
+
142
+ /* Chart */
143
+ .fw-chart { margin: 1rem 0 1.5rem 0; }
144
+ .fw-chart-bars {
145
+ display: flex;
146
+ align-items: flex-end;
147
+ gap: 2px;
148
+ height: 120px;
149
+ padding: 0.5rem 0;
150
+ }
151
+ .fw-chart-bar-wrapper {
152
+ flex: 1;
153
+ height: 100%;
154
+ display: flex;
155
+ align-items: flex-end;
156
+ }
157
+ .fw-chart-bar {
158
+ width: 100%;
159
+ background: var(--color-accent, #3b82f6);
160
+ border-radius: 2px 2px 0 0;
161
+ min-height: 2px;
162
+ transition: height 0.3s ease;
163
+ }
164
+ .fw-chart-bar-wrapper:hover .fw-chart-bar {
165
+ background: var(--color-accent-hover, #2563eb);
166
+ }
167
+ .fw-chart-labels {
168
+ display: flex;
169
+ justify-content: space-between;
170
+ font-size: 0.75rem;
171
+ color: var(--color-text-secondary, #666);
172
+ margin-top: 0.5rem;
173
+ }
174
+ </style>
175
+
4
176
  {% if error %}
5
177
  {{ prose({ text: error.message }) }}
6
178
  {% else %}
7
- <section class="funkwhale-section">
179
+ <section class="fw-section">
8
180
  {# Tabs #}
9
- <div class="funkwhale-tabs" role="tablist">
10
- <button class="funkwhale-tab funkwhale-tab--active" role="tab" data-tab="all" aria-selected="true">
181
+ <div class="fw-tabs" role="tablist">
182
+ <button class="fw-tab fw-tab--active" role="tab" data-tab="all" aria-selected="true">
11
183
  {{ __("funkwhale.allTime") }}
12
184
  </button>
13
- <button class="funkwhale-tab" role="tab" data-tab="month" aria-selected="false">
185
+ <button class="fw-tab" role="tab" data-tab="month" aria-selected="false">
14
186
  {{ __("funkwhale.thisMonth") }}
15
187
  </button>
16
- <button class="funkwhale-tab" role="tab" data-tab="week" aria-selected="false">
188
+ <button class="fw-tab" role="tab" data-tab="week" aria-selected="false">
17
189
  {{ __("funkwhale.thisWeek") }}
18
190
  </button>
19
- <button class="funkwhale-tab" role="tab" data-tab="trends" aria-selected="false">
191
+ <button class="fw-tab" role="tab" data-tab="trends" aria-selected="false">
20
192
  {{ __("funkwhale.trends") }}
21
193
  </button>
22
194
  </div>
23
195
 
24
196
  {# All Time Tab #}
25
- <div class="funkwhale-tab-content funkwhale-tab-content--active" id="tab-all" role="tabpanel">
26
- {% set summary = stats.summary.all %}
27
- {% include "partials/stats-summary.njk" %}
197
+ <div class="fw-tab-content fw-tab-content--active" id="tab-all" role="tabpanel">
198
+ {% set s = stats.summary.all %}
199
+ <div class="fw-stats-grid">
200
+ <div class="fw-stat">
201
+ <span class="fw-stat-value">{{ s.totalPlays | default(0) }}</span>
202
+ <span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
203
+ </div>
204
+ <div class="fw-stat">
205
+ <span class="fw-stat-value">{{ s.uniqueTracks | default(0) }}</span>
206
+ <span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
207
+ </div>
208
+ <div class="fw-stat">
209
+ <span class="fw-stat-value">{{ s.uniqueArtists | default(0) }}</span>
210
+ <span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
211
+ </div>
212
+ <div class="fw-stat">
213
+ <span class="fw-stat-value">{{ s.totalDurationFormatted | default("0m") }}</span>
214
+ <span class="fw-stat-label">{{ __("funkwhale.listeningTime") }}</span>
215
+ </div>
216
+ </div>
28
217
 
29
218
  <h3>{{ __("funkwhale.topArtists") }}</h3>
30
- {% set topArtists = stats.topArtists.all %}
31
- {% include "partials/top-artists.njk" %}
219
+ {% if stats.topArtists.all and stats.topArtists.all.length > 0 %}
220
+ <ol class="fw-top-list">
221
+ {% for artist in stats.topArtists.all %}
222
+ <li>
223
+ <span class="fw-rank">{{ loop.index }}</span>
224
+ <span class="fw-name">{{ artist.name }}</span>
225
+ <span class="fw-count">{{ artist.playCount }} {{ __("funkwhale.plays") }}</span>
226
+ </li>
227
+ {% endfor %}
228
+ </ol>
229
+ {% else %}
230
+ {{ prose({ text: "No data yet" }) }}
231
+ {% endif %}
32
232
 
33
233
  <h3>{{ __("funkwhale.topAlbums") }}</h3>
34
- {% set topAlbums = stats.topAlbums.all %}
35
- {% include "partials/top-albums.njk" %}
234
+ {% if stats.topAlbums.all and stats.topAlbums.all.length > 0 %}
235
+ <ul class="fw-album-grid">
236
+ {% for album in stats.topAlbums.all %}
237
+ <li class="fw-album">
238
+ {% if album.coverUrl %}
239
+ <img src="{{ album.coverUrl }}" alt="" loading="lazy">
240
+ {% else %}
241
+ <div class="fw-album-placeholder"></div>
242
+ {% endif %}
243
+ <span class="fw-album-title">{{ album.title }}</span>
244
+ <span class="fw-album-artist">{{ album.artist }}</span>
245
+ <span class="fw-album-plays">{{ album.playCount }} {{ __("funkwhale.plays") }}</span>
246
+ </li>
247
+ {% endfor %}
248
+ </ul>
249
+ {% else %}
250
+ {{ prose({ text: "No data yet" }) }}
251
+ {% endif %}
36
252
  </div>
37
253
 
38
254
  {# This Month Tab #}
39
- <div class="funkwhale-tab-content" id="tab-month" role="tabpanel" hidden>
40
- {% set summary = stats.summary.month %}
41
- {% include "partials/stats-summary.njk" %}
255
+ <div class="fw-tab-content" id="tab-month" role="tabpanel" hidden>
256
+ {% set s = stats.summary.month %}
257
+ <div class="fw-stats-grid">
258
+ <div class="fw-stat">
259
+ <span class="fw-stat-value">{{ s.totalPlays | default(0) }}</span>
260
+ <span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
261
+ </div>
262
+ <div class="fw-stat">
263
+ <span class="fw-stat-value">{{ s.uniqueTracks | default(0) }}</span>
264
+ <span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
265
+ </div>
266
+ <div class="fw-stat">
267
+ <span class="fw-stat-value">{{ s.uniqueArtists | default(0) }}</span>
268
+ <span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
269
+ </div>
270
+ <div class="fw-stat">
271
+ <span class="fw-stat-value">{{ s.totalDurationFormatted | default("0m") }}</span>
272
+ <span class="fw-stat-label">{{ __("funkwhale.listeningTime") }}</span>
273
+ </div>
274
+ </div>
42
275
 
43
276
  <h3>{{ __("funkwhale.topArtists") }}</h3>
44
- {% set topArtists = stats.topArtists.month %}
45
- {% include "partials/top-artists.njk" %}
277
+ {% if stats.topArtists.month and stats.topArtists.month.length > 0 %}
278
+ <ol class="fw-top-list">
279
+ {% for artist in stats.topArtists.month %}
280
+ <li>
281
+ <span class="fw-rank">{{ loop.index }}</span>
282
+ <span class="fw-name">{{ artist.name }}</span>
283
+ <span class="fw-count">{{ artist.playCount }} {{ __("funkwhale.plays") }}</span>
284
+ </li>
285
+ {% endfor %}
286
+ </ol>
287
+ {% else %}
288
+ {{ prose({ text: "No data yet" }) }}
289
+ {% endif %}
46
290
 
47
291
  <h3>{{ __("funkwhale.topAlbums") }}</h3>
48
- {% set topAlbums = stats.topAlbums.month %}
49
- {% include "partials/top-albums.njk" %}
292
+ {% if stats.topAlbums.month and stats.topAlbums.month.length > 0 %}
293
+ <ul class="fw-album-grid">
294
+ {% for album in stats.topAlbums.month %}
295
+ <li class="fw-album">
296
+ {% if album.coverUrl %}
297
+ <img src="{{ album.coverUrl }}" alt="" loading="lazy">
298
+ {% else %}
299
+ <div class="fw-album-placeholder"></div>
300
+ {% endif %}
301
+ <span class="fw-album-title">{{ album.title }}</span>
302
+ <span class="fw-album-artist">{{ album.artist }}</span>
303
+ <span class="fw-album-plays">{{ album.playCount }} {{ __("funkwhale.plays") }}</span>
304
+ </li>
305
+ {% endfor %}
306
+ </ul>
307
+ {% else %}
308
+ {{ prose({ text: "No data yet" }) }}
309
+ {% endif %}
50
310
  </div>
51
311
 
52
312
  {# This Week Tab #}
53
- <div class="funkwhale-tab-content" id="tab-week" role="tabpanel" hidden>
54
- {% set summary = stats.summary.week %}
55
- {% include "partials/stats-summary.njk" %}
313
+ <div class="fw-tab-content" id="tab-week" role="tabpanel" hidden>
314
+ {% set s = stats.summary.week %}
315
+ <div class="fw-stats-grid">
316
+ <div class="fw-stat">
317
+ <span class="fw-stat-value">{{ s.totalPlays | default(0) }}</span>
318
+ <span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
319
+ </div>
320
+ <div class="fw-stat">
321
+ <span class="fw-stat-value">{{ s.uniqueTracks | default(0) }}</span>
322
+ <span class="fw-stat-label">{{ __("funkwhale.tracks") }}</span>
323
+ </div>
324
+ <div class="fw-stat">
325
+ <span class="fw-stat-value">{{ s.uniqueArtists | default(0) }}</span>
326
+ <span class="fw-stat-label">{{ __("funkwhale.artists") }}</span>
327
+ </div>
328
+ <div class="fw-stat">
329
+ <span class="fw-stat-value">{{ s.totalDurationFormatted | default("0m") }}</span>
330
+ <span class="fw-stat-label">{{ __("funkwhale.listeningTime") }}</span>
331
+ </div>
332
+ </div>
56
333
 
57
334
  <h3>{{ __("funkwhale.topArtists") }}</h3>
58
- {% set topArtists = stats.topArtists.week %}
59
- {% include "partials/top-artists.njk" %}
335
+ {% if stats.topArtists.week and stats.topArtists.week.length > 0 %}
336
+ <ol class="fw-top-list">
337
+ {% for artist in stats.topArtists.week %}
338
+ <li>
339
+ <span class="fw-rank">{{ loop.index }}</span>
340
+ <span class="fw-name">{{ artist.name }}</span>
341
+ <span class="fw-count">{{ artist.playCount }} {{ __("funkwhale.plays") }}</span>
342
+ </li>
343
+ {% endfor %}
344
+ </ol>
345
+ {% else %}
346
+ {{ prose({ text: "No data yet" }) }}
347
+ {% endif %}
60
348
 
61
349
  <h3>{{ __("funkwhale.topAlbums") }}</h3>
62
- {% set topAlbums = stats.topAlbums.week %}
63
- {% include "partials/top-albums.njk" %}
350
+ {% if stats.topAlbums.week and stats.topAlbums.week.length > 0 %}
351
+ <ul class="fw-album-grid">
352
+ {% for album in stats.topAlbums.week %}
353
+ <li class="fw-album">
354
+ {% if album.coverUrl %}
355
+ <img src="{{ album.coverUrl }}" alt="" loading="lazy">
356
+ {% else %}
357
+ <div class="fw-album-placeholder"></div>
358
+ {% endif %}
359
+ <span class="fw-album-title">{{ album.title }}</span>
360
+ <span class="fw-album-artist">{{ album.artist }}</span>
361
+ <span class="fw-album-plays">{{ album.playCount }} {{ __("funkwhale.plays") }}</span>
362
+ </li>
363
+ {% endfor %}
364
+ </ul>
365
+ {% else %}
366
+ {{ prose({ text: "No data yet" }) }}
367
+ {% endif %}
64
368
  </div>
65
369
 
66
370
  {# Trends Tab #}
67
- <div class="funkwhale-tab-content" id="tab-trends" role="tabpanel" hidden>
371
+ <div class="fw-tab-content" id="tab-trends" role="tabpanel" hidden>
68
372
  <h3>{{ __("funkwhale.listeningTrend") }}</h3>
69
373
  {% if stats.trends and stats.trends.length > 0 %}
70
- <div class="funkwhale-chart">
374
+ <div class="fw-chart">
71
375
  {% set maxCount = 0 %}
72
376
  {% for day in stats.trends %}
73
377
  {% if day.count > maxCount %}
74
378
  {% set maxCount = day.count %}
75
379
  {% endif %}
76
380
  {% endfor %}
77
- <div class="funkwhale-chart__bars">
381
+ <div class="fw-chart-bars">
78
382
  {% for day in stats.trends %}
79
- <div class="funkwhale-chart__bar-wrapper" title="{{ day.date }}: {{ day.count }} plays">
80
- <div class="funkwhale-chart__bar" style="height: {{ (day.count / maxCount * 100) if maxCount > 0 else 0 }}%"></div>
383
+ <div class="fw-chart-bar-wrapper" title="{{ day.date }}: {{ day.count }} plays">
384
+ <div class="fw-chart-bar" style="height: {{ (day.count / maxCount * 100) if maxCount > 0 else 0 }}%"></div>
81
385
  </div>
82
386
  {% endfor %}
83
387
  </div>
84
- <div class="funkwhale-chart__labels">
388
+ <div class="fw-chart-labels">
85
389
  <span>{{ stats.trends[0].date }}</span>
86
390
  <span>{{ stats.trends[stats.trends.length - 1].date }}</span>
87
391
  </div>
@@ -92,35 +396,33 @@
92
396
  </div>
93
397
  </section>
94
398
 
95
- <div class="button-grid">
96
- {{ button({
97
- classes: "button--secondary",
98
- href: mountPath,
99
- text: "Back to Dashboard"
100
- }) }}
101
- </div>
399
+ {{ button({
400
+ classes: "button--secondary",
401
+ href: mountPath,
402
+ text: "Back to Dashboard"
403
+ }) }}
102
404
 
103
405
  <script>
104
406
  // Simple tab switching
105
- document.querySelectorAll('.funkwhale-tab').forEach(tab => {
407
+ document.querySelectorAll('.fw-tab').forEach(tab => {
106
408
  tab.addEventListener('click', () => {
107
409
  // Update tabs
108
- document.querySelectorAll('.funkwhale-tab').forEach(t => {
109
- t.classList.remove('funkwhale-tab--active');
410
+ document.querySelectorAll('.fw-tab').forEach(t => {
411
+ t.classList.remove('fw-tab--active');
110
412
  t.setAttribute('aria-selected', 'false');
111
413
  });
112
- tab.classList.add('funkwhale-tab--active');
414
+ tab.classList.add('fw-tab--active');
113
415
  tab.setAttribute('aria-selected', 'true');
114
416
 
115
417
  // Update content
116
- document.querySelectorAll('.funkwhale-tab-content').forEach(content => {
117
- content.classList.remove('funkwhale-tab-content--active');
418
+ document.querySelectorAll('.fw-tab-content').forEach(content => {
419
+ content.classList.remove('fw-tab-content--active');
118
420
  content.hidden = true;
119
421
  });
120
422
  const targetId = 'tab-' + tab.dataset.tab;
121
423
  const target = document.getElementById(targetId);
122
424
  if (target) {
123
- target.classList.add('funkwhale-tab-content--active');
425
+ target.classList.add('fw-tab-content--active');
124
426
  target.hidden = false;
125
427
  }
126
428
  });