diff --git a/frontend/src/AnalyticsView.vue b/frontend/src/AnalyticsView.vue
index 504a5bb..8d885ad 100644
--- a/frontend/src/AnalyticsView.vue
+++ b/frontend/src/AnalyticsView.vue
@@ -26,6 +26,11 @@ import TrailLink from './TrailLink.vue'
import VisitorCell from './VisitorCell.vue'
import TransitionGraph from './TransitionGraph.vue'
import VisitorCharts from './VisitorCharts.vue'
+import { VIEW_W } from './analytics/chart.js'
+
+// Same centering margin as the charts, so the totals row's left edge
+// aligns with the chart svg above the natural width.
+const CHART_MARGIN = `max(0px, calc(50% - ${VIEW_W / 2}px))`
const ABUSE_MAX_LINES = 5
@@ -148,7 +153,7 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c
⚠️ {{ error }}
loading…
-
+
{{ formatCount(visits.length) }} visits
{{ formatCount(totalViews) }} page views
{{ readStats.avgMinPerVisit }} min/visit
@@ -288,6 +293,8 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c
width: 100%;
/* Same 1.25rem side spacing as main's article padding. */
padding: 1.5rem 1.25rem 4rem;
+ /* Container for cqw-based shrink-to-fit (see .totals). */
+ container-type: inline-size;
}
.analytics-panel header {
@@ -354,12 +361,15 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c
.analytics-view :deep(.muted) { color: var(--muted); }
.analytics-view :deep(.small) { font-size: 0.75em; }
+/* One line at any width: the gap shrinks first, then the font (the number
+ scales along in em), both following the panel's container width. */
.totals {
display: flex;
- gap: 2rem;
- font-size: 1.1rem;
+ gap: clamp(0.5rem, 3cqw, 2rem);
+ font-size: clamp(0.6rem, 2.2cqw, 1.1rem);
+ white-space: nowrap;
}
-.totals strong { font-size: 1.5rem; }
+.totals strong { font-size: 1.36em; }
.visit-table-wrap {
overflow-x: auto;
diff --git a/frontend/src/VisitorCharts.vue b/frontend/src/VisitorCharts.vue
index 01f9b10..f9ce7a9 100644
--- a/frontend/src/VisitorCharts.vue
+++ b/frontend/src/VisitorCharts.vue
@@ -16,6 +16,11 @@ import {
const DAY_REFRESH_MS = 15000
+// Keep the whole svg within page bounds: full width below the natural
+// size, centered with equal side margins above it (max() clamps the
+// centering margin to 0 at the breakpoint, so the rule is continuous).
+const CHART_MARGIN = `max(0px, calc(50% - ${VIEW_W / 2}px))`
+
const props = defineProps({
data: { type: Object, default: null },
range: { type: String, required: true },
@@ -68,6 +73,7 @@ const viewChart = computed(() => buildChart(viewSeries.value, now.value))
]" :key="c.ylabel">