@byline/admin 4.15.0 → 4.16.0

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.
Files changed (29) hide show
  1. package/dist/abilities.js +2 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/modules/analytics/abilities.d.ts +14 -0
  5. package/dist/modules/analytics/abilities.js +21 -0
  6. package/dist/modules/analytics/components/dashboard.d.ts +20 -0
  7. package/dist/modules/analytics/components/dashboard.js +308 -0
  8. package/dist/modules/analytics/components/dashboard.module.js +29 -0
  9. package/dist/modules/analytics/components/dashboard.test.node.d.ts +8 -0
  10. package/dist/modules/analytics/components/dashboard_module.css +268 -0
  11. package/dist/modules/analytics/components/timeseries.d.ts +59 -0
  12. package/dist/modules/analytics/components/timeseries.js +253 -0
  13. package/dist/modules/analytics/components/timeseries.module.js +17 -0
  14. package/dist/modules/analytics/components/timeseries_module.css +126 -0
  15. package/dist/modules/analytics/index.d.ts +9 -0
  16. package/dist/modules/analytics/index.js +1 -0
  17. package/dist/modules/analytics/types.d.ts +20 -0
  18. package/dist/modules/analytics/types.js +1 -0
  19. package/package.json +17 -5
  20. package/src/abilities.ts +2 -0
  21. package/src/index.ts +1 -0
  22. package/src/modules/analytics/abilities.ts +33 -0
  23. package/src/modules/analytics/components/dashboard.module.css +330 -0
  24. package/src/modules/analytics/components/dashboard.test.node.ts +193 -0
  25. package/src/modules/analytics/components/dashboard.tsx +371 -0
  26. package/src/modules/analytics/components/timeseries.module.css +151 -0
  27. package/src/modules/analytics/components/timeseries.tsx +332 -0
  28. package/src/modules/analytics/index.ts +14 -0
  29. package/src/modules/analytics/types.ts +31 -0
