dynalitics-dashboard 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dynalitics contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ # dynalitics-dashboard
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The browser dashboard for [dynalitics](https://www.npmjs.com/package/dynalitics). It talks **only** to the dynalitics read API (`dynalitics/handler`) that you mount in your server; it never sees a database or a credential. It comes in two forms:
4
+
5
+ | Import | What it is | Use it when |
6
+ | ---------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------ |
7
+ | `dynalitics-dashboard/html` | `dashboardHtml`: one self-contained HTML page as a string (inline CSS, JS, SVG) | you want a dashboard page with no frontend work |
8
+ | `dynalitics-dashboard/react` | React 18/19 components: the whole dashboard or single widgets | you want it inside your own React admin, or need header auth |
9
+
10
+ > **Early release (`0.1.x`).** The HTML page is unit-tested against a typed mock of the read API and has an end-to-end run against the real handler on PostgreSQL and MySQL (`npm run e2e`, needs Docker and Playwright, not run in CI). It has not run against SQL Server, and only in Chromium. The React components are tested against the typed mock only.
11
+
12
+ ```sh
13
+ npm install dynalitics-dashboard # no runtime dependencies; react/react-dom are optional peers
14
+ ```
15
+
16
+ ## What it shows
17
+
18
+ **Overview** (both forms):
19
+
20
+ - **Visits over time**: total visits per hour, day or week, optionally split by a field you declared with `breakdown: true`.
21
+ - **Top pages**: the most visited pages and their share.
22
+ - **New vs returning**: new, returning and unknown visitors, with an "approximate" caveat when IP is part of the identity.
23
+ - **Consent**: granted vs declined over time.
24
+ - **Recent visits**: the newest rows, with personal values masked unless the server allows revealing them, and "Load more" paging.
25
+
26
+ **Explorer** (HTML page only for now; not yet in the React components): a search-first view for digging into individual visits, Grafana/Splunk style.
27
+
28
+ - A **query bar** with a small query language: `pricing plan:pro -consent:false page:/blog*` (the syntax is in the page's "Syntax" panel and in the [`dynalitics` README](https://www.npmjs.com/package/dynalitics#the-explorers-query-language-q)). Matching is case-insensitive. Personal fields (IP, email, username, user id) can never be searched.
29
+ - **Matches over time**: a histogram of matching visits; click or press Enter on a day to zoom into its hours.
30
+ - **Split by** any groupable field, to stack the histogram by value.
31
+ - **Fields**: the top values of each field among the matches. Click a value to filter on it, `−` to exclude it.
32
+ - **Matching visits**: the same table as Recent visits, with the match count.
33
+ - **Live**: refresh every 5, 15 or 30 seconds. New rows are marked. Polling pauses while the tab is hidden, and stops on an error.
34
+ - The query, split, range and live setting are kept in the URL, so a view can be shared as a link.
35
+
36
+ Everything else is shared: date ranges (24h, 7d, 30d, 90d; only those the server allows, and at most 31 days in the explorer by default), light and dark themes, all times in UTC, a table alternative for every chart, keyboard exploration of charts, `prefers-reduced-motion` support. Widgets that your configuration cannot support say "not available" instead of failing, and every widget handles its own loading, empty, error and timeout state.
37
+
38
+ ## The HTML page
39
+
40
+ Pass the string to `createHandler`, which serves it at `GET {basePath}/`:
41
+
42
+ ```ts
43
+ import { createHandler, toNodeListener } from 'dynalitics/handler';
44
+ import { dashboardHtml } from 'dynalitics-dashboard/html';
45
+
46
+ const handler = createHandler(readerInstance, {
47
+ authorize: (req) => isAdmin(req.raw),
48
+ basePath: '/admin/analytics',
49
+ dashboardHtml,
50
+ scope: 'none',
51
+ });
52
+ app.use('/admin/analytics', toNodeListener(handler)); // open /admin/analytics/ in a browser
53
+ ```
54
+
55
+ On every request the handler fills two placeholders and sends the page with a strict Content Security Policy:
56
+
57
+ | Placeholder | Replaced with |
58
+ | ---------------------- | ----------------------------------------------------------------- |
59
+ | `%%DYNALITICS_NONCE%%` | a fresh random nonce (also used in the `Content-Security-Policy`) |
60
+ | `%%DYNALITICS_BASE%%` | the mount path of the read API, e.g. `/admin/analytics` |
61
+
62
+ At the root mount the base is the **empty string**, never `/` (a `/` would make the page request `//api/meta`, which is a different host). The page builds `{base}/api/*` and only ever calls its own origin.
63
+
64
+ **Authentication.** The page calls the API with `credentials: 'same-origin'`, so it works with **ambient credentials only**, typically your session cookie. It cannot send an `Authorization: Bearer` header; use the React components for that. A redirect to a login page or single sign-on is reported as "unexpected reply", and a network failure on the first request adds a hint that the sign-in may have expired.
65
+
66
+ ## The React components
67
+
68
+ ```tsx
69
+ import { DynaliticsDashboard } from 'dynalitics-dashboard/react';
70
+ import 'dynalitics-dashboard/react/styles.css';
71
+
72
+ <DynaliticsDashboard endpoint="/admin/analytics" />;
73
+ ```
74
+
75
+ `endpoint` is the mount path of the read API. The stylesheet is scoped to the component and needs no CSS-in-JS library. The React form renders the same DOM as the HTML page (a parity test compares them panel by panel), so it looks identical. It also offers:
76
+
77
+ - **Header authorisation**: `getRequestInit` lets you add an `Authorization` header (or anything else) to every request.
78
+ - **Single widgets** to place in your own layout.
79
+ - **Theming**, server-side rendering and Next.js notes.
80
+
81
+ All of these are described in [`docs/REACT.md`](https://github.com/lucsoftsl/dynalitics/blob/main/docs/REACT.md).
82
+
83
+ ## Security properties
84
+
85
+ - Every string that comes from the API (page URLs, usernames, emails, user ids, IPs, field values) is rendered as text. The source contains no `innerHTML`, `eval` or `new Function`, and a build test fails if one appears.
86
+ - The HTML page works under `default-src 'none'; script-src 'nonce-…'; style-src 'nonce-…'; connect-src 'self'`: no inline event handlers, no inline `style` attributes, no external fonts, scripts or images (a build test checks the page for external URLs).
87
+ - Personal values arrive masked from the server. The page only offers a reveal button if the server says it is allowed, never stores the revealed state, and hides revealed values when printing.
88
+ - In the explorer, the query is sent to the server as text and parsed there; the page never builds SQL or filters data itself.
89
+
90
+ ## Size
91
+
92
+ The HTML page is about 81 KB raw (about 27 KB gzipped). A build test (`test/build.test.ts`) enforces a hard limit of 45 KB **gzipped** and a looser raw guard (110 KB) that only stops regressions. `npm run build` prints the current sizes.
93
+
94
+ MIT licensed.
@@ -0,0 +1,39 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="dynalitics-base" content="%%DYNALITICS_BASE%%">
7
+ <meta name="color-scheme" content="light dark">
8
+ <title>dynalitics</title>
9
+ <script nonce="%%DYNALITICS_NONCE%%">(function () {
10
+ var t;
11
+ try {
12
+ t = localStorage.getItem('dynalitics-theme');
13
+ } catch (e) {}
14
+ if (t !== 'light' && t !== 'dark') {
15
+ t = window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
16
+ }
17
+ document.documentElement.setAttribute('data-theme', t);
18
+ })();</script>
19
+ <style nonce="%%DYNALITICS_NONCE%%">:root{color-scheme:light;--bg: #f3eee4;--surface: #faf7f0;--surface-2: #e9e2d3;--ink: #1a1915;--ink-2: #4f4b43;--rule: #cbc2af;--rule-strong: #8c8472;--c-accent: #b03a0a;--c-new: var(--c-accent);--c-returning: #2a4a9e;--c-granted: #1d6b57;--c-declined: #566170;--c-unknown: #6f695b;--c-danger: #9e1b1b;--cat-1: #264573;--cat-2: #732eb8;--cat-3: #248f8f;--cat-4: #6f8f24;--cat-5: #862d36;--cat-1-on: var(--bg);--cat-2-on: var(--bg);--cat-3-on: var(--ink);--cat-4-on: var(--ink);--cat-5-on: var(--bg);--focus: var(--c-accent);--tip-bg: var(--ink);--tip-fg: var(--bg);--area-opacity: .14;--grain-opacity: .06;--sheet: 0 1px 0 var(--rule), 0 7px 0 -3px var(--surface-2), 0 8px 0 -3px var(--rule);--font-serif: "Iowan Old Style", "Palatino Linotype", Palatino, "URW Palladio L", P052, "Book Antiqua", serif;--font-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;--fs-hero: clamp(3.75rem, 2.2rem + 6.4vw, 6.5rem);--fs-kpi: clamp(2.5rem, 1.9rem + 2.4vw, 3.75rem);--fs-h2: 1.25rem;--fs-body: 1rem;--fs-data: .8125rem;--fs-label: .75rem;--track-label: .08em;--s-1: 4px;--s-2: 8px;--s-3: 12px;--s-4: 16px;--s-5: 24px;--s-6: 32px;--s-7: 48px;--s-8: 72px;--gutter: 16px;--page-pad: 16px;--row-gap: 32px;--r-1: 2px;--r-pill: 999px;--dur-fast: .12s;--dur-base: .24s;--dur-slow: .64s;--ease-out: cubic-bezier(.16, 1, .3, 1)}:root[data-theme=dark]{color-scheme:dark;--bg: #151410;--surface: #1d1b16;--surface-2: #2a2720;--ink: #f0eadc;--ink-2: #b2ab9b;--rule: #3a362c;--rule-strong: #7a7360;--c-accent: #ff8a5b;--c-returning: #93aaff;--c-granted: #55c9a2;--c-declined: #9aa4b2;--c-unknown: #8d8676;--c-danger: #ff7b72;--cat-1: #96b3dc;--cat-2: #9770db;--cat-3: #3dcfaa;--cat-4: #e0e085;--cat-5: #d46752;--cat-1-on: var(--bg);--cat-2-on: var(--bg);--cat-3-on: var(--bg);--cat-4-on: var(--bg);--cat-5-on: var(--bg);--area-opacity: .18;--grain-opacity: .05}@media(min-width:720px){:root{--gutter: 24px;--page-pad: 24px;--row-gap: 48px}}@media(min-width:1024px){:root{--gutter: 32px;--page-pad: 32px}}@media(min-width:1280px){:root{--page-pad: 40px}}*,*:before,*:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%}body{margin:0;background:var(--bg);color:var(--ink);font:400 var(--fs-body)/1.5 var(--font-serif);position:relative;min-height:100vh}body:before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;opacity:var(--grain-opacity);background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .9 0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>")}.dy-defs{position:absolute;width:0;height:0;overflow:hidden}.dy-pat-ground{fill:var(--surface-2)}.dy-hatch-line{stroke:var(--c-declined)}.dy-dot{fill:var(--c-unknown)}.dy-other-line{stroke:var(--ink-2)}.vh{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.dy-skip{position:absolute;left:8px;top:-60px;z-index:30;background:var(--ink);color:var(--bg);padding:12px 16px;font:500 var(--fs-label)/1 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label)}.dy-skip:focus{top:8px}:focus-visible{outline:2px solid var(--focus);outline-offset:3px}.v{font-family:var(--font-mono);unicode-bidi:isolate;overflow-wrap:anywhere}.dy-wrap{position:relative;z-index:1;max-width:1280px;margin-inline:auto;padding:0 var(--page-pad)}.label,.dy-eyebrow,.dy-btn,.dy-th{font:500 var(--fs-label)/1.2 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label)}.data{font:400 var(--fs-data)/1.35 var(--font-mono);font-variant-numeric:tabular-nums}.dy-mast{padding:var(--s-5) 0 var(--s-4);display:grid;gap:var(--s-3) var(--s-5);grid-template-columns:1fr auto;align-items:center;position:relative}.dy-mast:after{content:"";position:absolute;left:0;right:0;bottom:-7px;height:1px;background:var(--ink)}.dy-mast{border-bottom:3px solid var(--ink);margin-bottom:7px}.dy-wordmark{margin:0;font:italic 700 1.75rem/1 var(--font-serif);letter-spacing:-.01em;display:flex;align-items:center;gap:10px}.dy-wordmark:before{content:"";width:10px;height:10px;background:var(--c-accent);flex:none}.dy-updated{font:400 var(--fs-label)/1.2 var(--font-mono);color:var(--ink-2)}.dy-range{grid-column:1 / -1;grid-row:2}.dy-mast-meta{grid-column:1 / -1;grid-row:3;display:flex;align-items:center;justify-content:space-between;gap:var(--s-3) var(--s-5);flex-wrap:wrap}@media(min-width:720px){.dy-range{grid-column:1;grid-row:2;max-width:320px}.dy-mast-meta{grid-column:2;grid-row:2;justify-content:flex-end}}@media(min-width:1024px){.dy-mast{grid-template-columns:auto 1fr auto}.dy-range{grid-column:2;grid-row:1;justify-self:center;width:320px}.dy-mast-meta{grid-column:3;grid-row:1}}fieldset.dy-range{border:1px solid var(--rule-strong);border-radius:var(--r-1);padding:0;margin:0;min-width:0;position:relative;display:grid;grid-template-columns:repeat(var(--n, 4),1fr);isolation:isolate}.dy-pill{position:absolute;top:-1px;bottom:-1px;left:-1px;width:calc(100% / var(--n, 4) + 1px);background:var(--ink);border-radius:var(--r-1);transform:translate(calc(var(--i, 1) * 100%));transition:transform var(--dur-base) var(--ease-out);z-index:-1}.dy-range label{position:relative}.dy-range input{position:absolute;opacity:0;inset:0;width:100%;height:100%;margin:0;cursor:pointer}.dy-range span{display:grid;place-items:center;min-height:44px;font:500 var(--fs-label)/1 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label);transition:color var(--dur-fast)}.dy-range input:checked+span{color:var(--bg)}.dy-range label:hover input:not(:checked)+span{background:var(--surface-2)}.dy-range input:focus-visible+span{outline:2px solid var(--focus);outline-offset:3px}.dy-range label:active span{transform:translateY(1px)}.dy-btn{min-height:44px;min-width:44px;padding:0 var(--s-4);background:transparent;color:var(--ink);border:1px solid var(--rule-strong);border-radius:var(--r-1);cursor:pointer}.dy-btn:hover{background:var(--surface-2)}.dy-btn:active{transform:translateY(1px)}.dy-btn[aria-pressed=true]{background:var(--ink);color:var(--bg);border-color:var(--ink)}.dy-btn:disabled{opacity:.45;cursor:not-allowed}.dy-btn--text{border:0;padding:0 var(--s-2);text-decoration:underline 2px;text-underline-offset:5px}.dy-btn--text:hover{background:transparent;text-decoration-color:var(--c-accent)}.dy-main{margin-top:var(--s-7);padding-bottom:var(--s-8)}.dy-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--row-gap) var(--gutter);align-items:start}.dy-grid>*{min-width:0}@media(min-width:720px){.dy-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.p-hero,.p-pages,.p-table{grid-column:1 / -1}}@media(min-width:1024px){.dy-grid{grid-template-columns:repeat(12,minmax(0,1fr))}.p-hero{grid-column:1 / -1}.p-pages{grid-column:1 / 7}.p-nvr{grid-column:7 / 13}.p-consent,.p-table{grid-column:1 / -1}.p-consent .dy-body{display:grid;grid-template-columns:1fr 1.5fr;gap:var(--gutter);align-items:end}}@media(min-width:1280px){.p-hero{grid-column:1 / 9}.p-pages{grid-column:9 / 13}.p-nvr{grid-column:1 / 8}.p-consent{grid-column:8 / 13}.p-consent .dy-body{display:block}}.dy-panel{border-top:2px solid var(--ink);padding-top:var(--s-3);position:relative}.dy-panel--sheet{border-top:0;background:var(--surface);box-shadow:var(--sheet);padding:var(--s-5) var(--s-4) var(--s-4);--gap: var(--surface)}@media(min-width:720px){.dy-panel--sheet{padding:var(--s-5)}}.dy-panel-head{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--s-2) var(--s-4);margin-bottom:var(--s-4)}.dy-eyebrow{color:var(--ink-2);margin:0}.dy-h2{margin:0;font:italic 600 var(--fs-h2)/1.2 var(--font-serif)}.dy-tools{margin-left:auto;display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap}.dy-hero-num,.dy-kpi-num{margin:0;font-weight:400;font-variant-numeric:lining-nums tabular-nums;line-height:1;letter-spacing:-.03em}.dy-hero-num{font-size:var(--fs-hero)}.dy-kpi-num{font-size:var(--fs-kpi);display:flex;align-items:baseline;gap:var(--s-3)}.dy-kpi-num small{font:500 var(--fs-label)/1 var(--font-mono);letter-spacing:var(--track-label);text-transform:uppercase}.dy-kpi-sub{margin:var(--s-2) 0 var(--s-3);color:var(--ink-2);max-width:56ch}.dy-hero-wrap{position:relative}.dy-hero-num{width:fit-content}.dy-split-lbl-sel{display:flex;align-items:center;gap:var(--s-2)}.dy-hero-wrap .dy-hero-num{position:absolute;left:48px;top:var(--ny, 6px);z-index:2;pointer-events:none}.dy-hero-wrap.dy-hero--stacked .dy-hero-num,.dy-hero-wrap:has(.dy-chart-wrap[hidden]) .dy-hero-num{position:static;margin-bottom:var(--s-3)}@media(max-width:719px){.dy-hero-wrap .dy-hero-num{position:static;margin-bottom:var(--s-3)}}.dy-viz{position:relative;transition:opacity var(--dur-fast)}.dy-viz[aria-busy=true].is-refreshing{opacity:.5}.dy-progress{position:absolute;left:0;right:0;top:-2px;height:2px;background:var(--c-accent);transform-origin:left;transform:scaleX(.85);animation:dy-prog 8s linear;display:none}.is-refreshing-panel .dy-progress{display:block}@keyframes dy-prog{0%{transform:scaleX(0)}to{transform:scaleX(.85)}}.dy-chart{position:relative;touch-action:pan-y}.dy-chart svg{display:block;width:100%;height:auto;overflow:visible}.ax{font:400 12px var(--font-mono);fill:var(--ink-2)}.grid-l{stroke:var(--rule);stroke-width:1}.base-l{stroke:var(--rule-strong);stroke-width:1}.line{fill:none;stroke:var(--ink);stroke-width:2;stroke-linejoin:round;stroke-linecap:round}.line-dash{fill:none;stroke:var(--ink);stroke-width:2;stroke-dasharray:4 3}.area{fill:var(--c-accent);fill-opacity:var(--area-opacity)}.mark-now{fill:var(--surface);stroke:var(--c-accent);stroke-width:2}.cross{stroke:var(--ink);stroke-width:1}.cross-dot{fill:var(--ink);stroke:var(--surface);stroke-width:2}.seg{stroke:var(--gap, var(--bg));stroke-width:1.5}.f-new{fill:var(--c-new)}.f-returning{fill:var(--c-returning)}.f-unknown{fill:url(#dy-dots)}.f-granted{fill:var(--c-granted)}.f-declined{fill:url(#dy-hatch)}.stub{stroke:var(--rule-strong);stroke-width:1}.dy-col{transform-box:fill-box;transform-origin:bottom;animation:dy-grow .4s var(--ease-out) both;animation-delay:calc(var(--i, 0) * 12ms)}@keyframes dy-grow{0%{transform:scaleY(0)}to{transform:scaleY(1)}}.dy-tip{position:absolute;left:0;top:0;z-index:20;pointer-events:none;opacity:0;transition:opacity var(--dur-fast);background:var(--tip-bg);color:var(--tip-fg);border-radius:var(--r-1);padding:8px 12px;max-width:240px;font:400 12px/1.4 var(--font-mono);white-space:nowrap}.dy-tip.on{opacity:1}.dy-tip b{font-weight:500;display:block;margin-bottom:2px}.dy-tip i{display:inline-block;width:10px;height:10px;margin-right:6px;vertical-align:-1px;outline:1px solid var(--tip-fg)}.dy-sw{display:inline-block;width:12px;height:12px;outline:1px solid var(--rule-strong);outline-offset:0;flex:none}.dy-sw-granted{background:var(--c-granted)}.dy-sw-declined{background:repeating-linear-gradient(135deg,var(--c-declined) 0 2px,var(--surface-2) 2px 5px)}.dy-sw-unknown{background:radial-gradient(var(--c-unknown) 1.1px,transparent 1.3px) 0 0 / 5px 5px,var(--surface-2)}.dy-sw-new{background:var(--c-new)}.dy-sw-returning{background:var(--c-returning)}.dy-sw-cat1{background:var(--cat-1)}.dy-sw-cat2{background:var(--cat-2)}.dy-sw-cat3{background:var(--cat-3)}.dy-sw-cat4{background:var(--cat-4)}.dy-sw-cat5{background:var(--cat-5)}.dy-sw-other{background:repeating-linear-gradient(-45deg,var(--ink-2) 0 2px,var(--surface-2) 2px 5px)}.dy-legend{list-style:none;margin:var(--s-3) 0 0;padding:0;display:flex;flex-wrap:wrap;gap:var(--s-2) var(--s-4);font:400 var(--fs-label)/1.2 var(--font-mono)}.dy-legend li{display:flex;align-items:center;gap:6px}.dy-legend b{font-weight:500}.dy-split svg{display:block;width:100%;height:20px}.dy-pages{list-style:none;margin:0;padding:0;counter-reset:none}.dy-page-row{position:relative;display:grid;grid-template-columns:28px minmax(0,1fr) auto 48px;column-gap:var(--s-3);align-items:baseline;padding:10px var(--s-2);border-bottom:1px solid var(--rule);min-height:44px;isolation:isolate}.dy-rank{font:400 20px/1 var(--font-serif);color:var(--ink-2);font-variant-numeric:lining-nums}.dy-page{font-size:var(--fs-data);line-height:1.35}.dy-count{font:500 var(--fs-data)/1.35 var(--font-mono);text-align:right;font-variant-numeric:tabular-nums}.dy-share{font:400 var(--fs-data)/1.35 var(--font-mono);color:var(--ink-2);text-align:right}.dy-bar{position:absolute;inset:0;z-index:-1;transform-origin:left;transform:scaleX(var(--r, 1));background:var(--surface-2);background:color-mix(in srgb,var(--c-accent) 22%,transparent);transition:background var(--dur-fast)}.dy-page-row:hover .dy-bar{background:color-mix(in srgb,var(--c-accent) 30%,transparent)}.dy-page-row:hover .dy-rank,.dy-page-row:hover .dy-share{color:var(--ink)}.dy-page-row:hover .dy-page{text-decoration:underline 1px;text-underline-offset:3px}.dy-page-row--other .dy-bar{background:repeating-linear-gradient(135deg,var(--rule) 0 1px,transparent 1px 6px)}.dy-page-row--other:hover .dy-bar{background:repeating-linear-gradient(135deg,var(--rule-strong) 0 1px,transparent 1px 6px)}.dy-page-row--other .dy-page{font:italic 400 var(--fs-body)/1.3 var(--font-serif)}@media(max-width:479px){.dy-page-row{grid-template-columns:28px minmax(0,1fr);row-gap:2px}.dy-count,.dy-share{grid-column:2;text-align:left;display:inline}.dy-count{text-align:left}.dy-nums{grid-column:2;display:flex;gap:var(--s-3)}}.dy-nums{display:contents}.dy-select{min-height:44px;font:400 var(--fs-data)/1 var(--font-mono);color:var(--ink);background:transparent;border:1px solid var(--rule-strong);border-radius:var(--r-1);padding:0 var(--s-3)}.dy-select-lbl{display:flex;align-items:center;gap:var(--s-2)}.dy-table-wrap{overflow-wrap:anywhere}table.dy-table{width:100%;border-collapse:collapse}.dy-table th,.dy-table td{text-align:left;vertical-align:top;padding:10px 12px;border-bottom:1px solid var(--rule);font:400 var(--fs-data)/1.35 var(--font-mono);min-width:0}.dy-table thead th{color:var(--ink-2);font:500 var(--fs-label)/1.2 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label);border-top:2px solid var(--ink);border-bottom:1px solid var(--ink);padding-block:12px}.dy-table tbody tr{position:relative}.dy-table tbody tr:hover td{background:var(--surface-2)}.dy-table tbody tr:hover td:first-child{box-shadow:inset 3px 0 0 var(--c-accent)}.dy-t2{display:block;color:var(--ink-2);font-size:var(--fs-label)}.t-time b{font-weight:500;color:var(--ink);font-size:var(--fs-data);display:block}.chip{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--rule-strong);border-radius:var(--r-pill);padding:3px 10px 3px 8px;font:500 var(--fs-label)/1.2 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label);white-space:nowrap}.chip .dy-sw{width:10px;height:10px}.yes{color:var(--c-new);font-weight:500}.no{color:var(--ink-2)}.na{font:italic 400 var(--fs-body)/1.3 var(--font-serif)!important;color:var(--ink-2);background:repeating-linear-gradient(135deg,transparent 0 4px,var(--rule) 4px 5px)}.dash{color:var(--ink-2)}.th-lock:after{content:"";display:inline-block;width:8px;height:6px;margin-left:6px;border:1.5px solid currentColor;border-radius:1px;vertical-align:1px;box-shadow:0 -4px 0 -1.5px currentColor}.dy-revealed{border-top:3px solid var(--c-accent)}.dy-note{font:400 var(--fs-label)/1.4 var(--font-mono);color:var(--ink-2);margin:var(--s-2) 0 0}.dy-more{display:flex;align-items:center;justify-content:flex-end;padding-top:var(--s-3);min-height:56px}@media(min-width:1024px){table.dy-table{table-layout:fixed}.dy-table th.c-time{width:104px}.dy-table th.c-consent{width:116px}.dy-table th.c-first{width:92px}.dy-table th.c-ip{width:116px}.dy-table th.c-email{width:168px}.dy-table th.c-user{width:108px}}@media(min-width:1280px){.dy-table th.c-time{width:120px}.dy-table th.c-consent{width:124px}.dy-table th.c-first{width:104px}.dy-table th.c-ip{width:136px}.dy-table th.c-email{width:208px}.dy-table th.c-user{width:144px}}@media(max-width:1023px){.dy-table,.dy-table tbody{display:block}.dy-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}.dy-table tr{display:grid;grid-template-columns:110px minmax(0,1fr) 130px 100px;border-bottom:1px solid var(--rule)}.dy-table td{border-bottom:0}.dy-table td.c-ip{grid-column:1;grid-row:2}.dy-table td.c-email{grid-column:2;grid-row:2}.dy-table td.c-user{grid-column:3 / span 2;grid-row:2}.dy-table td.na{grid-column:1 / -1;grid-row:2}.dy-table td.c-ip:before,.dy-table td.c-email:before,.dy-table td.c-user:before,.dy-table td.c-first:before{content:attr(data-label);display:block;font:500 var(--fs-label)/1.2 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label);color:var(--ink-2)}.dy-table td.c-first:before{display:inline;margin-right:6px}}@media(max-width:719px){.dy-table tr{grid-template-columns:minmax(0,1fr);padding:16px 0}.dy-table td{display:grid;grid-template-columns:64px minmax(0,1fr);gap:8px;padding:4px 0;grid-column:1!important;grid-row:auto!important}.dy-table td:before{content:attr(data-label);font:500 var(--fs-label)/1.6 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label);color:var(--ink-2);display:block!important;margin:0!important}.dy-table td.na{grid-template-columns:1fr}.dy-table td.na:before{display:none!important}.dy-table tbody tr:hover td{background:transparent;box-shadow:none!important}.dy-t2{display:inline;margin-left:8px}.t-time b{display:inline}}table.dy-twin{width:100%;border-collapse:collapse}.dy-twin caption{text-align:left;font:italic 600 var(--fs-body)/1.3 var(--font-serif);padding:0 0 var(--s-2)}.dy-twin th,.dy-twin td{padding:8px 12px 8px 0;border-bottom:1px solid var(--rule);font:400 var(--fs-data)/1.35 var(--font-mono);text-align:left}.dy-twin td.n,.dy-twin th.n{text-align:right;font-variant-numeric:tabular-nums}.dy-twin thead th{font:500 var(--fs-label)/1.2 var(--font-mono);text-transform:uppercase;letter-spacing:var(--track-label);color:var(--ink-2);border-bottom:1px solid var(--ink)}.dy-skel{background:var(--rule);animation:dy-pulse 1.2s ease-in-out infinite alternate}@keyframes dy-pulse{0%{opacity:.55}to{opacity:1}}.dy-state{padding:var(--s-5) 0;max-width:46ch}.dy-state h3{margin:var(--s-3) 0 var(--s-2);font:italic 600 1.25rem/1.25 var(--font-serif)}.dy-state p{margin:0 0 var(--s-3)}.dy-state code{font:400 var(--fs-data) var(--font-mono);background:var(--surface-2);padding:1px 4px}.dy-err{border-top:2px solid var(--c-danger);padding-top:var(--s-4)}.dy-stamp{display:inline-block;border:1px solid var(--c-danger);color:var(--c-danger);padding:4px 8px;font:500 var(--fs-label)/1 var(--font-mono);letter-spacing:var(--track-label);text-transform:uppercase}.dy-ref{font:400 var(--fs-label)/1.4 var(--font-mono);color:var(--ink-2)}.dy-actions{display:flex;gap:var(--s-3);flex-wrap:wrap;align-items:center;margin-top:var(--s-3)}.dy-foot{position:relative;z-index:1;max-width:1280px;margin:0 auto;padding:0 var(--page-pad) var(--s-6);font:400 var(--fs-label)/1.5 var(--font-mono);color:var(--ink-2)}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important}.dy-skel{opacity:.8}.dy-progress{opacity:.5;transform:scaleX(.5)}}@media(forced-colors:active){body:before{display:none}.line,.line-dash{stroke:CanvasText}.area{fill:Highlight}.seg{stroke:Canvas}.dy-pill{background:Highlight}.dy-range input:checked+span{color:HighlightText}}@media print{body:before,.dy-mast-meta,.dy-range,.dy-btn{display:none}.dy-revealed .dy-pii{visibility:hidden}}[hidden]{display:none!important}.dy-mast-btns{display:flex;gap:var(--s-3)}.dy-toggle{display:flex}.dy-toggle .dy-btn{min-width:64px;border-radius:0}.dy-toggle .dy-btn+.dy-btn{border-left-width:0}.dy-figure{margin:0}.dy-bar-split{display:flex;gap:1px;height:20px;margin:var(--s-3) 0;outline:1px solid var(--rule-strong);background:var(--surface)}.dy-bar-split>span{min-width:3px;flex-basis:0}.c-new{color:var(--c-new)}.c-granted{color:var(--c-granted)}.dy-viz-body{min-width:0}.dy-loading--wait{visibility:hidden}.dy-skel-box{display:grid;gap:var(--s-2)}.dy-skel-num{height:.9em;width:5ch;font-size:var(--fs-kpi)}.dy-skel-line{height:12px;width:var(--w, 60%)}.dy-skel-row{height:40px;width:var(--w, 60%)}.dy-skel-chart{height:var(--h, 200px);margin-top:var(--s-3)}.dy-state .dy-ledger{display:block}.dy-state .dy-actions .dy-btn{margin-right:var(--s-2)}.dy-stale{margin:0 0 var(--s-3);font:400 var(--fs-label)/1.4 var(--font-mono)}.dy-stale .dy-stamp{margin-right:var(--s-2)}.dy-na-state .dy-eyebrow{margin-bottom:var(--s-2)}.dy-panel--sheet .dy-state{max-width:none}.dy-table td.c-page{min-width:0}.dy-hero-wrap .dy-eyebrow{margin-bottom:var(--s-2)}.c-first .yes,.c-first .no{font:inherit}.f-new,.f-returning,.f-granted{stroke-width:1.5}.dy-note+.dy-bar-split,.dy-kpi-sub+.dy-bar-split{margin-top:var(--s-3)}.dy-estimate{font:500 var(--fs-label) / 1 var(--font-mono);letter-spacing:var(--track-label);text-transform:uppercase;color:var(--ink);border:1px solid var(--rule-strong);border-radius:var(--r-1);padding:2px 6px}.dy-na-label{display:inline-block;font:500 var(--fs-label) / 1 var(--font-mono);letter-spacing:var(--track-label);text-transform:uppercase;color:var(--ink-2);border:1px dashed var(--rule-strong);border-radius:var(--r-1);padding:4px 8px}.dy-na-state{min-height:220px}.dy-kbd-hint{margin:var(--s-2) 0 0;min-height:1.4em;font:400 var(--fs-label) / 1.4 var(--font-mono);color:var(--ink-2);opacity:0}.dy-chart-wrap:has(.dy-chart:focus-visible) .dy-kbd-hint{opacity:1}.f-cat1{fill:var(--cat-1)}.f-cat2{fill:var(--cat-2)}.f-cat3{fill:var(--cat-3)}.f-cat4{fill:var(--cat-4)}.f-cat5{fill:var(--cat-5)}.f-other{fill:url(#dy-hatch-other)}.f-none{fill:url(#dy-dots)}.band-label{font:400 12px var(--font-mono);text-anchor:end}.on-cat1{fill:var(--cat-1-on)}.on-cat2{fill:var(--cat-2-on)}.on-cat3{fill:var(--cat-3-on)}.on-cat4{fill:var(--cat-4-on)}.on-cat5{fill:var(--cat-5-on)}.dy-chart:focus-visible{outline-offset:4px}.dy-views{grid-column:1 / -1;display:flex;gap:var(--s-2)}.dy-views:not([hidden])~.dy-range{grid-row:3}.dy-views:not([hidden])~.dy-mast-meta{grid-row:4}@media(min-width:720px){.dy-views{grid-column:2;grid-row:1;justify-self:end}.dy-views:not([hidden])~.dy-range,.dy-views:not([hidden])~.dy-mast-meta{grid-row:2}}.dy-view-btn[aria-current=page]{background:var(--ink);color:var(--bg);border-color:var(--ink)}@media(min-width:1024px){.dy-views{grid-column:1;grid-row:2;justify-self:start}.dy-views:not([hidden])~.dy-range,.dy-views:not([hidden])~.dy-mast-meta{grid-row:1}}.f-match{fill:var(--ink)}.dy-sw-match{background:var(--ink)}.dy-explorer{display:grid;gap:var(--s-4);min-width:0}.dy-explorer>*{min-width:0}.dy-grid--explorer{display:block}.dy-query{display:grid;gap:var(--s-2);border-top:2px solid var(--ink);padding-top:var(--s-3)}.dy-query-row{display:flex;gap:var(--s-2)}.dy-q{flex:1;min-width:0;min-height:44px;padding:0 var(--s-3);font:400 .9375rem/1.2 var(--font-mono);color:var(--ink);background:var(--surface);border:1px solid var(--rule-strong);border-radius:var(--r-1)}.dy-q:hover{border-color:var(--ink)}.dy-q[aria-invalid=true]{border-color:var(--c-accent);box-shadow:inset 3px 0 0 var(--c-accent)}.dy-q-err p{margin:0;font:400 var(--fs-data)/1.4 var(--font-mono)}.dy-q-echo{color:var(--ink-2);overflow-wrap:anywhere;white-space:pre-wrap}.dy-q-at{background:var(--c-accent);color:var(--bg)}.dy-syntax summary{cursor:pointer;font:500 var(--fs-label)/2 var(--font-mono);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}.dy-syntax dl{display:grid;grid-template-columns:max-content 1fr;gap:var(--s-1) var(--s-4);margin:var(--s-2) 0;font:400 var(--fs-data)/1.4 var(--font-mono)}.dy-syntax dt{color:var(--ink);font-weight:500}.dy-syntax dd{margin:0;color:var(--ink-2)}.dy-xmeta{display:flex;flex-wrap:wrap;align-items:end;gap:var(--s-3) var(--s-5);border-bottom:1px solid var(--rule);padding-bottom:var(--s-3)}.dy-xmatched{margin:0 auto 0 0;font:400 var(--fs-kpi)/1 var(--font-serif);font-variant-numeric:tabular-nums}.dy-xctl{display:grid;gap:var(--s-1)}.dy-xzoom{margin:0;font:400 var(--fs-data)/1.4 var(--font-mono);border-left:3px solid var(--c-accent);padding-left:var(--s-2)}.dy-xgrid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--row-gap) var(--gutter);align-items:start}.dy-xgrid>*{min-width:0}.dy-xfacets{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:var(--s-5) var(--gutter);align-items:start}.dy-xfacet-key{margin:0 0 var(--s-2);font:500 var(--fs-label)/1.2 var(--font-mono);text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2);border-bottom:1px solid var(--ink);padding-bottom:var(--s-1)}.dy-xvals{list-style:none;margin:0;padding:0}.dy-xval{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:0 var(--s-2);padding:2px 0;border-bottom:1px solid var(--rule);font:400 var(--fs-data)/1.35 var(--font-mono)}.dy-xval-add,.dy-xval-drop{min-height:32px;font:inherit;color:var(--ink);background:none;border:0;border-radius:var(--r-1);cursor:pointer}.dy-xval-add{display:flex;align-items:center;gap:var(--s-2);text-align:left;padding:0 var(--s-1);min-width:0}.dy-xval-add:hover,.dy-xval-drop:hover{background:var(--surface-2)}.dy-xval-add:hover .dy-xval-v{text-decoration:underline 2px;text-underline-offset:3px}.dy-xval-drop{grid-column:2;width:32px}.dy-xval-v{overflow-wrap:anywhere}.dy-xval-n{grid-column:3;font-variant-numeric:tabular-nums;color:var(--ink-2);text-align:right}.dy-xval--other,.dy-xval--none .dy-xval-v{color:var(--ink-2);font-style:italic}.dy-xval--other{display:flex;align-items:center;gap:var(--s-2);min-height:32px;padding-left:var(--s-1)}.dy-xval--other .dy-xval-n{margin-left:auto}.dy-xbar{grid-column:1 / -1;height:4px;background:linear-gradient(var(--ink),var(--ink)) 0 0 / calc(var(--w, 0) * 100%) 100% no-repeat}.dy-table tr.is-new{box-shadow:inset 3px 0 0 var(--c-accent);animation:dy-new 10s var(--ease-out) forwards}@keyframes dy-new{to{box-shadow:inset 3px 0 0 transparent}}@media(prefers-reduced-motion:reduce){.dy-table tr.is-new{animation:none}}</style>
20
+ </head>
21
+ <body>
22
+ <svg width="0" height="0" aria-hidden="true" focusable="false" class="dy-defs"><defs><pattern id="dy-hatch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="6" height="6" class="dy-pat-ground"/><line x1="0" y1="0" x2="0" y2="6" stroke-width="2" class="dy-hatch-line"/></pattern><pattern id="dy-dots" width="5" height="5" patternUnits="userSpaceOnUse"><rect width="5" height="5" class="dy-pat-ground"/><circle cx="2.5" cy="2.5" r="1.1" class="dy-dot"/></pattern><pattern id="dy-hatch-other" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)"><rect width="6" height="6" class="dy-pat-ground"/><line x1="0" y1="0" x2="0" y2="6" stroke-width="2" class="dy-other-line"/></pattern></defs></svg>
23
+ <a class="dy-skip" href="#main">Skip to analytics</a>
24
+ <div class="dy-wrap">
25
+ <header class="dy-mast">
26
+ <h1 class="dy-wordmark">dynalitics</h1>
27
+ <nav class="dy-views" id="dy-views" aria-label="Views" hidden></nav>
28
+ <fieldset class="dy-range" id="dy-range"><legend class="vh">Date range</legend></fieldset>
29
+ <div class="dy-mast-meta">
30
+ <span class="dy-updated">Updated <time id="dy-updated">—</time> UTC</span>
31
+ <span class="dy-mast-btns"><button class="dy-btn" id="dy-refresh" type="button">Refresh</button><button class="dy-btn" id="dy-theme" type="button" aria-pressed="false">Dark theme</button></span>
32
+ </div>
33
+ </header>
34
+ <main id="main" tabindex="-1" class="dy-main"><div class="dy-grid" id="dy-grid"><noscript><p class="dy-state">dynalitics needs JavaScript to draw the dashboard.</p></noscript></div></main>
35
+ </div>
36
+ <footer class="dy-foot">dynalitics 0.1.0 · data stays in your database · times in UTC</footer>
37
+ <script nonce="%%DYNALITICS_NONCE%%">"use strict";(()=>{var Zt=/^\/[A-Za-z0-9/_-]*$/;function We(e){return!e||!Zt.test(e)?"":e.replace(/\/+$/,"")}var Qt={forbidden:"forbidden",not_found:"not_found",timeout:"timeout",unavailable:"busy",range_too_large:"bad_request",invalid_parameter:"bad_request",invalid_query:"bad_request",method_not_allowed:"unexpected"};function Ye(e){return e===401||e===403?"forbidden":e===404?"not_found":e===400?"bad_request":e===503?"busy":e===504?"timeout":e>=500?"server":"unexpected"}function fe(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function ee(e,t,n){return{kind:"error",failure:{kind:e,status:t,...n}}}function Jt(e,t){if(!fe(t)||typeof t.success!="boolean")return ee(e===401||e===403?"forbidden":"unexpected",e);if(t.success===!1){let a=fe(t.error)?t.error:{},o=typeof a.code=="string"?a.code:void 0,i=o&&Qt[o]||Ye(e),s=typeof a.param=="string"?a.param:void 0,d=o==="invalid_query"&&typeof a.reason=="string"&&typeof a.position=="number"&&typeof a.message=="string"?{reason:a.reason,position:a.position,message:a.message}:void 0;return ee(i,e,{...o?{code:o}:{},...s?{param:s}:{},...d?{query:d}:{}})}let n=fe(t.meta)?t.meta:null;if(!n)return ee("unexpected",e);let r=t.unavailable;return fe(r)&&typeof r.reason=="string"&&typeof r.message=="string"?{kind:"unavailable",unavailable:r,meta:n}:fe(t.data)?{kind:"data",data:t.data,meta:n}:ee("unexpected",e)}function Ke(e,t){return{async get(n,r,a){let o=new URLSearchParams(r).toString(),i=`${e}/api/${n}${o?`?${o}`:""}`,s;try{s=await t(i,{credentials:"same-origin",redirect:"manual",headers:{accept:"application/json"},...a?{signal:a}:{}})}catch{return a?.aborted?{kind:"aborted"}:ee("unreachable",0)}if(a?.aborted)return{kind:"aborted"};if(s.redirected||s.type==="opaqueredirect")return ee("unexpected",s.status);let d;try{d=await s.json()}catch{return a?.aborted?{kind:"aborted"}:ee(Ye(s.status)==="forbidden"?"forbidden":"unexpected",s.status)}return a?.aborted?{kind:"aborted"}:Jt(s.status,d)}}}var en="http://www.w3.org/2000/svg";function Xe(e,t){if(t)for(let[n,r]of Object.entries(t))e.setAttribute(n,String(r))}function l(e,t,n,r){let a=document.createElement(e);return t&&(a.className=t),n!=null&&(a.textContent=n),Xe(a,r),a}function A(e,t,n){let r=document.createElementNS(en,e);return Xe(r,t),n?.appendChild(r),r}function W(e,t,n="",r=!1){let a=l(e,n?`v ${n}`:"v",t);return r&&a.setAttribute("dir","auto"),a}function _(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function g(e,...t){for(let n of t)n&&e.appendChild(n)}function te(e){return l("span",`dy-sw dy-sw-${e}`,null,{"aria-hidden":"true"})}function z(e){return l("span","vh",e)}function he(e,t){return new Intl.DateTimeFormat(e,{...t,timeZone:"UTC"})}function ze(e){let t=new Intl.NumberFormat(e),n=new Intl.NumberFormat(e,{notation:"compact",maximumFractionDigits:1}),r=he(e,{day:"numeric",month:"short"}),a=he(e,{weekday:"short",day:"numeric",month:"short",year:"numeric"}),o=he(e,{hour:"2-digit",minute:"2-digit",hour12:!1}),i=he(e,{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}),s=he(e,{month:"short"});return{num:d=>t.format(d),compact:d=>n.format(d),day:d=>r.format(d),weekday:d=>a.format(d),hour:d=>o.format(d),time:d=>i.format(d),month:d=>s.format(d)}}function G(e){return`${Math.round(e*1e3)/10}%`}function qe(e,t){return t>0&&e<.005?"<1%":`${Math.round(e*100)}%`}var tn=/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z)?$/;function D(e){return tn.test(e)?Date.parse(e):Number.NaN}var Te=36e5;function Ge(e,t,n,r){return n==="hour"?r||new Date(t).getUTCHours()===0?e.day(t):e.hour(t):n==="month"?e.month(t):e.day(t)}function Ce(e,t,n,r){let a;return n==="hour"?a=`${e.day(t)}, ${e.hour(t)}-${e.hour(t+Te)} UTC`:n==="week"?a=`Week of ${e.day(t)}`:n==="month"?a=`${e.month(t)} ${new Date(t).getUTCFullYear()}`:a=e.weekday(t),r.last?a+=" (so far)":r.first&&r.fromMs>t&&(a+=" (partial period)"),a}function Ze(e,t){let n=D(t);return Number.isNaN(n)?null:{time:e.time(n),date:e.day(n)}}function Qe(e){return Number.isFinite(e)?Math.min(1,Math.max(0,e)):0}function re(e,t){if(t==="hour")return e+Te;if(t==="day")return e+24*Te;if(t==="week")return e+168*Te;let n=new Date(e);return Date.UTC(n.getUTCFullYear(),n.getUTCMonth()+1,1)}function _e(e){return typeof e=="object"&&e!==null}function Je(e){if(!_e(e)||e.version!==2||typeof e.now!="string"||Number.isNaN(D(e.now)))return null;let{limits:t,defaults:n,widgets:r,fields:a,features:o}=e;if(![t,n,r,a,o].every(_e))return null;let i=t,s=n;if(![i.maxRangeDays,i.topPagesMax,i.recentMax,i.newVsReturningMaxRangeDays,s.rangeDays,s.topPagesLimit,s.recentLimit].every(h=>typeof h=="number"&&Number.isFinite(h)))return null;let y=r;return["visitsOverTime","topPages","consentRate","newVsReturning","recentVisits"].every(h=>_e(y[h]))?{...e,breakdowns:Array.isArray(e.breakdowns)?e.breakdowns:[]}:null}function oe(e){let t=Number.isFinite(e)&&e>0?e:0;if(t===0)return{step:1,yMax:4,ticks:[0,1,2,3,4]};let n=t/3,r=10**Math.floor(Math.log10(n)),a=[1,2,5,10].map(s=>s*r).find(s=>s>=n)??10*r,o=a*Math.ceil(t/a),i=[];for(let s=0;s<=o+a/2;s+=a)i.push(s);return{step:a,yMax:o,ticks:i}}function et(e,t=64){return e>0?Math.max(1,Math.ceil(t/e)):1}function tt(e,t){let n=[];for(let a=0;a<e;a+=t)n.push(a);let r=e-1;return r>0&&!n.includes(r)&&r-(n[n.length-1]??0)>=t&&n.push(r),n}var ke={top:8,right:8,bottom:28,left:40};function ae(e,t){return{width:e,height:t,left:ke.left,right:e-ke.right,top:ke.top,bottom:t-ke.bottom}}function Me(e,t){let n=e.right-e.left;return t<=1?[e.left+n/2]:Array.from({length:t},(r,a)=>e.left+a*n/(t-1))}function nt(e,t){let n=e.right-e.left,r=2,a=(n-r*(t-1))/t;return a<6&&(r=1,a=(n-r*(t-1))/t),a=Math.max(2,a),{xs:Array.from({length:t},(o,i)=>e.left+i*(a+r)),bandWidth:a,gap:r}}function nn(e,t){let n=e[0];if(n&&e.length===1)return n.x>=t.left&&n.x<=t.right&&n.y<t.bottom;for(let r=1;r<e.length;r++){let a=e[r-1],o=e[r];if(!a||!o)continue;let[i,s]=a.x<=o.x?[a,o]:[o,a];if(s.x<t.left||i.x>t.right)continue;let d=h=>s.x===i.x?Math.min(i.y,s.y):i.y+(s.y-i.y)*(h-i.x)/(s.x-i.x);if(Math.min(d(Math.max(i.x,t.left)),d(Math.min(s.x,t.right)))<t.bottom)return!0}return!1}var Ee={left:48,top:6},Se=12,rn=.6;function rt(e,t,n){return!t||!(t.w>0)||!(t.h>0)||!(n>0)||t.w>n*rn?!0:nn(e,{left:Ee.left-Se,top:Ee.top-Se,right:Ee.left+t.w+Se,bottom:Ee.top+t.h+Se})}function S(e){return String(Math.round(e*10)/10)}function Re(e,t,n){return r=>{let a=r.length>1?Math.abs((r[1]??0)-(r[0]??0)):64;return tt(r.length,et(a)).map(o=>({x:r[o]??0,text:Ge(e,t[o]??0,n,o===0)}))}}var on={hour:"hour",day:"day",week:"week",month:"month"};function j(e){return on[e]}function Z(e,t,n){return n==="hour"?`${e.day(t)} ${e.hour(t)}`:e.day(t)}function ne(){return{status:"loading",reqId:0,refreshing:!1,data:null,generatedAt:null,unavailable:null,failure:null,staleFailure:null}}var ot=e=>e.status==="ready"||e.status==="empty";function at(e){let t=e.reqId+1;return ot(e)?{...e,reqId:t,refreshing:!0}:{...ne(),reqId:t}}function it(e,t,n,r){if(t!==e.reqId||n.kind==="aborted")return e;switch(n.kind){case"data":return{...ne(),status:r(n.data)?"empty":"ready",reqId:t,data:n.data,generatedAt:n.meta.generatedAt};case"unavailable":return{...ne(),status:"unavailable",reqId:t,unavailable:n.unavailable};case"error":return ot(e)?{...e,refreshing:!1,staleFailure:n.failure}:{...ne(),status:"error",reqId:t,failure:n.failure}}}function lt(e,t,n){return{...e,status:n(t)?"empty":"ready",data:t,staleFailure:null}}function an(){let e=A("svg",{width:160,height:56,viewBox:"0 0 160 56","aria-hidden":"true",focusable:"false",class:"dy-ledger"});return[10,24,38].forEach(t=>A("line",{x1:14,x2:160,y1:t,y2:t,class:"base-l"},e)),A("line",{x1:0,x2:160,y1:52,y2:52,class:"base-l","stroke-dasharray":"1 4"},e),A("rect",{x:0,y:6,width:8,height:8,class:"f-new"},e),e}function F(e,t=[],n){let r=l("div","dy-state");if(g(r,an(),l("h3",void 0,e.title)),r.appendChild(n??l("p",void 0,e.body)),t.length){let a=l("div","dy-actions");g(a,...t),r.appendChild(a)}return r}function dt(e,t){let n=l("div","dy-state dy-na-state",null,{role:"status"});return g(n,l("span","dy-na-label","Not available"),l("h3",void 0,`${t} isn't available here.`),l("p",void 0,e.message),l("p","dy-ref",`reason: ${e.reason}`)),n}var ut={forbidden:{stamp:"NOT AUTHORISED",title:"You can't view analytics.",body:"The app that serves this page said no. Sign in with an account that is allowed to see analytics, then reload.",retry:!1,shorter:!1},unexpected:{stamp:"UNEXPECTED REPLY",title:"The analytics endpoint didn't return data.",body:"It returned something that isn't analytics JSON, or redirected somewhere else. This usually means a login page, single sign-on or a proxy in front of this route. Check the route and its authorisation.",retry:!0,shorter:!1},unreachable:{stamp:"UNREACHABLE",title:"Can't reach the analytics endpoint.",body:"Check your connection and that the server is running. Nothing was lost; data stays in your database.",retry:!0,shorter:!1},timeout:{stamp:"TIMED OUT",title:"That took too long.",body:"The query exceeded its time limit. Choose a shorter range.",retry:!0,shorter:!0},busy:{stamp:"UNAVAILABLE",title:"The database is busy or unavailable.",body:"Try again in a moment.",retry:!0,shorter:!1},bad_request:{stamp:"BAD REQUEST",title:"That range isn't allowed.",body:"Choose a shorter range.",retry:!1,shorter:!0},not_found:{stamp:"NOT FOUND",title:"This route doesn't exist.",body:"The dashboard and server versions may not match. Update both packages.",retry:!1,shorter:!1},server:{stamp:"ERROR",title:"Something went wrong on the server.",body:"The server logged the details. Try again.",retry:!0,shorter:!1}},ln="If this page sits behind single sign-on, your sign-in may have expired: reload the page.";function Ae(e,t,n="dy-btn dy-btn--text"){let r=l("button",n,e,{type:"button"});return r.addEventListener("click",t),r}function Le(e,t){let n=ut[e.kind],r=l("div","dy-state dy-err",null,{role:t.role});g(r,l("span","dy-stamp",n.stamp),l("h3",void 0,n.title),l("p",void 0,n.body));let a=e.code??(e.status?`HTTP ${e.status}`:""),o=l("p","dy-ref",a);e.param&&o.appendChild(l("span","v",` (${e.param})`)),a&&r.appendChild(o),e.kind==="unreachable"&&t.ssoHint&&r.appendChild(l("p",void 0,ln));let i=l("div","dy-actions"),s=e.kind==="bad_request"&&t.reloadOnBadRequest===!0;if((e.kind==="forbidden"||s)&&i.appendChild(Ae("Reload page",t.onReload,"dy-btn")),n.shorter&&!s&&t.onTrySevenDays&&i.appendChild(Ae("Try 7 days",t.onTrySevenDays)),n.retry){let d=Ae("Retry",t.onRetry);t.cooldownLeftMs>0&&(d.disabled=!0,d.setAttribute("aria-busy","true"),d.textContent="Retrying..."),i.appendChild(d)}return i.childElementCount&&r.appendChild(i),r}function pt(e,t,n){let r=ut[e.kind],a=l("p","dy-stale");return g(a,l("span","dy-stamp",r.stamp),document.createTextNode(` Showing data from ${t} UTC. `)),r.retry&&a.appendChild(Ae("Retry",n)),a}function be(e,t){let n=l("div",`dy-skel ${e}`,null,{"aria-hidden":"true"});return t&&n.style.setProperty("--w",t),n}function ie(){let e=l("div","dy-skel-box");return g(e,be("dy-skel-num"),be("dy-skel-line","70%"),be("dy-skel-line","45%")),e}function Q(e){let t=be("dy-skel-chart");return t.style.setProperty("--h",`${e}px`),t}var st=["100%","82%","74%","60%","51%","44%","33%","28%","20%","14%"];function le(e){let t=l("div","dy-skel-box");for(let n=0;n<e;n++)t.appendChild(be("dy-skel-row",st[n%st.length]));return t}var sn=200,dn=2e3;function q(e,t,n){let r=ne(),a=null,o,i=!1,s=Number.NEGATIVE_INFINITY,d,y="status",h=l("section",`dy-panel ${e.className}${e.sheet?" dy-panel--sheet":""}`,null,{"aria-labelledby":`h-${e.id}`}),m=l("div","dy-tools"),u=l("div","dy-viz"),f=l("p","vh",null,{role:"status","aria-live":"polite"}),p=l("header","dy-panel-head"),c=l("p","dy-eyebrow",`No. ${e.no}`,{"aria-hidden":"true"});g(p,c,l("h2","dy-h2",e.title,{id:`h-${e.id}`}),m);let v=l("div","dy-body");v.appendChild(u),g(h,l("div","dy-progress",null,{"aria-hidden":"true"}),p,v,f);let b={element:h,host:n,getCtx:t,getState:()=>r,load:T,rerender:P,say:w=>{f.textContent=w},patch:w=>{r=lt(r,w,e.isEmpty),P()}},k=()=>Math.max(0,dn-(n.now()-s));function E(){s=n.now(),T()}function M(w){switch(r.status){case"loading":{let C=l("div",i?"dy-loading":"dy-loading dy-loading--wait");return C.appendChild(e.skeleton(w)),C}case"ready":return e.ready(r.data,w,b);case"empty":return e.empty(w,n);case"unavailable":return dt(r.unavailable,e.title);case"error":return Le(r.failure,{role:y,cooldownLeftMs:k(),onRetry:E,onReload:n.reloadPage,onTrySevenDays:w.range.days>7?()=>n.setRange("7d"):null,reloadOnBadRequest:e.reloadOnBadRequest===!0})}}function P(){let w=t();e.eyebrow&&(c.textContent=e.eyebrow(w));let C=r.status==="loading"||r.refreshing;if(u.setAttribute("aria-busy",String(C)),u.classList.toggle("is-refreshing",r.refreshing),h.classList.toggle("is-refreshing-panel",r.refreshing),_(u),r.staleFailure&&r.generatedAt){let $=D(r.generatedAt);u.appendChild(pt(r.staleFailure,w.fmt.time($),E))}u.appendChild(M(w)),clearTimeout(d),r.status==="error"&&k()>0&&(d=setTimeout(P,k()))}function I(){_(m),g(m,...e.tools?.(t(),b)??[])}function O(w,C){let $=it(r,w,C,e.isEmpty);if($===r)return;let N=$.status==="error"&&r.status!=="error";r=$,N&&(y=n.claimErrorRole()),clearTimeout(o),P();let x=t();r.status==="ready"&&r.data?b.say(e.summary(r.data,x)):r.status==="error"&&b.say(`${e.title} failed to load.`)}function T(){a?.abort(),clearTimeout(o);let w=t(),C=e.availability(w);if(C){r={...ne(),status:"unavailable",reqId:r.reqId+1,unavailable:C},P();return}a=new AbortController,r=at(r),i=!1,o=setTimeout(()=>{i=!0,r.status==="loading"&&(P(),b.say(`Loading ${e.title}`))},sn);let $=r.reqId;P(),e.request(w,a.signal,b).then(N=>O($,N))}return{element:h,load:T,refreshTools:I}}var un=36e5,ge=24*un,ct=[{key:"24h",ms:ge,days:1,interval:"hour",label:"LAST 24 HOURS",short:"24 hours"},{key:"7d",ms:7*ge,days:7,interval:"day",label:"LAST 7 DAYS",short:"7 days"},{key:"30d",ms:30*ge,days:30,interval:"day",label:"LAST 30 DAYS",short:"30 days"},{key:"90d",ms:90*ge,days:90,interval:"week",label:"LAST 90 DAYS",short:"90 days"}],pn="7d";function Ue(e){return ct.find(t=>t.key===e)}function se(e){return ct.filter(t=>t.days<=e.limits.maxRangeDays)}function mt(e,t){let n=se(e),r=/(?:^|[#&])range=(24h|7d|30d|90d)(?:&|$)/.exec(t)?.[1];return(n.find(o=>o.key===r)??n.find(o=>o.days===e.defaults.rangeDays)??n[0])?.key??pn}function B(e,t,n){let r=D(t);if(Number.isNaN(r))return null;let a=n!==void 0&&e.days>n,o=a?n*ge:e.ms,i=a?n<=1?"hour":"day":e.interval,s=r-o;return{from:new Date(s).toISOString(),to:new Date(r).toISOString(),fromMs:s,toMs:r,interval:i}}function cn(e,t){return e.days>t}function ve(e,t){if(!cn(e,t))return{capped:!1,upper:e.label,lower:e.label.toLowerCase()};let n=t===1?"last day":`last ${t} days`;return{capped:!0,upper:n.toUpperCase(),lower:n}}function V(e){return e.available?null:{reason:e.reason,message:e.message}}function Y(e,t,n,r,a){let o=B(e.range,e.meta.now,a);return o?e.api.get(t,r(o),n):Promise.resolve({kind:"error",failure:{kind:"unexpected",status:0}})}function mn(e,t){let n=l("div","dy-toggle",null,{role:"group","aria-label":"Show as"});for(let[r,a]of[["chart","Chart"],["table","Table"]]){let o=l("button","dy-btn",a,{type:"button","aria-pressed":String(e===r)});o.addEventListener("click",()=>{n.querySelectorAll("button").forEach(i=>i.setAttribute("aria-pressed",String(i===o))),t(r)}),n.appendChild(o)}return n}function Pe(e,t,n){let r=l("div","dy-table-wrap"),a=l("table","dy-twin");a.appendChild(l("caption",void 0,e));let o=l("tr");t.forEach(s=>o.appendChild(l("th",s.numeric?"n":"",s.text,{scope:"col"}))),a.appendChild(l("thead")).appendChild(o);let i=l("tbody");for(let s of n){let d=l("tr");s.forEach((y,h)=>d.appendChild(h===0?l("th","",y,{scope:"row"}):l("td",t[h]?.numeric?"n":"",y))),i.appendChild(d)}return g(a,i),r.appendChild(a),r}function $e(e){let t=l("ul","dy-legend");for(let n of e){let r=l("li");g(r,te(n.swatch),document.createTextNode(`${n.label} `),l("b",void 0,n.value)),t.appendChild(r)}return t}function Ne(e,t){let n=l("div","dy-bar-split",null,{role:"img","aria-label":t});for(let r of e){if(r.value<=0)continue;let a=l("span",`dy-sw-${r.swatch}`);a.style.setProperty("flex-grow",String(r.value)),n.appendChild(a)}return n}function He(e,t,n){t.hidden=e!=="chart",n.hidden=e!=="table";let r=l("div","dy-viz-body");return g(r,t,n),r}function J(e,t,n){return[mn(t,r=>{n(r),e.rerender()})]}var yt=320,yn="Use the left and right arrow keys to move between periods. Escape closes the tooltip.",fn="← → explore · Esc close",hn=0,bn=new Set(["ArrowLeft","ArrowRight","Home","End","Escape"]);function De(e,t){let n=`dy-kbd-${++hn}`,r=l("div","dy-chart",null,{tabindex:0,role:"group","aria-roledescription":"chart","aria-label":e.label,"aria-describedby":n}),a=l("div","dy-chart-wrap");g(a,r,l("p","vh",yn,{id:n}),l("p","dy-kbd-hint",fn,{"aria-hidden":"true"}));let o=l("div","dy-tip",null,{role:"presentation","aria-hidden":"true"});r.appendChild(o);let i=null,s=null,d=null,y=0;function h(){d=null,o.classList.remove("on"),s?.setAttribute("visibility","hidden")}function m(p,c){let v=i,b=v?.centers[p];if(!v||b===void 0)return;d=p;let{title:k,lines:E}=e.tip(p);_(o),o.appendChild(l("b",void 0,k));for(let O of E){let T=l("div");O.swatch&&T.appendChild(l("i",`dy-sw dy-sw-${O.swatch}`)),T.appendChild(document.createTextNode(O.text)),o.appendChild(T)}s?.setAttribute("x1",String(b)),s?.setAttribute("x2",String(b)),s?.setAttribute("visibility","visible");let M=r.clientWidth||yt,P=o.offsetWidth||0,I=b+12+P>M?Math.max(0,b-12-P):b+12;o.style.transform=`translate(${Math.round(I)}px, 8px)`,o.classList.add("on"),c&&t(e.announce(p))}function u(){let p=Math.floor(r.clientWidth)||yt;y=p;let c=e.draw(p,e.height(p));i?.svg.remove(),i=c,s=A("line",{class:"cross",y1:c.plot.top,y2:c.plot.bottom,visibility:"hidden"},c.svg),r.insertBefore(c.svg,o),e.onDraw?.(c),d!==null&&m(Math.min(d,c.centers.length-1),!1)}function f(p){let c=r.getBoundingClientRect(),v=c.width>0?y/c.width:1,b=(p-c.left)*v,k=i?.centers??[],E=0;return k.forEach((M,P)=>{Math.abs(M-b)<Math.abs((k[E]??0)-b)&&(E=P)}),E}if(r.addEventListener("pointermove",p=>m(f(p.clientX),!1)),r.addEventListener("pointerdown",p=>m(f(p.clientX),!1)),r.addEventListener("pointerleave",p=>{p.pointerType==="mouse"&&h()}),r.addEventListener("blur",()=>h()),e.onPick){let p=e.onPick;r.addEventListener("click",c=>p(f(c.clientX))),r.addEventListener("keydown",c=>{c.key==="Enter"&&d!==null&&p(d)})}if(r.addEventListener("keydown",p=>{let c=i?.centers.length??0;if(!bn.has(p.key)||c===0)return;if(p.preventDefault(),p.key==="Escape")return h();let v=c-1,b;p.key==="Home"?b=0:p.key==="End"?b=v:d===null?b=p.key==="ArrowLeft"?v:0:b=Math.min(v,Math.max(0,d+(p.key==="ArrowLeft"?-1:1))),m(b,!0)}),typeof ResizeObserver<"u"){let p=0;new ResizeObserver(()=>{cancelAnimationFrame(p),p=requestAnimationFrame(()=>{r.isConnected&&Math.floor(r.clientWidth)!==y&&u()})}).observe(r)}return{element:a,redraw:u}}function de(e,t,n,r){for(let a of n.ticks){let o=S(t.bottom-r.yRatio(a)*(t.bottom-t.top));A("line",{class:a===0?"base-l":"grid-l",x1:t.left,x2:t.right,y1:o,y2:o},e);let i=A("text",{class:"ax",x:t.left-8,y:Number(o)+4,"text-anchor":"end"},e);i.textContent=r.yLabel(a)}}function ue(e,t,n){for(let{x:r,text:a}of n){A("line",{class:"base-l",x1:S(r),x2:S(r),y1:t.bottom,y2:t.bottom+4},e);let o=r>t.right-20?"end":"middle",i=A("text",{class:"ax",x:S(r),y:t.bottom+20,"text-anchor":o},e);i.textContent=a}}var gn={step:.5,yMax:1,ticks:[0,.5,1]};function ft(e,t,n){let r=ae(e,t),a=n.series[0]?.values.length??0,o=Array.from({length:a},(u,f)=>n.series.reduce((p,c)=>p+(c.values[f]??0),0)),i=n.percent?gn:oe(Math.max(0,...o)),s=A("svg",{viewBox:`0 0 ${e} ${t}`,role:"img","aria-hidden":"true"}),d=n.percent?u=>`${Math.round(u*100)}%`:n.compact;de(s,r,i,{yLabel:d,yRatio:u=>u/i.yMax});let y=nt(r,a),h=y.xs.map(u=>u+y.bandWidth/2);ue(s,r,n.xLabels(h));let m=r.bottom-r.top;return y.xs.forEach((u,f)=>{let p=o[f]??0;if(n.percent&&p===0){let v=r.bottom-1;A("line",{class:"stub",x1:S(u),x2:S(u+y.bandWidth),y1:v,y2:v},s);return}let c=r.bottom;for(let v of n.series){let b=v.values[f]??0;if(b<=0)continue;let k=(n.percent?b/p:b)/i.yMax*m;c-=k,A("rect",{class:`seg dy-col ${v.fill}`,x:S(u),y:S(c),width:S(y.bandWidth),height:S(k)},s).style.setProperty("--i",String(Math.min(f,30)))}}),{svg:s,plot:r,centers:h,yMax:i.yMax,line:[]}}var vn=(e,t)=>e.reduce((n,r)=>n+(r.values[t]??0),0);function pe(e,t,n,r){let a=t.fmt,{series:o,order:i,times:s,interval:d,win:y}=n,h=c=>Z(a,s[c]??0,d),m=De({label:n.ariaLabel,height:c=>c<400?180:200,draw:(c,v)=>ft(c,v,{series:o.map(b=>({fill:`f-${b.swatch}`,values:b.values})),percent:n.percent,xLabels:Re(a,s,d),compact:a.compact}),tip:c=>{let v=vn(o,c),b=c===s.length-1&&y!==null&&re(s[c]??0,d)>y.toMs;return{title:Ce(a,s[c]??0,d,{last:b,first:c===0,fromMs:y?.fromMs??0}),lines:v===0?[{swatch:null,text:"No visits"}]:i.map(k=>{let E=o[k],M=E.values[c]??0;return{swatch:E.swatch,text:`${E.label} ${a.num(M)} (${G(M/v)})`}})}},announce:c=>`${h(c)}: ${o.map(v=>`${v.label.toLowerCase()} ${v.values[c]??0}`).join(", ")}`,...n.onPick?{onPick:n.onPick}:{}},e.say),u=i.map(c=>o[c]),f=Pe(n.caption,[{text:"Period (UTC)",numeric:!1},...u.map(c=>({text:c.label,numeric:!0})),...n.extra?[{text:n.extra.text,numeric:!0}]:[]],s.map((c,v)=>[h(v),...u.map(b=>a.num(b.values[v]??0)),...n.extra?[n.extra.cell(v)]:[]])),p=l("div");return g(p,$e(u.map(c=>({swatch:c.swatch,label:c.label,value:a.num(c.values.reduce((v,b)=>v+b,0))}))),He(r,m.element,f)),queueMicrotask(m.redraw),p}function wn(e,t,n,r){let a=t.fmt,{totals:o}=e,i=l("div"),s=l("p","dy-kpi-num");return g(s,document.createTextNode(o.rate===null?"—":G(o.rate)),l("small","c-granted","granted")),i.appendChild(s),i.appendChild(l("p","dy-kpi-sub",`${a.num(o.denied)} visits declined. Declined visits store only the page and the time. No IP, user id, name or email is kept, and they are never counted as new or returning.`)),i.appendChild(Ne([{swatch:"granted",value:o.granted},{swatch:"declined",value:o.denied}],`${a.num(o.granted)} granted, ${a.num(o.denied)} declined`)),i.appendChild(pe(n,t,{interval:e.interval,times:e.points.map(d=>D(d.bucket)),series:[{swatch:"granted",label:"Granted",values:e.points.map(d=>d.granted)},{swatch:"declined",label:"Declined",values:e.points.map(d=>d.denied)}],order:[0,1],percent:!0,win:B(t.range,t.meta.now),ariaLabel:`Consent per ${j(e.interval)}. ${a.num(o.granted)} granted, ${a.num(o.denied)} declined${o.rate===null?"":`, ${G(o.rate)} granted overall`}.`,caption:`Consent per ${j(e.interval)}`,extra:{text:"Share of visits",cell:d=>e.points[d]?.rate==null?"—":G(e.points[d].rate)}},r)),i}function ht(e,t){let n="chart";return q({id:"consent",no:4,title:"Consent",className:"p-consent",availability:a=>V(a.meta.widgets.consentRate),request:(a,o)=>Y(a,"consent-rate",o,i=>({from:i.from,to:i.to,interval:i.interval})),isEmpty:a=>a.totals.granted+a.totals.denied===0,skeleton:()=>{let a=l("div");return g(a,ie(),Q(200)),a},empty:()=>F({title:"No consent decisions yet.",body:"Granted and declined visits will appear here."}),ready:(a,o,i)=>wn(a,o,i,n),summary:(a,o)=>`Consent loaded: ${o.fmt.num(a.totals.granted)} granted, ${o.fmt.num(a.totals.denied)} declined`,tools:(a,o)=>J(o,n,i=>{n=i})},e,t)}function bt(e,t,n){let r=ae(e,t),a=n.values.length,o=oe(Math.max(0,...n.values)),i=A("svg",{viewBox:`0 0 ${e} ${t}`,role:"img","aria-hidden":"true"});de(i,r,o,{yLabel:n.compact,yRatio:u=>u/o.yMax});let s=Me(r,a);ue(i,r,n.xLabels(s));let d=n.values.map(u=>r.bottom-u/o.yMax*(r.bottom-r.top)),y=s.map((u,f)=>`${S(u)},${S(d[f]??r.bottom)}`);if(a>=2){let u=y.length-1,f=n.inProgressLast?y.slice(0,u):y;A("polygon",{class:"area",points:`${S(r.left)},${r.bottom} ${y.join(" ")} ${S(r.right)},${r.bottom}`},i),f.length>=2&&A("polyline",{class:"line",points:f.join(" ")},i),n.inProgressLast&&A("polyline",{class:"line-dash",points:y.slice(u-1).join(" ")},i)}let h=s[a-1],m=d[a-1];return n.inProgressLast&&h!==void 0&&m!==void 0?A("circle",{class:"mark-now",cx:S(h),cy:S(m),r:4},i):a===1&&h!==void 0&&m!==void 0&&A("circle",{class:"cross-dot",cx:S(h),cy:S(m),r:4},i),{svg:i,plot:r,centers:s,yMax:o.yMax,line:s.map((u,f)=>({x:u,y:d[f]??r.bottom}))}}var xn=20,Tn=12,Cn=(e,t=Tn)=>[...e].length>t?`${[...e].slice(0,t-1).join("")}…`:e;function gt(e,t,n){let r=ae(e,t),a=n.series[0]?.values.length??0,o=Array.from({length:a},(f,p)=>n.series.reduce((c,v)=>c+(v.values[p]??0),0)),i=oe(Math.max(0,...o)),s=r.bottom-r.top,d=A("svg",{viewBox:`0 0 ${e} ${t}`,role:"img","aria-hidden":"true"});de(d,r,i,{yLabel:n.compact,yRatio:f=>f/i.yMax});let y=Me(r,a);ue(d,r,n.xLabels(y));let h=f=>r.bottom-f/i.yMax*s,m=new Array(a).fill(0),u=[];for(let f of n.series){let p=m.map((E,M)=>E+(f.values[M]??0)),c=y.map((E,M)=>`${S(E)},${S(h(p[M]??0))}`),v=y.map((E,M)=>`${S(E)},${S(h(m[M]??0))}`).reverse();a>=2?A("polygon",{class:`seg ${f.fill}`,points:[...c,...v].join(" ")},d):a===1&&A("rect",{class:`seg ${f.fill}`,x:S(r.left),width:S(r.right-r.left),y:S(h(p[0]??0)),height:S(h(m[0]??0)-h(p[0]??0))},d);let b=Math.min(Math.max(n.labelIndex,0),a-1),k=h(m[b]??0)-h(p[b]??0);f.labelClass&&k>=xn&&u.push({x:(y[b]??r.right)-8,y:h(p[b]??0)+k/2+4,text:Cn(f.label),cls:f.labelClass}),m=p}for(let f of u){let p=A("text",{class:`band-label ${f.cls}`,x:S(f.x),y:S(f.y)},d);p.textContent=f.text}return{svg:d,plot:r,centers:y,yMax:i.yMax,line:y.map((f,p)=>({x:f,y:h(o[p]??0)}))}}var kn="Rows without a value for this field.",En=e=>e.reduce((t,n)=>t+n,0),Sn=e=>e.points.map(t=>t.visits),vt=(e,t)=>e.map((n,r)=>n+(t[r]??0));function ce(e){return{...e,total:En(e.values)}}function Ve(e,t){let n=[],r=0,a=null,o=-1;for(let i of t){let s=Sn(i);if(i.isOther)o=n.length,n.push(ce({name:"Other",swatch:"other",fill:"f-other",labelClass:null,values:s}));else if(i.value===null)n.push(ce({name:"No value",swatch:"unknown",fill:"f-none",labelClass:null,values:s,note:kn}));else if(e==="consent"){let d=i.value==="true";n.push(ce({name:d?"Granted":"Declined",swatch:d?"granted":"declined",fill:d?"f-granted":"f-declined",labelClass:null,values:s}))}else r<5?(r+=1,n.push(ce({name:i.value,swatch:`cat${r}`,fill:`f-cat${r}`,labelClass:`on-cat${r}`,values:s}))):a=a?vt(a,s):s}if(a)if(o>=0){let i=n[o];n[o]=ce({...i,values:vt(i.values,a)})}else n.push(ce({name:"Other",swatch:"other",fill:"f-other",labelClass:null,values:a}));return n}function Mn(e){return e>=800?340:e>=640?300:e>=400?260:220}function wt(e){let t=0,n=Number.POSITIVE_INFINITY,r=0,a=0;return e.forEach((o,i)=>{o.visits>t&&(t=o.visits,r=i),o.visits<n&&(n=o.visits,a=i)}),{max:t,min:Number.isFinite(n)?n:0,maxAt:r,minAt:a}}function Rn(e,t,n,r){let{points:a,interval:o,total:i}=e,s=u=>Z(t.fmt,D(a[u]?.bucket??""),o),d=wt(a),y=a.length?s(0):"",h=a.length?s(a.length-1):"",m=n.length?` Split by ${r}: ${[...n].reverse().map(u=>`${u.name} ${t.fmt.num(u.total)}`).join(", ")}.`:"";return`Visits per ${j(o)}, ${y} to ${h} UTC. Total ${t.fmt.num(i)}. Highest: ${s(d.maxAt)} with ${t.fmt.num(d.max)}. Lowest: ${s(d.minAt)} with ${t.fmt.num(d.min)}.${m}`}function An(e,t){let n=$e([...e].reverse().map(r=>({swatch:r.swatch,label:r.name,value:t.fmt.num(r.total)})));return[...e].reverse().forEach((r,a)=>{if(!r.note)return;let o=n.children[a];o.setAttribute("title",r.note),o.appendChild(z(` ${r.note}`))}),n}function Ln(e,t,n,r){let a=t.fmt,o=B(t.range,t.meta.now),i=e.points.map(T=>D(T.bucket)),s=e.points.map(T=>T.visits),d=i.length,y=i[d-1]??0,h=o?re(y,e.interval)>o.toMs:!1,m=e.breakdown,u=m?Ve(m.key,m.series):[],f=t.meta.breakdowns.find(T=>T.key===m?.key)?.label??m?.key??"",p=l("div","dy-hero-wrap"),c=l("p","dy-hero-num");g(c,z("Total visits: "),document.createTextNode(a.num(e.total)));let v=wt(e.points),b=l("p","dy-kpi-sub",`${Z(a,i[v.maxAt]??0,e.interval)} was the busiest ${j(e.interval)}: ${a.num(v.max)} visits.`);g(p,c,b);let k=[...u].reverse(),E=Re(a,i,e.interval),M=De({label:Rn(e,t,u,f),height:Mn,draw:(T,w)=>u.length?gt(T,w,{series:u.map(C=>({fill:C.fill,labelClass:C.labelClass,label:C.name,values:C.values})),xLabels:E,compact:a.compact,labelIndex:h?d-2:d-1}):bt(T,w,{values:s,xLabels:E,inProgressLast:h,compact:a.compact}),tip:T=>({title:Ce(a,i[T]??0,e.interval,{last:h&&T===d-1,first:T===0,fromMs:o?.fromMs??0}),lines:u.length?k.map(w=>({swatch:w.swatch,text:`${w.name} ${a.num(w.values[T]??0)}`})):[{swatch:null,text:`${a.num(s[T]??0)} visits`}]}),announce:T=>u.length?`${Z(a,i[T]??0,e.interval)}: ${k.map(w=>`${w.name} ${w.values[T]??0}`).join(", ")}`:`${Z(a,i[T]??0,e.interval)}: ${a.num(s[T]??0)} visits`,onDraw:T=>{let w={w:c.offsetWidth,h:c.offsetHeight},C=T.plot.right-T.plot.left;p.classList.toggle("dy-hero--stacked",rt(T.line,w,C)),c.style.setProperty("--ny",`${M.element.offsetTop+6}px`)}},n.say),P=[{text:"Period (UTC)",numeric:!1},{text:"Visits",numeric:!0},...k.map(T=>({text:T.name,numeric:!0}))],I=Pe(`Visits per ${j(e.interval)}, ${t.range.short}${u.length?`, split by ${f}`:""}`,P,e.points.map((T,w)=>[Z(a,i[w]??0,e.interval),a.num(T.visits),...k.map(C=>a.num(C.values[w]??0))])),O=l("figure","dy-figure");return g(O,He(r.view,M.element,I)),u.length&&O.appendChild(An(u,t)),p.appendChild(O),queueMicrotask(M.redraw),p}function Pn(e,t){let n=B(e.range,e.meta.now);if(e.range.key==="90d"){let a=l("p");g(a,document.createTextNode("dynalitics has not stored a visit in the last 90 days. Call "),l("code",void 0,"track()"),document.createTextNode(" from a route handler, check that your app's "),l("code",void 0,"onError"),document.createTextNode(" is not reporting insert errors, then refresh."));let o=l("button","dy-btn","Refresh",{type:"button"});return o.addEventListener("click",t.refresh),F({title:"Nothing recorded yet.",body:""},[o],a)}let r=l("button","dy-btn","Try 90 days",{type:"button"});return r.addEventListener("click",()=>t.setRange("90d")),F({title:"The ledger is empty for this range.",body:n?`No visits between ${n.from.slice(0,10)} and ${n.to.slice(0,10)} (UTC).`:"No visits in this range."},[r])}function xt(e,t){let n={view:"chart",split:null},r=o=>n.split!==null&&o.meta.breakdowns.some(i=>i.key===n.split)?n.split:null;return q({id:"hero",no:1,title:"Visits over time",className:"p-hero",sheet:!0,eyebrow:o=>`No. 1 · ${o.range.label}`,availability:o=>V(o.meta.widgets.visitsOverTime),request:async(o,i)=>{let s=r(o),d=await Y(o,"visits-over-time",i,y=>({from:y.from,to:y.to,interval:y.interval,...s?{breakdown:s}:{}}));return d.kind==="data"&&t.touchUpdated(d.meta.generatedAt),d},isEmpty:o=>o.total===0,skeleton:()=>{let o=l("div");return g(o,ie(),Q(220)),o},empty:Pn,ready:(o,i,s)=>Ln(o,i,s,n),summary:(o,i)=>`Visits over time loaded: ${i.fmt.num(o.total)} visits`,tools:(o,i)=>{let s=[];if(o.meta.breakdowns.length>0){let d=l("label","dy-split-lbl-sel"),y=l("select","dy-select");y.appendChild(l("option",void 0,"None",{value:""}));for(let h of o.meta.breakdowns)y.appendChild(l("option",void 0,h.label,{value:h.key,...h.key===r(o)?{selected:"selected"}:{}}));y.addEventListener("change",()=>{n.split=y.value===""?null:y.value,i.load()}),g(d,l("span","dy-eyebrow","Split by"),y),s.push(d)}return s.push(...J(i,n.view,d=>{n.view=d})),s}},e,t)}var $n="Unknown means no consent, or consent but no usable identity.",Nn="Visitors are matched by IP address as well as user id, so shared addresses can merge people and hashed addresses reset when the salt changes. Read these as estimates, not a head count.",Tt=e=>e===1?"Showing the last day: this widget is capped at 1 day.":`Showing the last ${e} days: this widget is capped at ${e} days.`;function Hn(e,t,n,r){let a=t.fmt,{totals:o}=e,i=o.new+o.returning,s=i+o.unknown,d=t.meta.limits.newVsReturningMaxRangeDays,y=ve(t.range,d),h=e.points.map(p=>D(p.bucket)),m=l("div"),u=l("p","dy-kpi-num");g(u,document.createTextNode(i>0?G(o.new/i):"—"),l("small","c-new","new")),e.approximate&&u.appendChild(l("span","dy-estimate","Estimate")),m.appendChild(u);let f=o.unknown>0?` ${a.num(o.unknown)} (${G(o.unknown/s)}) could not be classified: declined consent or no usable identity.`:"";m.appendChild(l("p","dy-kpi-sub",i>0?`Based on ${a.num(i)} visits with a known visitor.${f}`:"No visitors could be classified yet."));for(let p of[y.capped?Tt(d):null,e.approximate?Nn:null,$n])p&&m.appendChild(l("p","dy-note",p));return m.appendChild(Ne([{swatch:"new",value:o.new},{swatch:"returning",value:o.returning},{swatch:"unknown",value:o.unknown}],`${a.num(o.new)} new, ${a.num(o.returning)} returning, ${a.num(o.unknown)} unknown`)),m.appendChild(pe(n,t,{interval:e.interval,times:h,series:[{swatch:"returning",label:"Returning",values:e.points.map(p=>p.returning)},{swatch:"new",label:"New",values:e.points.map(p=>p.new)},{swatch:"unknown",label:"Unknown",values:e.points.map(p=>p.unknown)}],order:[1,0,2],percent:!1,win:B(t.range,t.meta.now,d),ariaLabel:`Visits by new, returning and unknown visitors per ${j(e.interval)}. ${a.num(o.new)} new, ${a.num(o.returning)} returning, ${a.num(o.unknown)} unknown in total. Window: ${y.lower}.`,caption:`New vs returning visits per ${j(e.interval)}, ${y.lower} (UTC)`},r)),m}function Ct(e,t){let n="chart",r=o=>o.meta.limits.newVsReturningMaxRangeDays;return q({id:"nvr",no:3,title:"New vs returning",className:"p-nvr",eyebrow:o=>`No. 3 · ${ve(o.range,r(o)).upper}`,reloadOnBadRequest:!0,availability:o=>V(o.meta.widgets.newVsReturning),request:(o,i)=>Y(o,"new-vs-returning",i,s=>({from:s.from,to:s.to,interval:s.interval}),r(o)),isEmpty:o=>o.totals.new+o.totals.returning+o.totals.unknown===0,skeleton:()=>{let o=l("div");return g(o,ie(),Q(200)),o},empty:o=>{let i=F({title:"Nobody classified yet.",body:"Visits count as new or returning only when consent was given and an identity was supplied."});return ve(o.range,r(o)).capped&&i.appendChild(l("p","dy-note",Tt(r(o)))),i},ready:(o,i,s)=>Hn(o,i,s,n),summary:(o,i)=>`New vs returning loaded (${ve(i.range,r(i)).lower}): ${i.fmt.num(o.totals.new)} new, ${i.fmt.num(o.totals.returning)} returning, ${i.fmt.num(o.totals.unknown)} unknown`,tools:(o,i)=>J(i,n,s=>{n=s})},e,t)}var Dn=[10,25,100];function kt(e,t,n,r,a,o=!1){let i=l("li",o?"dy-page-row dy-page-row--other":"dy-page-row"),s=l("span","dy-nums");return g(s,l("span","dy-count",n),l("span","dy-share",r)),g(i,l("span","dy-rank",e,{"aria-hidden":"true"}),t,s,l("span","dy-bar",null,{"aria-hidden":"true"})),i.lastElementChild.style.setProperty("--r",String(Qe(a))),i}function Vn(e,t){let n=t.fmt,r=Math.max(1,...e.pages.map(i=>i.visits)),a=l("div");a.appendChild(l("p","dy-eyebrow",`TOP ${e.pages.length} · ${n.num(e.totalVisits)} VISITS · ${t.range.label}`));let o=l("ol","dy-pages");if(e.pages.forEach((i,s)=>{o.appendChild(kt(String(s+1),W("span",i.page,"dy-page",!0),n.num(i.visits),qe(i.share,i.visits),i.visits/r))}),e.otherVisits>0){let i=e.totalVisits>0?e.otherVisits/e.totalVisits:0;o.appendChild(kt("",l("span","dy-page","Other pages"),n.num(e.otherVisits),qe(i,e.otherVisits),e.otherVisits/r,!0))}return a.appendChild(o),a}function Et(e,t){let n=null,r=o=>n??Math.min(o.meta.defaults.topPagesLimit,o.meta.limits.topPagesMax);return q({id:"pages",no:2,title:"Top pages",className:"p-pages",availability:o=>V(o.meta.widgets.topPages),request:(o,i)=>Y(o,"top-pages",i,s=>({from:s.from,to:s.to,limit:String(r(o))})),isEmpty:o=>o.pages.length===0,skeleton:()=>le(10),empty:()=>F({title:"No pages yet.",body:"Pages appear once visits are recorded."}),ready:Vn,summary:(o,i)=>`Top pages loaded: ${o.pages.length} pages, ${i.fmt.num(o.totalVisits)} visits`,tools:(o,i)=>{let s=Dn.filter(m=>m<=o.meta.limits.topPagesMax),d=r(o);s.includes(d)||s.push(d);let y=l("label","dy-select-lbl"),h=l("select","dy-select");for(let m of s)h.appendChild(l("option",void 0,`Top ${m}`,{value:m,...m===d?{selected:"selected"}:{}}));return h.addEventListener("change",()=>{n=Number(h.value),i.load()}),g(y,l("span","vh","Number of pages"),h),[y]}},e,t)}function Mt(e){return[{key:"time",label:"Time (UTC)",pii:!1,show:!0},{key:"page",label:"Page",pii:!1,show:!0},{key:"consent",label:"Consent",pii:!1,show:!0},{key:"first",label:"First visit",pii:!1,show:e.isFirstVisit},{key:"ip",label:"IP",pii:!0,show:e.ipAddress},{key:"email",label:"Email",pii:!0,show:e.email},{key:"user",label:e.username?"Username":"User ID",pii:!0,idOnly:!e.username,show:e.username||e.userId}].filter(n=>n.show).map(({key:n,label:r,pii:a,idOnly:o})=>o===void 0?{key:n,label:r,pii:a}:{key:n,label:r,pii:a,idOnly:o})}function K(e,t){return e.setAttribute("role",t),e}function Rt(e){let t=l("span","dash");return g(t,l("span",void 0,"—",{"aria-hidden":"true"}),z(e)),t}function je(e){return e===null?Rt("not provided"):W("span",e)}function In(e,t){let n=K(l("td","t-time",null,{"data-label":"Time"}),"cell"),r=Ze(e,t);if(!r)return n.appendChild(W("span",t)),n;let a=l("time",void 0,null,{datetime:t});return g(a,l("b",void 0,r.time),l("span","dy-t2",r.date)),n.appendChild(a),n}function Fn(e){let t=l("span","chip");return g(t,te(e?"granted":"declined"),document.createTextNode(e?"Granted":"Declined")),t}function On(e){let t=K(l("td","c-first",null,{"data-label":"First"}),"cell");return e===null?t.appendChild(Rt("unknown")):t.appendChild(l("span",e?"yes":"no",e?"Yes":"No")),t}function qn(e,t){let n=K(l("td","c-user dy-pii",null,{"data-label":t?"User ID":"User"}),"cell");return t?(n.appendChild(je(e.userId)),n):(n.appendChild(je(e.username)),e.userId&&n.appendChild(W("span",e.userId,"dy-t2")),n)}function St(e,t,n){let r=K(l("td",`${e} dy-pii`,null,{"data-label":t}),"cell");return r.appendChild(je(n)),r}function At(e,t,n){let r=K(l("tr"),"row"),a=t.filter(i=>i.pii).length,o=!1;for(let i of t){if(i.pii&&!e.consent){if(!o){let s=K(l("td","na","Not collected: visitor declined consent",{colspan:a}),"cell");r.appendChild(s),o=!0}continue}switch(i.key){case"time":r.appendChild(In(n,e.timestamp));break;case"page":r.appendChild(K(W("td",e.pageVisited,"c-page",!0),"cell"));break;case"consent":r.appendChild(K(l("td","c-consent",null,{"data-label":"Consent"}),"cell")).appendChild(Fn(e.consent));break;case"first":r.appendChild(On(e.consent?e.isFirstVisit:null));break;case"ip":r.appendChild(St("c-ip","IP",e.ipAddress));break;case"email":r.appendChild(St("c-email","Email",e.email));break;case"user":r.appendChild(qn(e,i.idOnly===!0));break}}return r}function Lt(e,t){let n=K(l("tr"),"row");for(let r of e){let a=K(l("th",`dy-th c-${r.key}${r.pii&&t?" th-lock":""}`,r.label,{scope:"col"}),"columnheader");r.pii&&t&&a.appendChild(z(" (masked)")),n.appendChild(a)}return n}function Be(e){return e.kind!=="data"?e:{kind:"data",meta:e.meta,data:{visits:e.data.visits,nextCursor:e.data.nextCursor,piiMasked:e.meta.piiMasked,...typeof e.data.matched=="number"?{matched:e.data.matched}:{}}}}var _n={id:"table",no:5,title:"Recent visits",className:"p-table",ariaLabel:"Recent visits, newest first",empty:{title:"No visits in this range.",body:"Newest visits appear here as soon as they are written."},availability:e=>V(e.meta.widgets.recentVisits),fetch:async(e,t,n)=>Be(await Y(e,"recent-visits",t,r=>({from:r.from,to:r.to,...n})))};function Pt(e){return Math.max(1,Math.min(e.meta.defaults.recentLimit,e.meta.limits.recentMax))}function Ie(e,t,n=_n){let r=!1,a=!1,o=!1,i=(m,u)=>({limit:String(Pt(m)),...r?{pii:"reveal"}:{},...u});async function s(m){let u=m.getState(),f=u.data?.nextCursor;if(!f||o)return;o=!0,a=!1,m.rerender();let p=m.getCtx(),c=await n.fetch(p,new AbortController().signal,i(p,{cursor:f}),m);o=!1;let v=m.getState();if(v.reqId!==u.reqId||!v.data)return;if(c.kind!=="data"){a=c.kind==="error",m.rerender();return}let b=c.data.visits.length,k=v.data.visits.length;m.patch({...v.data,visits:[...v.data.visits,...c.data.visits],nextCursor:c.data.nextCursor,piiMasked:c.data.piiMasked});let E=m.element.querySelectorAll("tbody tr")[k]?.querySelector("td");E instanceof HTMLElement&&(E.setAttribute("tabindex","-1"),E.focus()),m.say(`${b} more visits loaded`)}function d(m,u,f){let p=l("div","dy-more");if(a&&p.appendChild(l("span","dy-note","Could not load more visits. ")),m.nextCursor){let c=l("button","dy-btn dy-btn--text",o?"Loading...":`Load ${Pt(u)} more`,{type:"button"});c.disabled=o,c.setAttribute("aria-busy",String(o)),c.addEventListener("click",()=>{s(f)}),p.appendChild(c)}else p.appendChild(l("span","dy-note",u.meta.features.recentVisitsCursor?"End of range.":`Showing the latest ${m.visits.length} visits.`));return p}function y(m,u,f){let p=Mt(u.meta.fields),c=r&&!m.piiMasked,v=l("div",c?"dy-revealed":"");c?v.appendChild(l("p","dy-note","Showing personal data. Hide when sharing your screen.")):r&&m.piiMasked&&v.appendChild(l("p","dy-note","The server kept values masked."));let b=l("table","dy-table",null,{role:"table","aria-label":n.ariaLabel}),k=l("thead",void 0,null,{role:"rowgroup"});k.appendChild(Lt(p,m.piiMasked));let E=l("tbody",void 0,null,{role:"rowgroup"}),M=m.visits.map(I=>At(I,p,u.fmt));M.forEach(I=>E.appendChild(I)),n.decorate?.(m,M),g(b,k,E);let P=l("div","dy-table-wrap");return P.appendChild(b),g(v,P,d(m,u,f)),v}let h={id:n.id,no:n.no,title:n.title,className:n.className,sheet:!0,availability:n.availability,request:(m,u,f)=>(a=!1,n.fetch(m,u,i(m,{}),f)),isEmpty:m=>m.visits.length===0,skeleton:()=>le(6),empty:()=>F(n.empty),ready:y,summary:m=>`${n.title} loaded: ${m.visits.length} rows`,tools:(m,u)=>{if(!m.meta.features.piiRevealable)return[];let f=l("button","dy-btn",r?"Hide full values":"Show full values",{type:"button","aria-pressed":String(r)});return f.addEventListener("click",()=>{r=!r,f.setAttribute("aria-pressed",String(r)),f.textContent=r?"Hide full values":"Show full values",u.load()}),[f]}};return q(h,e,t)}var $t=[5,15,30],Nt={q:"",split:null,tail:0,zoom:null},Fe=(e,t)=>{let n=new RegExp(`(?:^|[#&])${t}=([^&]*)`).exec(e);if(!n)return null;try{return decodeURIComponent(n[1]??"")}catch{return null}};function Ht(e){return Fe(e,"view")==="explorer"}function Dt(e,t){let n=new Set(t.explorer?.fields.filter(i=>i.facet).map(i=>i.key)??[]),r=Fe(e,"split"),a=Number(Fe(e,"tail")),o=t.explorer?.limits.maxQueryChars??512;return{q:(Fe(e,"q")??"").slice(0,o),split:r!==null&&n.has(r)?r:null,tail:Vt(a,t),zoom:null}}function Vt(e,t){let n=(t.explorer?.limits.tailMinIntervalMs??5e3)/1e3;return $t.includes(e)&&e>=n?e:0}function It(e){return $t.filter(t=>Vt(t,e)===t)}function Ft(e,t){let n=["view=explorer",`range=${e}`];return t.q&&n.push(`q=${encodeURIComponent(t.q)}`),t.split&&n.push(`split=${encodeURIComponent(t.split)}`),t.tail&&n.push(`tail=${t.tail}`),`#${n.join("&")}`}function Ot(e,t){let n=e.explorer?.limits.maxRangeDays??31;return t.filter(r=>r.days<=n)}function qt(e,t,n){return n?{from:new Date(n.fromMs).toISOString(),to:new Date(n.toMs).toISOString(),fromMs:n.fromMs,toMs:n.toMs,interval:n.interval}:B(e,t)}function _t(e,t,n){let a={day:"hour",week:"day",month:"day"}[t];if(!a)return null;let o=Math.max(e,n.fromMs),i=Math.min(re(e,t),n.toMs);return i>o?{fromMs:o,toMs:i,interval:a}:null}var Un=/[\s:*"-]/,Oe=Symbol("any value");function Ut(e,t,n,r){if(n!==Oe&&n.includes('"'))return null;let a=n===Oe?"*":n===""||Un.test(n)?`"${n}"`:n,o=`${r?"-":""}${t}:${a}`,i=e.trim();return i.split(/\s+/).includes(o)?i:i?`${i} ${o}`:o}async function me(e,t,n,r,a,o=()=>({})){let i=t.window(e);if(!i)return{kind:"error",failure:{kind:"unexpected",status:0}};let{q:s}=t.state(),d=t.begin(),y;try{y=await e.api.get(n,{from:i.from,to:i.to,...s?{q:s}:{},...o(i)},r)}finally{d()}if(y.kind==="data"&&t.onData(y.meta.generatedAt),y.kind!=="error")return y;if(y.failure.code!=="invalid_query")return t.onFailure(y.failure),y;t.onQueryError(y.failure);let h=a.getState();return h.data===null?y:{kind:"data",data:h.data,meta:{generatedAt:h.generatedAt??e.meta.now,piiMasked:!0,cached:!1}}}var we={title:"No visits match this search.",body:"Try fewer words, or a longer range."};function jn(e){return e.breakdown?Ve(e.breakdown.key,e.breakdown.series).map(t=>({swatch:t.swatch,label:t.name,values:t.values})):[{swatch:"match",label:"Matches",values:e.points.map(t=>t.visits)}]}function jt(e,t,n){let r="chart";return q({id:"xhist",no:1,title:"Matches over time",className:"p-xhist",availability:o=>o.meta.widgets.explorer?V(o.meta.widgets.explorer):null,request:(o,i,s)=>me(o,n,"search/histogram",i,s,d=>({interval:d.interval,...n.state().split?{split:n.state().split}:{}})),isEmpty:o=>o.total===0,skeleton:()=>Q(200),empty:()=>F(we),ready:(o,i,s)=>{let d=i.fmt,y=o.points.map(c=>D(c.bucket)),h=jn(o),m=n.window(i),u=j(o.interval),f=m!==null&&o.interval!=="hour",p=l("div");return p.appendChild(pe(s,i,{interval:o.interval,times:y,series:h,order:h.map((c,v)=>v),percent:!1,win:m,ariaLabel:`Matching visits per ${u}: ${d.num(o.total)} in total.${f?" Press Enter on a period to zoom into it.":""}`,caption:`Matching visits per ${u}`,...f?{onPick:c=>{let v=_t(y[c]??0,o.interval,m);v&&n.zoom(v)}}:{}},r)),p},summary:(o,i)=>`Matches over time loaded: ${i.fmt.num(o.total)} visits`,tools:(o,i)=>J(i,r,s=>{r=s})},e,t)}var Bn="This value contains a double quote and cannot be added to a search.";function Wn(e){let t=l("span","dy-xbar",null,{"aria-hidden":"true"});return t.style.setProperty("--w",String(Math.max(0,Math.min(1,e)))),t}function Yn(e,t,n,r,a,o){let i=l("li","dy-xval"),s=a.fmt.num(n);if(t.includes('"'))g(i,W("span",t,"dy-xval-v",!0),z(Bn));else{let d=l("button","dy-xval-add",null,{type:"button","aria-label":`Only ${e.key} = ${t} (${s} visits)`});d.appendChild(W("span",t,"dy-xval-v",!0)),d.addEventListener("click",()=>o.addClause(e.key,t,!1));let y=l("button","dy-xval-drop","−",{type:"button","aria-label":`Exclude ${e.key} = ${t}`});y.addEventListener("click",()=>o.addClause(e.key,t,!0)),g(i,d,y)}return g(i,l("span","dy-xval-n",s),Wn(r===0?0:n/r)),i}function Kn(e,t,n,r){let a=l("section","dy-xfacet",null,{"aria-label":`Field ${e.key}`});a.appendChild(l("h3","dy-xfacet-key",e.key));let o=l("ul","dy-xvals");for(let i of e.values)o.appendChild(Yn(e,i.value,i.count,t,n,r));if(e.nullCount>0){let i=l("li","dy-xval dy-xval--none"),s=l("button","dy-xval-add",null,{type:"button","aria-label":`Only visits without a ${e.key} value`});g(s,te("unknown"),l("span","dy-xval-v","No value")),s.addEventListener("click",()=>r.addClause(e.key,Oe,!0)),g(i,s,l("span","dy-xval-n",n.fmt.num(e.nullCount))),o.appendChild(i)}if(e.otherCount>0){let i=l("li","dy-xval dy-xval--other");g(i,te("other"),l("span","dy-xval-v","Other values"),l("span","dy-xval-n",n.fmt.num(e.otherCount))),o.appendChild(i)}return a.appendChild(o),a}function Bt(e,t,n){return q({id:"xfields",no:2,title:"Fields",className:"p-xfields",availability:a=>a.meta.widgets.explorer?V(a.meta.widgets.explorer):null,request:(a,o,i)=>me(a,n,"search/fields",o,i),isEmpty:a=>a.matched===0,skeleton:()=>le(5),empty:()=>F(we),ready:(a,o)=>{let i=l("div","dy-xfacets");for(let s of a.fields)i.appendChild(Kn(s,a.matched,o,n));return i},summary:a=>`Fields loaded for ${a.matched} matching visits`},e,t)}var Xn=[["pricing",'any searchable field contains "pricing" (case-insensitive)'],['"two words"',"the exact phrase"],["page:/blog*","starts with /blog (also *end and *contains*)"],["plan:pro","equals pro"],["-consent:false","leave out matches; - works on any part"],["amount:>=10","numbers: > >= < <="],["plan:*","has a value"]],zn=e=>e===0?"Off":`Every ${e} s`;function Gn(e){let t=l("details","dy-syntax");t.appendChild(l("summary",void 0,"Syntax"));let n=l("dl");for(let[a,o]of Xn)g(n,l("dt",void 0,a),l("dd",void 0,o));t.appendChild(n);let r=e.explorer?.fields??[];return t.appendChild(l("p","dy-note",`Fields: ${r.map(a=>`${a.key} (${a.type})`).join(", ")}. Personal fields cannot be searched.`)),t}function Wt(e,t,n,r){let a=l("label","dy-xctl"),o=l("select","dy-select");for(let[i,s]of t){let d=l("option",void 0,s,{value:i});d.selected=i===n,o.appendChild(d)}return o.addEventListener("change",()=>r(o.value)),g(a,l("span","dy-eyebrow",e),o),a}function Yt(e,t,n){let r=n.initial,a=0,o=!1,i,s=new Set,d=new Set,y=l("div","dy-explorer"),h=l("form","dy-query",null,{role:"search",novalidate:""}),m=l("input","dy-q",null,{id:"dy-q",type:"search",name:"q",spellcheck:"false",autocomplete:"off","aria-describedby":"dy-q-err"});m.value=r.q;let u=l("div","dy-q-err",null,{id:"dy-q-err",role:"alert"});u.hidden=!0;let f=l("div","dy-xmeta"),p=l("p","dy-xmatched",null,{role:"status"}),c=l("p","vh",null,{role:"status","aria-live":"polite"}),v=l("div","dy-xgrid"),b=(x,H=!0)=>{r=x,H&&n.onChange(r)};function k(x){if(_(u),u.hidden=x===null,m.setAttribute("aria-invalid",String(x!==null)),!x)return;let H=x.query;if(u.appendChild(l("p",void 0,H?.message??"Invalid query.")),H){let R=r.q,L=Math.min(Math.max(0,H.position),R.length),X=l("p","dy-q-echo");g(X,document.createTextNode(R.slice(0,L)),l("mark","dy-q-at",R.slice(L,L+1)||" "),document.createTextNode(R.slice(L+1))),u.appendChild(X)}}function E(x){clearTimeout(i),r.tail!==0&&b({...r,tail:0}),o=!1,$(x)}function M(){clearTimeout(i),r.tail!==0&&(i=setTimeout(()=>{if(r.tail!==0){if(document.hidden||a>0)return M();o=!0,t.refresh(),M()}},r.tail*1e3))}let P={state:()=>r,window:x=>qt(x.range,x.meta.now,r.zoom),onQueryError:x=>{k(x),o&&E("Live updates stopped: the search is not valid.")},onMatched:x=>{p.textContent=`${e().fmt.num(x)} ${x===1?"match":"matches"}`},zoom:x=>{b({...r,zoom:x},!1),w(!1)},addClause:(x,H,R)=>{let L=Ut(r.q,x,H,R);L!==null&&(m.value=L,C())},isTailTick:()=>o,begin:()=>{a+=1;let x=!1;return()=>{x||(a-=1),x=!0}},onData:x=>t.touchUpdated(x),onFailure:()=>{o&&E("Live updates stopped after an error.")}},I=Ie(e,t,{id:"xresults",no:3,title:"Matching visits",className:"p-xresults",ariaLabel:"Matching visits, newest first",empty:we,availability:x=>x.meta.widgets.explorer?V(x.meta.widgets.explorer):null,fetch:async(x,H,R,L)=>{let X="cursor"in R,U=Be(await me(x,P,"search",H,L,()=>R));return U.kind==="data"&&!X&&(typeof U.data.matched=="number"&&P.onMatched(U.data.matched),O(U.data.visits)),U},decorate:(x,H)=>{let R=x.visits;H.forEach((L,X)=>{let U=R[X]?.id;U&&d.has(U)&&L.classList.add("is-new")})}});function O(x){let H=x.map(R=>R.id).filter(R=>R!==null);d=o?new Set(H.filter(R=>!s.has(R))):new Set,s=new Set(H),d.size>0&&(c.textContent=`${d.size} new matching ${d.size===1?"visit":"visits"}`)}let T=[jt(e,t,P),Bt(e,t,P),I];function w(x){o=x,T.forEach(H=>H.load())}function C(){k(null),b({...r,q:m.value.trim()}),w(!1)}function $(x=null){_(f);let H=e().meta,R=(H.explorer?.fields??[]).filter(L=>L.facet);if(g(f,p),r.zoom){let L=l("p","dy-xzoom"),X=l("button","dy-btn dy-btn--text","Reset zoom",{type:"button"});X.addEventListener("click",()=>{b({...r,zoom:null},!1),$(),w(!1)});let U=e().fmt,xe=r.zoom,Gt=xe.interval==="hour"?U.weekday(xe.fromMs):`${U.day(xe.fromMs)} to ${U.day(xe.toMs-1)}`;g(L,document.createTextNode(`Zoomed to ${Gt} (UTC) `),X),f.appendChild(L)}f.appendChild(Wt("Split by",[["","None"],...R.map(L=>[L.key,L.key])],r.split??"",L=>{b({...r,split:L||null}),T[0]?.load()})),f.appendChild(Wt("Live",[0,...It(H)].map(L=>[String(L),zn(L)]),String(r.tail),L=>{b({...r,tail:Number(L)}),M()})),x&&f.appendChild(l("span","dy-note",x))}h.addEventListener("submit",x=>{x.preventDefault(),C()});let N=l("div","dy-query-row");return g(N,m,l("button","dy-btn","Search",{type:"submit"})),g(h,l("label","dy-eyebrow","Search visits",{for:"dy-q"}),N,u,Gn(e().meta)),g(y,h,f,c,v),g(v,...T.map(x=>x.element)),$(),M(),{element:y,panels:T,state:()=>r,resetZoom:()=>{r.zoom&&(b({...r,zoom:null},!1),$())},refreshTools:()=>{$(),T.forEach(x=>x.refreshTools())},stop:()=>{clearTimeout(i)}}}function ye(e){let t=document.getElementById(e);if(!t)throw new Error(`dynalitics: missing #${e}`);return t}function Zn(e){let t=document.documentElement,n=()=>t.getAttribute("data-theme")==="dark";e.setAttribute("aria-pressed",String(n())),e.addEventListener("click",()=>{let r=n()?"light":"dark";t.setAttribute("data-theme",r),e.setAttribute("aria-pressed",String(r==="dark"));try{localStorage.setItem("dynalitics-theme",r)}catch{}})}function Xt(e,t){let n=[...e.querySelectorAll("input")];n.forEach(a=>{a.checked=a.value===t});let r=Math.max(0,n.findIndex(a=>a.value===t));e.querySelector(".dy-pill")?.style.setProperty("--i",String(r))}var Qn=e=>e.widgets.explorer?.available===!0&&e.explorer!==void 0,Jn=(e,t)=>t==="explorer"?Ot(e,se(e)):se(e);function er(e,t,n){_(e),e.hidden=!1;for(let[r,a]of[["overview","Overview"],["explorer","Explorer"]]){let o=l("button","dy-btn dy-view-btn",a,{type:"button"});r===t&&o.setAttribute("aria-current","page"),o.addEventListener("click",()=>n(r)),e.appendChild(o)}}function Kt(e,t,n,r){_(e),g(e,l("legend","vh","Date range"),l("span","dy-pill",null,{"aria-hidden":"true"})),e.style.setProperty("--n",String(t.length));for(let a of t){let o=l("label"),i=l("input",void 0,null,{type:"radio",name:"dy-range",value:a.key});i.addEventListener("change",()=>r(a.key)),g(o,i,l("span",void 0,a.key)),e.appendChild(o)}Xt(e,n)}function zt(e){let t=We(e.base),n=e.fetchFn??((w,C)=>fetch(w,C)),r=Ke(t,n),a=ze(e.locale),o=ye("dy-grid"),i=ye("dy-range"),s=ye("dy-views"),d=ye("dy-updated"),y="overview",h=null,m=e.now??(()=>Date.now()),u=null,f=[],p=!1,c=0,v=null,b={now:m,setRange:w=>I(w),reloadPage:e.reloadPage??(()=>location.reload()),refresh:()=>{T()},claimErrorRole:()=>{let w=p?"status":"alert";return p=!0,w},touchUpdated:w=>{let C=D(w);Number.isNaN(C)||(d.textContent=a.time(C))}},k=()=>{if(!u)throw new Error("dynalitics: panel used before /api/meta");return u};function E(w){_(o),o.appendChild(Le(w,{role:b.claimErrorRole(),cooldownLeftMs:0,onRetry:()=>{T()},onReload:b.reloadPage,onTrySevenDays:null,ssoHint:!0})),f=[]}function M(){if(!u)return;let w=u.range.key,C=y==="explorer"&&h?Ft(w,h.state()):`#range=${w}`;try{history.replaceState(null,"",C)}catch{}}function P(w){if(h?.stop(),h=null,_(o),o.classList.toggle("dy-grid--explorer",y==="explorer"),y==="explorer"&&u){h=Yt(k,b,{initial:w?Dt(w,u.meta):Nt,onChange:M}),f=h.panels,o.appendChild(h.element);return}f=[xt(k,b),Et(k,b),Ct(k,b),ht(k,b),Ie(k,b)],g(o,...f.map(C=>C.element))}function I(w){let C=Ue(w);!C||!u||(u={...u,range:C},Xt(i,w),h?.resetZoom(),M(),T())}function O(w,C=""){if(!u)return;y=w;let $=Jn(u.meta,y);if(!$.some(N=>N.key===u?.range.key)){let N=$[$.length-1];N&&(u={...u,range:N})}Kt(i,$,u.range.key,N=>I(N)),er(s,y,N=>{N!==y&&O(N)}),P(C),M(),f.forEach(N=>N.refreshTools()),h?.refreshTools(),f.forEach(N=>N.load())}async function T(){let w=++c;v?.abort(),v=new AbortController;let C=await r.get("meta",{},v.signal);if(w!==c||C.kind==="aborted")return;let $=C.kind==="data"?Je(C.data):null;if(!$){if(u){f.forEach(R=>R.load());return}E(C.kind==="error"?C.failure:{kind:"unexpected",status:0});return}let N=!u,x=N?location.hash:"",H=Ue(mt($,N?x:`#range=${u?.range.key}`));if(H){if(u={api:r,fmt:a,meta:$,range:u&&se($).some(R=>R.key===u?.range.key)?u.range:H},N){if(Qn($))return O(Ht(x)?"explorer":"overview",x);Kt(i,se($),u.range.key,R=>I(R)),P("")}f.forEach(R=>R.refreshTools()),f.forEach(R=>R.load())}}return Zn(ye("dy-theme")),ye("dy-refresh").addEventListener("click",()=>{T()}),T(),{refresh:()=>{T()}}}var tr=document.querySelector('meta[name="dynalitics-base"]')?.getAttribute("content")??"";zt({base:tr});})();</script>
38
+ </body>
39
+ </html>
package/dist/html.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const dashboardHtml: string;