@liiift-studio/sanity-visitor-insights 0.27.0 → 0.28.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.
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +71 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +75 -46
- package/dist/index.mjs.map +1 -1
- package/dist/{ranges-_SBvGBJx.d.mts → ranges-D1A9JY7h.d.mts} +33 -9
- package/dist/{ranges-_SBvGBJx.d.ts → ranges-D1A9JY7h.d.ts} +33 -9
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +96 -23
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +96 -23
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
- package/src/reportData.ts +33 -9
- package/src/server/reports/acquisition.ts +161 -16
- package/src/server/reports/measurementHealth.ts +41 -10
- package/src/server/reports/reports.test.ts +5 -1
- package/src/studio/CrossSourceTimeline.tsx +18 -4
- package/src/studio/Figure.tsx +19 -4
- package/src/studio/VisitorInsightsTool.tsx +3 -3
- package/src/studio/panels.test.tsx +156 -19
- package/src/studio/panels.tsx +98 -48
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as sanity from 'sanity';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-
|
|
4
|
-
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-D1A9JY7h.mjs';
|
|
4
|
+
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-D1A9JY7h.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
|
@@ -117,7 +117,7 @@ declare function formatCount(value: number): string;
|
|
|
117
117
|
declare function formatPercent(ratio: number, digits?: number): string;
|
|
118
118
|
/** Props for MetricFigure. */
|
|
119
119
|
/** Format a money value in the site's currency, falling back to a plain number. */
|
|
120
|
-
declare function formatMoney(value: number, currency: string | null): string;
|
|
120
|
+
declare function formatMoney(value: number, currency: string | null, fractionDigits?: 0 | 2): string;
|
|
121
121
|
/** Props for Delta. */
|
|
122
122
|
interface DeltaProps {
|
|
123
123
|
/** This period's value, or null when it could not be measured. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as sanity from 'sanity';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-
|
|
4
|
-
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-D1A9JY7h.js';
|
|
4
|
+
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-D1A9JY7h.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
|
@@ -117,7 +117,7 @@ declare function formatCount(value: number): string;
|
|
|
117
117
|
declare function formatPercent(ratio: number, digits?: number): string;
|
|
118
118
|
/** Props for MetricFigure. */
|
|
119
119
|
/** Format a money value in the site's currency, falling back to a plain number. */
|
|
120
|
-
declare function formatMoney(value: number, currency: string | null): string;
|
|
120
|
+
declare function formatMoney(value: number, currency: string | null, fractionDigits?: 0 | 2): string;
|
|
121
121
|
/** Props for Delta. */
|
|
122
122
|
interface DeltaProps {
|
|
123
123
|
/** This period's value, or null when it could not be measured. */
|
package/dist/index.js
CHANGED
|
@@ -219,19 +219,25 @@ var REASON_TEXT = {
|
|
|
219
219
|
source_error: "Source did not respond",
|
|
220
220
|
not_applicable: "Does not apply to this site"
|
|
221
221
|
};
|
|
222
|
+
var LOCALE = "en-GB";
|
|
222
223
|
function formatCount(value) {
|
|
223
|
-
return new Intl.NumberFormat(
|
|
224
|
+
return new Intl.NumberFormat(LOCALE).format(Math.round(value));
|
|
224
225
|
}
|
|
225
226
|
function formatPercent(ratio, digits = 0) {
|
|
226
227
|
return `${(ratio * 100).toFixed(digits)}%`;
|
|
227
228
|
}
|
|
228
|
-
function formatMoney(value, currency) {
|
|
229
|
+
function formatMoney(value, currency, fractionDigits = 0) {
|
|
229
230
|
if (!currency) return formatCount(Math.round(value));
|
|
230
231
|
try {
|
|
231
|
-
return new Intl.NumberFormat(
|
|
232
|
+
return new Intl.NumberFormat(LOCALE, {
|
|
232
233
|
style: "currency",
|
|
233
234
|
currency,
|
|
234
|
-
|
|
235
|
+
// One precision for the whole column, chosen by the caller, not by each value's own size.
|
|
236
|
+
// Switching at 1,000 put "$850.00" and "$1,200" in adjacent rows, and printed an axis
|
|
237
|
+
// reading "$1,000" over a baseline reading "$0.00" — the mismatch the timeline's own
|
|
238
|
+
// comment claims to have fixed by routing both ends through this function.
|
|
239
|
+
maximumFractionDigits: fractionDigits,
|
|
240
|
+
minimumFractionDigits: fractionDigits
|
|
235
241
|
}).format(value);
|
|
236
242
|
} catch {
|
|
237
243
|
return `${formatCount(Math.round(value))} ${currency}`;
|
|
@@ -1203,7 +1209,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1203
1209
|
}
|
|
1204
1210
|
const sentences = [];
|
|
1205
1211
|
if (excess > 0) {
|
|
1206
|
-
sentences.push(`${row.shortfall.source} counted ${formatValue(Math.round(excess), row.unit, currency)}
|
|
1212
|
+
sentences.push(`${row.shortfall.source} counted ${formatValue(Math.round(excess), row.unit, currency)} more ${row.label.toLowerCase()} than ${row.source} on some days, which usually means a tag firing twice rather than extra traffic.`);
|
|
1207
1213
|
}
|
|
1208
1214
|
if (gap <= 0) return sentences;
|
|
1209
1215
|
const total = `${row.shortfall.source} missed ${formatValue(Math.round(gap), row.unit, currency)} ${row.label.toLowerCase()} over this period`;
|
|
@@ -1222,7 +1228,6 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1222
1228
|
setHoverIndex(null);
|
|
1223
1229
|
setBrushAnchor(null);
|
|
1224
1230
|
setBrushed(null);
|
|
1225
|
-
setTooShort(false);
|
|
1226
1231
|
},
|
|
1227
1232
|
onPointerDown: (event) => {
|
|
1228
1233
|
if (!onBrush) return;
|
|
@@ -1274,7 +1279,8 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1274
1279
|
event.preventDefault();
|
|
1275
1280
|
setHoverIndex(next);
|
|
1276
1281
|
if (event.shiftKey && onBrush) {
|
|
1277
|
-
const anchor =
|
|
1282
|
+
const anchor = brushAnchor ?? current;
|
|
1283
|
+
if (brushAnchor === null) setBrushAnchor(current);
|
|
1278
1284
|
setBrushed([Math.min(anchor, next), Math.max(anchor, next)]);
|
|
1279
1285
|
}
|
|
1280
1286
|
},
|
|
@@ -1296,7 +1302,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1296
1302
|
const pitch = plotWidth / Math.max(1, dates.length - 1);
|
|
1297
1303
|
const stemWidth = Math.max(1, Math.min(9, pitch > 2 ? pitch - 1 : pitch));
|
|
1298
1304
|
const stems = row.mark === "events" ? row.points.filter((p) => p.value !== null && p.value !== 0) : [];
|
|
1299
|
-
const zeroes = row.mark === "events" ? row.points.filter((p) => p.value === 0) : [];
|
|
1305
|
+
const zeroes = row.mark === "events" && pitch >= 6 ? row.points.filter((p) => p.value === 0) : [];
|
|
1300
1306
|
const gap = row.shortfall ? gapGen(row.points) ?? "" : "";
|
|
1301
1307
|
const shortfallLine = row.shortfall && revealed ? lineGen(shortfallPoints) ?? "" : "";
|
|
1302
1308
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
@@ -1672,7 +1678,7 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
1672
1678
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1673
1679
|
ChartData,
|
|
1674
1680
|
{
|
|
1675
|
-
note: "A negative miss means GA4 counted
|
|
1681
|
+
note: "A negative miss means GA4 counted more than Vercel that day \u2014 usually a tag firing twice, not extra traffic.",
|
|
1676
1682
|
label: "Show these figures as a table",
|
|
1677
1683
|
rows: data.crossSource ?? [],
|
|
1678
1684
|
rowKey: (d) => d.date,
|
|
@@ -2031,16 +2037,23 @@ function AcquisitionPanel({ data, previous }) {
|
|
|
2031
2037
|
* total and the money column is empty.
|
|
2032
2038
|
*/
|
|
2033
2039
|
key: "brought",
|
|
2034
|
-
|
|
2040
|
+
// The hedge belongs on the SPLIT, not on the money: the total is exact and the
|
|
2041
|
+
// division of it is the estimate. "(est.)" on a revenue header implied the reverse.
|
|
2042
|
+
label: "Revenue, split",
|
|
2035
2043
|
numeric: true,
|
|
2036
2044
|
sortValue: (row) => finiteOrNull(row.revenueShare),
|
|
2037
|
-
exportValue: (row) =>
|
|
2045
|
+
exportValue: (row) => {
|
|
2046
|
+
const money = finiteOrNull(row.apportionedRevenue);
|
|
2047
|
+
return money === null ? null : formatMoney(money, data.currency ?? null);
|
|
2048
|
+
},
|
|
2038
2049
|
render: (row) => {
|
|
2039
|
-
|
|
2050
|
+
const share = finiteOrNull(row.revenueShare);
|
|
2051
|
+
if (share === null || share === 0) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "\u2014" });
|
|
2052
|
+
const money = finiteOrNull(row.apportionedRevenue);
|
|
2040
2053
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 1, children: [
|
|
2041
|
-
|
|
2054
|
+
money !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatMoney(money, data.currency ?? null) }),
|
|
2042
2055
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
2043
|
-
formatPercent(
|
|
2056
|
+
formatPercent(share, 0),
|
|
2044
2057
|
" of tracked sales"
|
|
2045
2058
|
] })
|
|
2046
2059
|
] });
|
|
@@ -2049,21 +2062,27 @@ function AcquisitionPanel({ data, previous }) {
|
|
|
2049
2062
|
]
|
|
2050
2063
|
}
|
|
2051
2064
|
),
|
|
2052
|
-
data.
|
|
2065
|
+
data.splitIsSound === true && finiteOrNull(data.actualRevenue) !== null && // Says exactly what was combined and what was estimated. The money in that column
|
|
2053
2066
|
// is Sanity's real total spread across GA4's split — right in scale, and derived,
|
|
2054
2067
|
// which is a different claim from measured. Saying so here is what makes the
|
|
2055
2068
|
// column usable rather than another figure to distrust.
|
|
2056
2069
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
2057
2070
|
"Revenue is ",
|
|
2058
|
-
formatMoney(data.actualRevenue, data.currency ?? null),
|
|
2071
|
+
formatMoney(finiteOrNull(data.actualRevenue) ?? 0, data.currency ?? null),
|
|
2059
2072
|
" from",
|
|
2060
2073
|
" ",
|
|
2061
|
-
data.actualOrders === null ? "your orders" : `${formatCount(data.actualOrders)} orders`,
|
|
2074
|
+
finiteOrNull(data.actualOrders) === null ? "your orders" : `${formatCount(data.actualOrders)} orders`,
|
|
2062
2075
|
" in Sanity, split across the ",
|
|
2063
|
-
formatCount(data.trackedPurchases),
|
|
2076
|
+
formatCount(data.trackedPurchases ?? 0),
|
|
2064
2077
|
" purchase",
|
|
2065
2078
|
data.trackedPurchases === 1 ? "" : "s",
|
|
2066
|
-
" Google Analytics attributed to a source. Google Analytics sees a fraction of your traffic,
|
|
2079
|
+
" Google Analytics attributed to a source. Google Analytics sees a fraction of your traffic, so treat this as a proportion with a real total behind it rather than measured takings per channel. Two things to hold in mind: it credits whichever source a buyer arrived from on the visit they bought, which for a considered purchase is often a bookmark rather than the blog that first sent them; and its loss is not perfectly even across channels \u2014 ad-blocking runs high among designers, so earned referrals and direct visits are likelier to be under-credited than paid clicks.",
|
|
2080
|
+
finiteOrNull(data.shownPurchases) !== null && (data.shownPurchases ?? 0) < (data.trackedPurchases ?? 0) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2081
|
+
" ",
|
|
2082
|
+
"The rows above account for ",
|
|
2083
|
+
formatCount(data.shownPurchases ?? 0),
|
|
2084
|
+
" of those sales; the rest came from sources outside this list, so this column does not add up to the whole."
|
|
2085
|
+
] })
|
|
2067
2086
|
] })
|
|
2068
2087
|
] }),
|
|
2069
2088
|
data.rowsWithheld && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(NoticeList, { notices: ["GA4 withheld some low-traffic rows for privacy, so this list is shorter than reality."] })
|
|
@@ -2146,24 +2165,31 @@ function JourneyPanel({ data }) {
|
|
|
2146
2165
|
] })
|
|
2147
2166
|
] });
|
|
2148
2167
|
}
|
|
2149
|
-
function
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
const
|
|
2153
|
-
|
|
2168
|
+
function catalogueRate(rows) {
|
|
2169
|
+
let views = 0;
|
|
2170
|
+
let sales = 0;
|
|
2171
|
+
for (const row of rows) {
|
|
2172
|
+
const viewed = metricSortValue(row.viewed);
|
|
2173
|
+
const bought = metricSortValue(row.bought);
|
|
2174
|
+
if (viewed === null || bought === null) continue;
|
|
2175
|
+
views += viewed;
|
|
2176
|
+
sales += bought;
|
|
2177
|
+
}
|
|
2178
|
+
return views > 0 && sales > 0 ? sales / views : null;
|
|
2154
2179
|
}
|
|
2155
|
-
function buyRateIndex(row,
|
|
2180
|
+
function buyRateIndex(row, benchmark) {
|
|
2156
2181
|
const rate = row.buyRate;
|
|
2157
|
-
if (
|
|
2158
|
-
|
|
2182
|
+
if (benchmark === null || benchmark <= 0) return null;
|
|
2183
|
+
if (rate === null || rate === void 0 || !Number.isFinite(rate) || rate < 0) return null;
|
|
2184
|
+
return rate / benchmark;
|
|
2159
2185
|
}
|
|
2160
2186
|
function TypefaceInterestPanel({ data }) {
|
|
2161
|
-
const
|
|
2187
|
+
const benchmark = catalogueRate(data.rows ?? []);
|
|
2162
2188
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
|
|
2163
2189
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: data.interpretationNote }) }),
|
|
2164
2190
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2165
2191
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Heading, { size: 1, style: sectionHeading, children: "Engagement by typeface" }),
|
|
2166
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Sort by any column. \u201CSells vs catalogue\u201D compares each family\u2019s
|
|
2192
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Sort by any column. \u201CSells vs catalogue\u201D compares each family\u2019s sales-per-view against the catalogue as a whole \u2014 1.0\xD7 is average, and sorting up finds the families that are looked at and do not sell. It is a comparison between your families, not the share of visitors who buy: Google Analytics sees only a fraction of the views, which moves every family together and so cancels out here. With one or two sales a family will still swing a long way, so read it alongside the order counts beside it." }),
|
|
2167
2193
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2168
2194
|
SortableTable,
|
|
2169
2195
|
{
|
|
@@ -2244,24 +2270,26 @@ function TypefaceInterestPanel({ data }) {
|
|
|
2244
2270
|
key: "buyRate",
|
|
2245
2271
|
label: "Sells vs catalogue",
|
|
2246
2272
|
numeric: true,
|
|
2247
|
-
|
|
2273
|
+
// The INDEX, so a row rendered as a dash cannot sort above a row with a value.
|
|
2274
|
+
// Sorting on the raw rate returned 0 — a real number — for a family whose
|
|
2275
|
+
// cell showed "—", so ascending put a block of dashes at the top of the
|
|
2276
|
+
// table, which is the one thing this file's header forbids.
|
|
2277
|
+
sortValue: (row) => buyRateIndex(row, benchmark),
|
|
2248
2278
|
exportValue: (row) => {
|
|
2249
|
-
const index = buyRateIndex(row,
|
|
2250
|
-
return index === null ? null : `${index.toFixed(1)}x`;
|
|
2279
|
+
const index = buyRateIndex(row, benchmark);
|
|
2280
|
+
return index === null ? null : `${index.toFixed(1)}x catalogue`;
|
|
2251
2281
|
},
|
|
2252
2282
|
render: (row) => {
|
|
2253
|
-
const index = buyRateIndex(row,
|
|
2283
|
+
const index = buyRateIndex(row, benchmark);
|
|
2254
2284
|
if (index === null) {
|
|
2255
2285
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, "aria-label": `${row.typeface} not comparable`, children: "\u2014" });
|
|
2256
2286
|
}
|
|
2287
|
+
if (index === 0) {
|
|
2288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: "No sales" });
|
|
2289
|
+
}
|
|
2257
2290
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, children: [
|
|
2258
2291
|
index.toFixed(1),
|
|
2259
|
-
"\xD7"
|
|
2260
|
-
" ",
|
|
2261
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { opacity: 0.6, fontSize: "0.85em" }, children: [
|
|
2262
|
-
index >= 1.15 ? "above" : index <= 0.85 ? "below" : "at",
|
|
2263
|
-
" median"
|
|
2264
|
-
] })
|
|
2292
|
+
"\xD7 catalogue"
|
|
2265
2293
|
] });
|
|
2266
2294
|
}
|
|
2267
2295
|
},
|
|
@@ -2608,9 +2636,10 @@ function CoverageRibbon({ ratio }) {
|
|
|
2608
2636
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Card, { padding: 3, radius: 2, tone: ratio > 0.6 ? "critical" : "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Text, { size: 1, children: [
|
|
2609
2637
|
"Every figure on this tab comes from Google Analytics, which is seeing about ",
|
|
2610
2638
|
seen,
|
|
2611
|
-
"% of this site\u2019s traffic in this window
|
|
2612
|
-
|
|
2613
|
-
|
|
2639
|
+
"% of this site\u2019s traffic in this window \u2014 so the real numbers are roughly",
|
|
2640
|
+
" ",
|
|
2641
|
+
seen > 0 ? `${(100 / seen).toFixed(1)}\xD7` : "many times",
|
|
2642
|
+
" these. Data health explains why."
|
|
2614
2643
|
] }) });
|
|
2615
2644
|
}
|
|
2616
2645
|
function ReadyReport({
|