@@ -0,0 +1,126 @@
1
+ .root-L2qL4D {
2
+ border-radius: .25rem;
3
+ flex-direction: column;
4
+ gap: .5rem;
5
+ display: flex;
6
+ }
7
+
8
+ .root-L2qL4D:focus-visible {
9
+ outline: 2px solid var(--primary-500);
10
+ outline-offset: 4px;
11
+ }
12
+
13
+ :is(.chart-giYTrx, .byline-analytics-chart) {
14
+ width: 100%;
15
+ height: 11rem;
16
+ display: block;
17
+ }
18
+
19
+ .gridline-R_wAwP {
20
+ stroke: var(--gray-200);
21
+ stroke-width: 1px;
22
+ }
23
+
24
+ .column-_Cw2Ua {
25
+ transition: opacity .12s;
26
+ }
27
+
28
+ .views-WHbm44 {
29
+ fill: #38bdf8;
30
+ opacity: .38;
31
+ transition: opacity .12s;
32
+ }
33
+
34
+ .columnActive-CwHwbo .views-WHbm44 {
35
+ opacity: .62;
36
+ }
37
+
38
+ .visitors-n6IL4B {
39
+ fill: #7c3aed;
40
+ opacity: .95;
41
+ }
42
+
43
+ .hit-ZxDHvk {
44
+ fill: #0000;
45
+ cursor: crosshair;
46
+ }
47
+
48
+ :is(.axis-fBshTw, .byline-analytics-axis) {
49
+ font-variant-numeric: tabular-nums;
50
+ letter-spacing: .04em;
51
+ color: var(--gray-500);
52
+ justify-content: space-between;
53
+ font-size: .6875rem;
54
+ display: flex;
55
+ }
56
+
57
+ :is(.readout-L9jEpz, .byline-analytics-readout) {
58
+ font-variant-numeric: tabular-nums;
59
+ min-height: 1.25rem;
60
+ color: var(--gray-500);
61
+ flex-wrap: wrap;
62
+ align-items: center;
63
+ gap: .35rem .75rem;
64
+ margin: 0;
65
+ font-size: .75rem;
66
+ display: flex;
67
+ }
68
+
69
+ .readout-L9jEpz strong {
70
+ color: inherit;
71
+ font-weight: 600;
72
+ }
73
+
74
+ .swatchViews-OCeKNe, .swatchVisitors-O3BWz6, .swatchDownloads-SVo9mN {
75
+ border-radius: 1px;
76
+ width: .5rem;
77
+ height: .5rem;
78
+ margin-inline: .4rem -.4rem;
79
+ display: inline-block;
80
+ }
81
+
82
+ .swatchViews-OCeKNe {
83
+ opacity: .55;
84
+ background-color: #38bdf8;
85
+ }
86
+
87
+ .swatchVisitors-O3BWz6 {
88
+ background-color: #7c3aed;
89
+ }
90
+
91
+ .swatchDownloads-SVo9mN {
92
+ background-color: #10b981;
93
+ }
94
+
95
+ :is([data-theme="dark"], .dark) .gridline-R_wAwP {
96
+ stroke: var(--gray-700);
97
+ }
98
+
99
+ :is(:is([data-theme="dark"], .dark) .axis-fBshTw, :is([data-theme="dark"], .dark) .byline-analytics-axis, :is([data-theme="dark"], .dark) .readout-L9jEpz, :is([data-theme="dark"], .dark) .byline-analytics-readout) {
100
+ color: var(--gray-400);
101
+ }
102
+
103
+ :is([data-theme="dark"], .dark) .visitors-n6IL4B {
104
+ fill: #a78bfa;
105
+ }
106
+
107
+ :is([data-theme="dark"], .dark) .swatchVisitors-O3BWz6 {
108
+ background-color: #a78bfa;
109
+ }
110
+
111
+ :is([data-theme="dark"], .dark) .swatchDownloads-SVo9mN {
112
+ background-color: #34d399;
113
+ }
114
+
115
+ @media (prefers-reduced-motion: reduce) {
116
+ .column-_Cw2Ua, .views-WHbm44 {
117
+ transition: none;
118
+ }
119
+ }
120
+
121
+ @media (max-width: 720px) {
122
+ :is(.chart-giYTrx, .byline-analytics-chart) {
123
+ height: 9rem;
124
+ }
125
+ }
126
+
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export { ANALYTICS_ABILITIES, type AnalyticsAbilityKey, registerAnalyticsAbilities, } from './abilities.js';
9
+ export type { AnalyticsDashboardData, AnalyticsDashboardPeriod } from './types.js';
@@ -0,0 +1 @@
1
+ export { ANALYTICS_ABILITIES, registerAnalyticsAbilities } from "./abilities.js";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { AnalyticsCountryTotal, AnalyticsDateRange, AnalyticsPathTotal, AnalyticsRankedTotals, AnalyticsReferrerTotal, AnalyticsReportCoverage, AnalyticsSummary } from '@byline/analytics';
9
+ import type { AnalyticsDashboardPeriod as PortableAnalyticsDashboardPeriod } from '@byline/analytics/config';
10
+ export type AnalyticsDashboardPeriod = PortableAnalyticsDashboardPeriod;
11
+ export interface AnalyticsDashboardData {
12
+ range: AnalyticsDateRange;
13
+ coverage: AnalyticsReportCoverage;
14
+ summary: AnalyticsSummary;
15
+ pages: AnalyticsRankedTotals<AnalyticsPathTotal>;
16
+ downloads: AnalyticsRankedTotals<AnalyticsPathTotal>;
17
+ referrers: AnalyticsRankedTotals<AnalyticsReferrerTotal>;
18
+ /** Countries are unbounded by a top-N limit, so there is nothing to truncate. */
19
+ countries: AnalyticsCountryTotal[];
20
+ }
@@ -0,0 +1 @@
1
+ export { };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/admin",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "4.15.0",
5
+ "version": "4.16.0",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -77,6 +77,16 @@
77
77
  "import": "./dist/modules/admin-preferences/index.js",
78
78
  "require": "./dist/modules/admin-preferences/index.js"
79
79
  },
80
+ "./analytics": {
81
+ "types": "./dist/modules/analytics/index.d.ts",
82
+ "import": "./dist/modules/analytics/index.js",
83
+ "require": "./dist/modules/analytics/index.js"
84
+ },
85
+ "./analytics/components/dashboard": {
86
+ "types": "./dist/modules/analytics/components/dashboard.d.ts",
87
+ "import": "./dist/modules/analytics/components/dashboard.js",
88
+ "require": "./dist/modules/analytics/components/dashboard.js"
89
+ },
80
90
  "./services": {
81
91
  "types": "./dist/services/admin-services-context.d.ts",
82
92
  "import": "./dist/services/admin-services-context.js",
@@ -173,10 +183,12 @@
173
183
  "react-diff-viewer-continued": "^4.4.0",
174
184
  "uuid": "^14.0.2",
175
185
  "zod": "^4.4.3",
176
- "@byline/auth": "4.15.0",
177
- "@byline/core": "4.15.0",
178
- "@byline/ui": "4.15.0",
179
- "@byline/i18n": "4.15.0"
186
+ "@byline/analytics": "4.16.0",
187
+ "@byline/i18n": "4.16.0",
188
+ "@byline/core": "4.16.0",
189
+ "@byline/ui": "4.16.0",
190
+ "@byline/auth": "4.16.0",
191
+ "@byline/analytics-agent": "4.16.0"
180
192
  },
181
193
  "peerDependencies": {
182
194
  "react": "^19.0.0",
package/src/abilities.ts CHANGED
@@ -12,6 +12,7 @@ import { registerAdminActivityAbilities } from './modules/admin-activity/abiliti
12
12
  import { registerAdminPermissionsAbilities } from './modules/admin-permissions/abilities.js'
13
13
  import { registerAdminRolesAbilities } from './modules/admin-roles/abilities.js'
14
14
  import { registerAdminUsersAbilities } from './modules/admin-users/abilities.js'
15
+ import { registerAnalyticsAbilities } from './modules/analytics/abilities.js'
15
16
 
16
17
  /**
17
18
  * Register every ability contributed by the admin subsystem.
@@ -30,5 +31,6 @@ export function registerAdminAbilities(registry: AbilityRegistry): void {
30
31
  registerAdminRolesAbilities(registry)
31
32
  registerAdminPermissionsAbilities(registry)
32
33
  registerAdminActivityAbilities(registry)
34
+ registerAnalyticsAbilities(registry)
33
35
  // registerAccountAbilities(registry) — added when that module lands
34
36
  }
package/src/index.ts CHANGED
@@ -36,5 +36,6 @@ export * from './modules/admin-activity/index.js'
36
36
  export * from './modules/admin-permissions/index.js'
37
37
  export * from './modules/admin-roles/index.js'
38
38
  export * from './modules/admin-users/index.js'
39
+ export * from './modules/analytics/index.js'
39
40
  export * from './modules/auth/index.js'
40
41
  export type { AdminStore } from './store.js'
@@ -0,0 +1,33 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type { AbilityRegistry } from '@byline/auth'
10
+
11
+ export const ANALYTICS_ABILITIES = {
12
+ read: 'analytics.read',
13
+ maintain: 'analytics.maintain',
14
+ } as const
15
+
16
+ export type AnalyticsAbilityKey = (typeof ANALYTICS_ABILITIES)[keyof typeof ANALYTICS_ABILITIES]
17
+
18
+ export function registerAnalyticsAbilities(registry: AbilityRegistry): void {
19
+ registry.register({
20
+ key: ANALYTICS_ABILITIES.read,
21
+ label: 'Read analytics',
22
+ description: 'View installation-level page, visitor, download, referrer, and country totals.',
23
+ group: 'analytics',
24
+ source: 'admin',
25
+ })
26
+ registry.register({
27
+ key: ANALYTICS_ABILITIES.maintain,
28
+ label: 'Maintain analytics',
29
+ description: 'Delete retained analytics events and rebuild affected daily aggregates.',
30
+ group: 'analytics',
31
+ source: 'admin',
32
+ })
33
+ }
@@ -0,0 +1,330 @@
1
+ /**
2
+ * AnalyticsDashboard — the installation-level analytics page.
3
+ *
4
+ * Override handles:
5
+ * .byline-analytics-header — title block and period/exclusion controls
6
+ * .byline-analytics-title — page heading
7
+ * .byline-analytics-help — the daily-uniques qualification
8
+ * .byline-analytics-controls — period picker + exclusion toggle
9
+ * .byline-analytics-stats — the three metric tiles
10
+ * .byline-analytics-stat — one tile
11
+ * .byline-analytics-stat-label — tracked uppercase metric name
12
+ * .byline-analytics-stat-value — the figure
13
+ * .byline-analytics-stat-foot — the figure's qualification
14
+ * .byline-analytics-chart-card — card wrapping the timeseries
15
+ * .byline-analytics-lists — top-pages / referrers+countries band
16
+ * .byline-analytics-list — one ranked-list card
17
+ * .byline-analytics-ranking — the ordered list
18
+ * .byline-analytics-ranking-row — one row, carrying its share bar
19
+ *
20
+ * The tile hues are the collection dashboard's status-tile values
21
+ * (`byline-dashboard-stat-tile`), so the two surfaces read as one system.
22
+ * Amber is deliberately unused here: it already means "draft".
23
+ */
24
+
25
+ .header,
26
+ :global(.byline-analytics-header) {
27
+ display: flex;
28
+ align-items: flex-end;
29
+ justify-content: space-between;
30
+ gap: 1rem 1.5rem;
31
+ flex-wrap: wrap;
32
+ margin-bottom: 1.25rem;
33
+ padding-bottom: 1rem;
34
+ border-bottom: var(--border-width-thin) var(--border-style-solid) var(--gray-200);
35
+ }
36
+
37
+ .title,
38
+ :global(.byline-analytics-title) {
39
+ margin: 0;
40
+ font-size: 1.875rem;
41
+ font-weight: 650;
42
+ letter-spacing: -0.02em;
43
+ }
44
+
45
+ .help,
46
+ :global(.byline-analytics-help) {
47
+ max-width: 48ch;
48
+ margin: 0.35rem 0 0;
49
+ font-size: 0.8125rem;
50
+ }
51
+
52
+ /* `Card.Title` defaults to 1.8rem bold — larger than this page's own heading,
53
+ which inverted the hierarchy. Bring the section titles below the h1 while
54
+ keeping them clearly headings. Scoped to the analytics cards so no other
55
+ Card consumer shifts. */
56
+ .chartCard :global(.byline-card-title),
57
+ .list :global(.byline-card-title) {
58
+ font-size: 1.25rem;
59
+ font-weight: 600;
60
+ letter-spacing: normal;
61
+ }
62
+
63
+ .chartCard :global(.byline-card-description),
64
+ .list :global(.byline-card-description) {
65
+ font-size: 0.75rem;
66
+ }
67
+
68
+ .controls,
69
+ :global(.byline-analytics-controls) {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 0.75rem;
73
+ flex-wrap: wrap;
74
+ }
75
+
76
+ /* ── Metric tiles ──────────────────────────────────────────────────── */
77
+
78
+ .stats,
79
+ :global(.byline-analytics-stats) {
80
+ display: grid;
81
+ grid-template-columns: repeat(3, minmax(0, 1fr));
82
+ gap: 0.75rem;
83
+ margin-bottom: 0.75rem;
84
+ }
85
+
86
+ .stat,
87
+ :global(.byline-analytics-stat) {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: 0.5rem;
91
+ min-width: 0;
92
+ padding: 1rem 1.125rem 1.125rem;
93
+ border-radius: 0.375rem;
94
+ }
95
+
96
+ .statLabel,
97
+ :global(.byline-analytics-stat-label) {
98
+ font-size: 0.625rem;
99
+ font-weight: 600;
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.1em;
102
+ line-height: 1;
103
+ }
104
+
105
+ .statValue,
106
+ :global(.byline-analytics-stat-value) {
107
+ font-size: clamp(1.75rem, 4.5vw, 2.5rem);
108
+ font-weight: 700;
109
+ font-variant-numeric: tabular-nums;
110
+ line-height: 1;
111
+ letter-spacing: -0.02em;
112
+ }
113
+
114
+ .statFoot,
115
+ :global(.byline-analytics-stat-foot) {
116
+ font-size: 0.6875rem;
117
+ font-variant-numeric: tabular-nums;
118
+ color: var(--gray-600);
119
+ }
120
+
121
+ .toneViews {
122
+ background-color: #f0f9ff;
123
+ }
124
+
125
+ .toneViews .statLabel,
126
+ .toneViews .statValue {
127
+ color: #0369a1;
128
+ }
129
+
130
+ .toneVisitors {
131
+ background-color: #f5f3ff;
132
+ }
133
+
134
+ .toneVisitors .statLabel,
135
+ .toneVisitors .statValue {
136
+ color: #6d28d9;
137
+ }
138
+
139
+ .toneDownloads {
140
+ background-color: #ecfdf5;
141
+ }
142
+
143
+ .toneDownloads .statLabel,
144
+ .toneDownloads .statValue {
145
+ color: #047857;
146
+ }
147
+
148
+ :is([data-theme="dark"], :global(.dark)) .statFoot,
149
+ :is([data-theme="dark"], :global(.dark)) :global(.byline-analytics-stat-foot) {
150
+ color: var(--gray-400);
151
+ }
152
+
153
+ :is([data-theme="dark"], :global(.dark)) .toneViews {
154
+ background-color: rgba(12, 74, 110, 0.28);
155
+ }
156
+
157
+ :is([data-theme="dark"], :global(.dark)) .toneViews .statLabel,
158
+ :is([data-theme="dark"], :global(.dark)) .toneViews .statValue {
159
+ color: #7dd3fc;
160
+ }
161
+
162
+ :is([data-theme="dark"], :global(.dark)) .toneVisitors {
163
+ background-color: rgba(76, 29, 149, 0.3);
164
+ }
165
+
166
+ :is([data-theme="dark"], :global(.dark)) .toneVisitors .statLabel,
167
+ :is([data-theme="dark"], :global(.dark)) .toneVisitors .statValue {
168
+ color: #c4b5fd;
169
+ }
170
+
171
+ :is([data-theme="dark"], :global(.dark)) .toneDownloads {
172
+ background-color: rgba(6, 78, 59, 0.28);
173
+ }
174
+
175
+ :is([data-theme="dark"], :global(.dark)) .toneDownloads .statLabel,
176
+ :is([data-theme="dark"], :global(.dark)) .toneDownloads .statValue {
177
+ color: #6ee7b7;
178
+ }
179
+
180
+ /* ── Lists ─────────────────────────────────────────────────────────── */
181
+
182
+ .chartCard,
183
+ :global(.byline-analytics-chart-card) {
184
+ margin-bottom: 0.75rem;
185
+ }
186
+
187
+ /* Top pages is the list people actually read, so it takes the wide track. */
188
+ .lists,
189
+ :global(.byline-analytics-lists) {
190
+ display: grid;
191
+ grid-template-columns: 1.55fr 1fr;
192
+ align-items: start;
193
+ gap: 0.75rem;
194
+ margin-bottom: 0.75rem;
195
+ }
196
+
197
+ .stack {
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 0.75rem;
201
+ }
202
+
203
+ .ranking,
204
+ :global(.byline-analytics-ranking) {
205
+ display: flex;
206
+ flex-direction: column;
207
+ gap: 1px;
208
+ padding: 0;
209
+ margin: 0;
210
+ list-style: none;
211
+ }
212
+
213
+ .rankingRow,
214
+ :global(.byline-analytics-ranking-row) {
215
+ position: relative;
216
+ display: grid;
217
+ grid-template-columns: minmax(0, 1fr) auto auto;
218
+ align-items: center;
219
+ gap: 0.75rem;
220
+ padding: 0.45rem 0.625rem;
221
+ border-radius: 0.25rem;
222
+ /* The row paints its own stacking context so the share bar can sit behind
223
+ the text without escaping to the card background. */
224
+ isolation: isolate;
225
+ }
226
+
227
+ .rankingRow::before {
228
+ content: "";
229
+ position: absolute;
230
+ inset: 0 auto 0 0;
231
+ z-index: -1;
232
+ width: var(--byline-analytics-share, 0%);
233
+ border-radius: 0.25rem;
234
+ background-color: var(--byline-analytics-bar, transparent);
235
+ opacity: 0.16;
236
+ }
237
+
238
+ .barViews {
239
+ --byline-analytics-bar: #38bdf8;
240
+ }
241
+
242
+ .barVisitors {
243
+ --byline-analytics-bar: #7c3aed;
244
+ }
245
+
246
+ .barDownloads {
247
+ --byline-analytics-bar: #10b981;
248
+ }
249
+
250
+ /* The reserved overflow bucket is an aggregate, not a page — a dashed rule
251
+ and muted label keep it from reading as traffic to a real path. */
252
+ .rankingOverflow {
253
+ border: var(--border-width-thin) dashed var(--gray-300);
254
+ }
255
+
256
+ .rankingOverflow::before {
257
+ opacity: 0.07;
258
+ }
259
+
260
+ .rankingOverflow .rankingLabel {
261
+ font-style: italic;
262
+ color: var(--gray-600);
263
+ }
264
+
265
+ .rankingLabel {
266
+ overflow: hidden;
267
+ min-width: 0;
268
+ font-size: 0.8125rem;
269
+ text-overflow: ellipsis;
270
+ white-space: nowrap;
271
+ }
272
+
273
+ .rankingValue {
274
+ font-size: 0.8125rem;
275
+ font-weight: 600;
276
+ font-variant-numeric: tabular-nums;
277
+ }
278
+
279
+ .rankingVisitors {
280
+ min-width: 3ch;
281
+ font-size: 0.6875rem;
282
+ font-variant-numeric: tabular-nums;
283
+ text-align: end;
284
+ color: var(--gray-500);
285
+ }
286
+
287
+ :is([data-theme="dark"], :global(.dark)) .header,
288
+ :is([data-theme="dark"], :global(.dark)) :global(.byline-analytics-header) {
289
+ border-bottom-color: var(--gray-700);
290
+ }
291
+
292
+ :is([data-theme="dark"], :global(.dark)) .rankingVisitors {
293
+ color: var(--gray-400);
294
+ }
295
+
296
+ :is([data-theme="dark"], :global(.dark)) .rankingOverflow {
297
+ border-color: var(--gray-600);
298
+ }
299
+
300
+ :is([data-theme="dark"], :global(.dark)) .rankingOverflow .rankingLabel {
301
+ color: var(--gray-400);
302
+ }
303
+
304
+ :is([data-theme="dark"], :global(.dark)) .barVisitors {
305
+ --byline-analytics-bar: #a78bfa;
306
+ }
307
+
308
+ :is([data-theme="dark"], :global(.dark)) .barDownloads {
309
+ --byline-analytics-bar: #34d399;
310
+ }
311
+
312
+ @media (max-width: 900px) {
313
+ .lists,
314
+ :global(.byline-analytics-lists) {
315
+ grid-template-columns: 1fr;
316
+ }
317
+ }
318
+
319
+ @media (max-width: 720px) {
320
+ .header,
321
+ :global(.byline-analytics-header) {
322
+ align-items: stretch;
323
+ flex-direction: column;
324
+ }
325
+
326
+ .stats,
327
+ :global(.byline-analytics-stats) {
328
+ grid-template-columns: 1fr;
329
+ }
330
+ }