hearald 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,938 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Analytics</title>
6
+
7
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
8
+ <meta
9
+ name="viewport"
10
+ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover, user-scalable=no"
11
+ />
12
+
13
+ <style>:root {
14
+ --global-max-width: 480px;
15
+
16
+ --border-radius: 4px;
17
+
18
+ /* Fonts */
19
+ --font-sans-serif: Raleway, sans-serif;
20
+
21
+ /* Colors: start point https://color.adobe.com/Architecture-color-theme-20477657 */
22
+ --color-black: hsla(214, 94%, 7%, 1);
23
+ --color-dark: hsla(209, 37%, 18%, 1);
24
+ --color-mid: hsla(196, 34%, 48%, 1);
25
+ --color-light: hsla(191, 20%, 68%, 1);
26
+ --color-accent: hsla(38, 85%, 64%, 1);
27
+ --color-white: hsla(38, 38%, 90%, 1);
28
+
29
+ --color-text: var(--color-white);
30
+ --color-label: var(--color-light);
31
+
32
+ --color-link: var(--color-accent);
33
+ --color-link-hover: var(--color-light);
34
+ --color-title: var(--color-accent);
35
+
36
+ --color-bg: var(--color-black);
37
+ --color-menu: var(--color-dark);
38
+
39
+ --color-input-bg: var(--color-white);
40
+ --color-input-text: var(--color-dark);
41
+
42
+ --color-button-bg: var(--color-mid);
43
+ --color-button-bg-hover: var(--color-light);
44
+ --color-button-text: var(--color-black);
45
+
46
+ --color-logo-sign: var(--color-success);
47
+
48
+ --color-success: #127d44;
49
+ --color-error: #ad2e41;
50
+
51
+ --animation-plop: plop 300ms ease-out forwards;
52
+
53
+ --hero-box-shadow: 0 0 0.75em rgba(0, 0, 0, 0.75);
54
+
55
+ --carousel-hover-transition: 80ms ease-out transform;
56
+ }
57
+
58
+ @media (prefers-color-scheme: light) {
59
+ :root {
60
+ --color-text: var(--color-dark);
61
+ --color-label: hsla(196, 34%, 38%, 1);
62
+
63
+ --color-link: var(--color-mid);
64
+ --color-link-hover: var(--color-light);
65
+ --color-title: var(--color-dark);
66
+
67
+ --color-bg: #fff;
68
+ --color-menu: #fff;
69
+
70
+ --color-input-bg: var(--color-white);
71
+ --color-input-text: var(--color-dark);
72
+
73
+ --color-button-bg: var(--color-mid);
74
+ --color-button-bg-hover: var(--color-light);
75
+ --color-button-text: var(--color-black);
76
+
77
+ --color-logo-sign: var(--color-success);
78
+ }
79
+ }
80
+
81
+ html {
82
+ height: 100%;
83
+ }
84
+
85
+ * {
86
+ box-sizing: border-box;
87
+ }
88
+
89
+ body {
90
+ font-family: var(--font-sans-serif);
91
+ line-height: 1.6;
92
+ font-size: 14px;
93
+ background: var(--color-bg);
94
+ margin: 0;
95
+ height: 100%;
96
+ -webkit-text-size-adjust: none;
97
+ text-size-adjust: none;
98
+ color: var(--color-text);
99
+ }
100
+
101
+ /* need this to override the user style sheet in chrome, which has higher
102
+ specificity than inheriting the font style tag from the body */
103
+ input {
104
+ font-family: var(--font-sans-serif);
105
+ }
106
+
107
+ body.lock-zoom {
108
+ touch-action: none;
109
+ overscroll-behavior-y: none;
110
+ }
111
+
112
+ h1 {
113
+ font-family: var(--font-sans-serif);
114
+ font-size: 2.5em;
115
+ margin: 0;
116
+ }
117
+
118
+ h2 {
119
+ font-family: var(--font-sans-serif);
120
+ font-size: 1.5em;
121
+ margin: 0;
122
+ }
123
+
124
+ td,
125
+ th {
126
+ padding: 0 0.5em;
127
+ }
128
+
129
+ th {
130
+ text-align: left;
131
+ font-size: 0.8em;
132
+ }
133
+
134
+ @media screen and (max-width: 640px) {
135
+ body {
136
+ font-size: 12px;
137
+ font-feature-settings: 'lnum';
138
+ }
139
+ }
140
+
141
+ .hidden {
142
+ display: none !important;
143
+ }
144
+
145
+ .header__logo,
146
+ .header__button {
147
+ width: 2rem;
148
+ height: 2rem;
149
+ font-size: 2rem;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ padding: 0;
154
+ text-decoration: none;
155
+ }
156
+
157
+ .header__button:hover {
158
+ text-decoration: none;
159
+ }
160
+
161
+ .header__button--has-menu {
162
+ cursor: pointer;
163
+ }
164
+
165
+ /* ---- */
166
+
167
+ .analytics__header {
168
+ grid-area: header;
169
+ padding: 2em;
170
+ background-color: var(--color-dark);
171
+ display: flex;
172
+ gap: 2em;
173
+ align-items: center;
174
+ grid-template-columns: auto auto auto auto 1fr;
175
+ }
176
+
177
+ .analytics {
178
+ --analytics-grid-columns: 4;
179
+
180
+ display: grid;
181
+ grid-template-columns: repeat(var(--analytics-grid-columns), 1fr);
182
+ grid-gap: 0.5em;
183
+ padding: 1em;
184
+ }
185
+
186
+ .analytics__title {
187
+ grid-column: span var(--analytics-grid-columns);
188
+ }
189
+
190
+ .analytics__tile {
191
+ display: flex;
192
+ /* flex: 1fr 5fr; */
193
+ flex: 1 1 auto;
194
+ flex-direction: column;
195
+ border: 1px solid var(--color-accent);
196
+ }
197
+
198
+ .analytics__tile--end-row {
199
+ border: 0;
200
+ grid-column: span var(--analytics-grid-columns);
201
+ }
202
+
203
+ .analytics__tile__header {
204
+ padding: 0.2em 0.5em;
205
+ border-bottom: 1px solid var(--color-accent);
206
+ }
207
+ .analytics__tile__value {
208
+ padding: 0.2em 0.5em;
209
+ margin: 0 auto;
210
+ font-size: 2.5em;
211
+ font-weight: 600;
212
+ }
213
+
214
+ .analytics__menu {
215
+ grid-column: 1 /1;
216
+ display: flex;
217
+ gap: 1em;
218
+ align-items: center;
219
+ }
220
+
221
+ .analytics__menu > label {
222
+ display: flex;
223
+ gap: 0.5em;
224
+ align-items: center;
225
+ }
226
+
227
+ .analytics__menu > button {
228
+ font-size: 9px;
229
+ line-height: 9px;
230
+ height: 18px;
231
+ }
232
+
233
+ .bar-chart {
234
+ grid-column: 1/-1;
235
+ height: 200px;
236
+ display: grid;
237
+ grid-auto-flow: column;
238
+ grid-auto-columns: 1fr;
239
+ grid-template-columns: 1fr;
240
+ gap: 2px;
241
+ position: relative;
242
+ align-items: end;
243
+ margin-top: 1em;
244
+ padding: 0 1.5em 0 0;
245
+ border-bottom: 1px solid var(--color-light);
246
+ }
247
+ .bar-chart__bar {
248
+ background: var(--color-mid);
249
+ height: calc(var(--bar-height) * 100%);
250
+ position: relative;
251
+ }
252
+ .bar-chart__bar__label {
253
+ display: none;
254
+ position: absolute;
255
+ bottom: calc(100% + 0.75em);
256
+ left: 50%;
257
+ transform: translateX(-50%);
258
+ z-index: 2;
259
+ background: var(--color-label);
260
+ padding: 0.25em 0.5em;
261
+ border-radius: 0.25em;
262
+ text-align: center;
263
+ white-space: nowrap;
264
+ word-break: keep-all;
265
+ }
266
+ .bar-chart__bar__label:after {
267
+ content: '';
268
+ width: 12px;
269
+ height: 12px;
270
+ background: var(--color-label);
271
+ position: absolute;
272
+ margin: auto;
273
+ left: 0;
274
+ right: 0;
275
+ bottom: 0;
276
+ transform: translateY(50%) rotate(45deg);
277
+ z-index: 1;
278
+ }
279
+ .bar-chart__bar:hover {
280
+ background: var(--color-light);
281
+ }
282
+ .bar-chart__bar:hover > .bar-chart__bar__label {
283
+ display: block;
284
+ }
285
+ .bar-chart__empty {
286
+ place-self: center;
287
+ }
288
+ .bar-chart__scale {
289
+ align-self: start;
290
+ border-top: 1px solid var(--color-light);
291
+ border-right: 1px solid var(--color-light);
292
+ height: 100%;
293
+ padding-right: 0.2em;
294
+ text-align: right;
295
+ }
296
+
297
+ .analytics__tile__histogram {
298
+ list-style-type: none;
299
+ padding: 0;
300
+ margin: 0;
301
+ display: grid;
302
+ grid-template-columns: auto 1fr;
303
+ gap: 1em;
304
+ grid-column: 1/-1;
305
+ min-height: 10em;
306
+ max-height: 20em;
307
+ overflow: auto;
308
+ align-items: start;
309
+ grid-auto-rows: max-content;
310
+ padding: 0.5em 0.5em;
311
+ }
312
+ .analytics__tile__histogram__value {
313
+ text-align: right;
314
+ }
315
+ .analytics__tile__histogram__row {
316
+ display: grid;
317
+ grid-template-columns: subgrid;
318
+ grid-column: 1/-1;
319
+ align-self: start;
320
+ }
321
+ .analytics__tile__histogram__label {
322
+ position: relative;
323
+ background-image: linear-gradient(
324
+ to right,
325
+ var(--color-mid) calc(var(--bar-width) * 100%),
326
+ transparent calc(var(--bar-width) * 100%)
327
+ );
328
+ background-size: 100%;
329
+ padding: 0 0.5em;
330
+ min-width: 0;
331
+ min-height: 0;
332
+ }
333
+
334
+ .analytics__tile--span-2 {
335
+ grid-column: span 2;
336
+ }
337
+
338
+ @media screen and (max-width: 640px) {
339
+ .analytics {
340
+ --analytics-grid-columns: 2;
341
+ }
342
+
343
+ .analytics__tile--end-row {
344
+ display: none !important;
345
+ }
346
+
347
+ .analytics__tile--span-2 {
348
+ grid-column: span 1;
349
+ }
350
+ }
351
+
352
+ @media screen and (max-width: 380px) {
353
+ .analytics {
354
+ --analytics-grid-columns: 1;
355
+ }
356
+
357
+ .analytics__header__input {
358
+ width: 35%;
359
+ }
360
+
361
+ .analytics__menu > label {
362
+ display: grid;
363
+ }
364
+
365
+ .bar-chart {
366
+ grid-auto-flow: row;
367
+ height: auto;
368
+ border-bottom: 0;
369
+ border-left: 1px solid var(--color-light);
370
+ padding: 0;
371
+ }
372
+ .bar-chart__scale {
373
+ height: auto;
374
+ width: 100%;
375
+ border-top: 0;
376
+ border-bottom: 1px solid var(--color-light);
377
+ border-right: 1px solid var(--color-light);
378
+ }
379
+ .bar-chart__bar {
380
+ height: 0.5em;
381
+ width: calc(var(--bar-height) * 100%);
382
+ }
383
+ .bar-chart__bar__label {
384
+ left: 0.5em;
385
+ transform: translateX(0);
386
+ }
387
+ .bar-chart__bar__label:after {
388
+ left: 3px;
389
+ right: auto;
390
+ bottom: 0;
391
+ transform: translateY(50%) rotate(45deg);
392
+ }
393
+ }
394
+ </style>
395
+ </head>
396
+
397
+ <body>
398
+ <div id="analytics-root"></div>
399
+ <script>(function () {
400
+ 'use strict';
401
+
402
+ var n$1,l,u,i,r,o,e$1,f,c,s,a,p={},v=[],y=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,w=Array.isArray;function d(n,l){for(var u in l)n[u]=l[u];return n}function g(n){n&&n.parentNode&&n.parentNode.removeChild(n);}function _(l,u,t){var i,r,o,e={};for(o in u)"key"==o?i=u[o]:"ref"==o?r=u[o]:e[o]=u[o];if(arguments.length>2&&(e.children=arguments.length>3?n$1.call(arguments,2):t),"function"==typeof l&&null!=l.defaultProps)for(o in l.defaultProps)void 0===e[o]&&(e[o]=l.defaultProps[o]);return m$1(l,e,i,r,null)}function m$1(n,t,i,r,o){var e={type:n,props:t,key:i,ref:r,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:null==o?++u:o,__i:-1,__u:0};return null==o&&null!=l.vnode&&l.vnode(e),e}function k(n){return n.children}function x(n,l){this.props=n,this.context=l;}function S(n,l){if(null==l)return n.__?S(n.__,n.__i+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return "function"==typeof n.type?S(n):null}function C(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return C(n)}}function M(n){(!n.__d&&(n.__d=!0)&&i.push(n)&&!$.__r++||r!=l.debounceRendering)&&((r=l.debounceRendering)||o)($);}function $(){for(var n,u,t,r,o,f,c,s=1;i.length;)i.length>s&&i.sort(e$1),n=i.shift(),s=i.length,n.__d&&(t=void 0,r=void 0,o=(r=(u=n).__v).__e,f=[],c=[],u.__P&&((t=d({},r)).__v=r.__v+1,l.vnode&&l.vnode(t),O(u.__P,t,r,u.__n,u.__P.namespaceURI,32&r.__u?[o]:null,f,null==o?S(r):o,!!(32&r.__u),c),t.__v=r.__v,t.__.__k[t.__i]=t,N(f,t,c),r.__e=r.__=null,t.__e!=o&&C(t)));$.__r=0;}function I(n,l,u,t,i,r,o,e,f,c,s){var a,h,y,w,d,g,_,m=t&&t.__k||v,b=l.length;for(f=P(u,l,m,f,b),a=0;a<b;a++)null!=(y=u.__k[a])&&(h=-1==y.__i?p:m[y.__i]||p,y.__i=a,g=O(n,y,h,i,r,o,e,f,c,s),w=y.__e,y.ref&&h.ref!=y.ref&&(h.ref&&B(h.ref,null,y),s.push(y.ref,y.__c||w,y)),null==d&&null!=w&&(d=w),(_=!!(4&y.__u))||h.__k===y.__k?f=A(y,f,n,_):"function"==typeof y.type&&void 0!==g?f=g:w&&(f=w.nextSibling),y.__u&=-7);return u.__e=d,f}function P(n,l,u,t,i){var r,o,e,f,c,s=u.length,a=s,h=0;for(n.__k=new Array(i),r=0;r<i;r++)null!=(o=l[r])&&"boolean"!=typeof o&&"function"!=typeof o?(f=r+h,(o=n.__k[r]="string"==typeof o||"number"==typeof o||"bigint"==typeof o||o.constructor==String?m$1(null,o,null,null,null):w(o)?m$1(k,{children:o},null,null,null):null==o.constructor&&o.__b>0?m$1(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):o).__=n,o.__b=n.__b+1,e=null,-1!=(c=o.__i=L(o,u,f,a))&&(a--,(e=u[c])&&(e.__u|=2)),null==e||null==e.__v?(-1==c&&(i>s?h--:i<s&&h++),"function"!=typeof o.type&&(o.__u|=4)):c!=f&&(c==f-1?h--:c==f+1?h++:(c>f?h--:h++,o.__u|=4))):n.__k[r]=null;if(a)for(r=0;r<s;r++)null!=(e=u[r])&&0==(2&e.__u)&&(e.__e==t&&(t=S(e)),D(e,e));return t}function A(n,l,u,t){var i,r;if("function"==typeof n.type){for(i=n.__k,r=0;i&&r<i.length;r++)i[r]&&(i[r].__=n,l=A(i[r],l,u,t));return l}n.__e!=l&&(t&&(l&&n.type&&!l.parentNode&&(l=S(n)),u.insertBefore(n.__e,l||null)),l=n.__e);do{l=l&&l.nextSibling;}while(null!=l&&8==l.nodeType);return l}function L(n,l,u,t){var i,r,o,e=n.key,f=n.type,c=l[u],s=null!=c&&0==(2&c.__u);if(null===c&&null==n.key||s&&e==c.key&&f==c.type)return u;if(t>(s?1:0))for(i=u-1,r=u+1;i>=0||r<l.length;)if(null!=(c=l[o=i>=0?i--:r++])&&0==(2&c.__u)&&e==c.key&&f==c.type)return o;return -1}function T(n,l,u){"-"==l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||y.test(l)?u:u+"px";}function j(n,l,u,t,i){var r,o;n:if("style"==l)if("string"==typeof u)n.style.cssText=u;else {if("string"==typeof t&&(n.style.cssText=t=""),t)for(l in t)u&&l in u||T(n.style,l,"");if(u)for(l in u)t&&u[l]==t[l]||T(n.style,l,u[l]);}else if("o"==l[0]&&"n"==l[1])r=l!=(l=l.replace(f,"$1")),o=l.toLowerCase(),l=o in n||"onFocusOut"==l||"onFocusIn"==l?o.slice(2):l.slice(2),n.l||(n.l={}),n.l[l+r]=u,u?t?u.u=t.u:(u.u=c,n.addEventListener(l,r?a:s,r)):n.removeEventListener(l,r?a:s,r);else {if("http://www.w3.org/2000/svg"==i)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!=l&&"height"!=l&&"href"!=l&&"list"!=l&&"form"!=l&&"tabIndex"!=l&&"download"!=l&&"rowSpan"!=l&&"colSpan"!=l&&"role"!=l&&"popover"!=l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||!1===u&&"-"!=l[4]?n.removeAttribute(l):n.setAttribute(l,"popover"==l&&1==u?"":u));}}function F(n){return function(u){if(this.l){var t=this.l[u.type+n];if(null==u.t)u.t=c++;else if(u.t<t.u)return;return t(l.event?l.event(u):u)}}}function O(n,u,t,i,r,o,e,f,c,s){var a,h,p,v,y,_,m,b,S,C,M,$,P,A,H,L,T,j=u.type;if(null!=u.constructor)return null;128&t.__u&&(c=!!(32&t.__u),o=[f=u.__e=t.__e]),(a=l.__b)&&a(u);n:if("function"==typeof j)try{if(b=u.props,S="prototype"in j&&j.prototype.render,C=(a=j.contextType)&&i[a.__c],M=a?C?C.props.value:a.__:i,t.__c?m=(h=u.__c=t.__c).__=h.__E:(S?u.__c=h=new j(b,M):(u.__c=h=new x(b,M),h.constructor=j,h.render=E),C&&C.sub(h),h.props=b,h.state||(h.state={}),h.context=M,h.__n=i,p=h.__d=!0,h.__h=[],h._sb=[]),S&&null==h.__s&&(h.__s=h.state),S&&null!=j.getDerivedStateFromProps&&(h.__s==h.state&&(h.__s=d({},h.__s)),d(h.__s,j.getDerivedStateFromProps(b,h.__s))),v=h.props,y=h.state,h.__v=u,p)S&&null==j.getDerivedStateFromProps&&null!=h.componentWillMount&&h.componentWillMount(),S&&null!=h.componentDidMount&&h.__h.push(h.componentDidMount);else {if(S&&null==j.getDerivedStateFromProps&&b!==v&&null!=h.componentWillReceiveProps&&h.componentWillReceiveProps(b,M),!h.__e&&null!=h.shouldComponentUpdate&&!1===h.shouldComponentUpdate(b,h.__s,M)||u.__v==t.__v){for(u.__v!=t.__v&&(h.props=b,h.state=h.__s,h.__d=!1),u.__e=t.__e,u.__k=t.__k,u.__k.some(function(n){n&&(n.__=u);}),$=0;$<h._sb.length;$++)h.__h.push(h._sb[$]);h._sb=[],h.__h.length&&e.push(h);break n}null!=h.componentWillUpdate&&h.componentWillUpdate(b,h.__s,M),S&&null!=h.componentDidUpdate&&h.__h.push(function(){h.componentDidUpdate(v,y,_);});}if(h.context=M,h.props=b,h.__P=n,h.__e=!1,P=l.__r,A=0,S){for(h.state=h.__s,h.__d=!1,P&&P(u),a=h.render(h.props,h.state,h.context),H=0;H<h._sb.length;H++)h.__h.push(h._sb[H]);h._sb=[];}else do{h.__d=!1,P&&P(u),a=h.render(h.props,h.state,h.context),h.state=h.__s;}while(h.__d&&++A<25);h.state=h.__s,null!=h.getChildContext&&(i=d(d({},i),h.getChildContext())),S&&!p&&null!=h.getSnapshotBeforeUpdate&&(_=h.getSnapshotBeforeUpdate(v,y)),L=a,null!=a&&a.type===k&&null==a.key&&(L=V(a.props.children)),f=I(n,w(L)?L:[L],u,t,i,r,o,e,f,c,s),h.base=u.__e,u.__u&=-161,h.__h.length&&e.push(h),m&&(h.__E=h.__=null);}catch(n){if(u.__v=null,c||null!=o)if(n.then){for(u.__u|=c?160:128;f&&8==f.nodeType&&f.nextSibling;)f=f.nextSibling;o[o.indexOf(f)]=null,u.__e=f;}else {for(T=o.length;T--;)g(o[T]);z(u);}else u.__e=t.__e,u.__k=t.__k,n.then||z(u);l.__e(n,u,t);}else null==o&&u.__v==t.__v?(u.__k=t.__k,u.__e=t.__e):f=u.__e=q(t.__e,u,t,i,r,o,e,c,s);return (a=l.diffed)&&a(u),128&u.__u?void 0:f}function z(n){n&&n.__c&&(n.__c.__e=!0),n&&n.__k&&n.__k.forEach(z);}function N(n,u,t){for(var i=0;i<t.length;i++)B(t[i],t[++i],t[++i]);l.__c&&l.__c(u,n),n.some(function(u){try{n=u.__h,u.__h=[],n.some(function(n){n.call(u);});}catch(n){l.__e(n,u.__v);}});}function V(n){return "object"!=typeof n||null==n||n.__b&&n.__b>0?n:w(n)?n.map(V):d({},n)}function q(u,t,i,r,o,e,f,c,s){var a,h,v,y,d,_,m,b=i.props,k=t.props,x=t.type;if("svg"==x?o="http://www.w3.org/2000/svg":"math"==x?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),null!=e)for(a=0;a<e.length;a++)if((d=e[a])&&"setAttribute"in d==!!x&&(x?d.localName==x:3==d.nodeType)){u=d,e[a]=null;break}if(null==u){if(null==x)return document.createTextNode(k);u=document.createElementNS(o,x,k.is&&k),c&&(l.__m&&l.__m(t,e),c=!1),e=null;}if(null==x)b===k||c&&u.data==k||(u.data=k);else {if(e=e&&n$1.call(u.childNodes),b=i.props||p,!c&&null!=e)for(b={},a=0;a<u.attributes.length;a++)b[(d=u.attributes[a]).name]=d.value;for(a in b)if(d=b[a],"children"==a);else if("dangerouslySetInnerHTML"==a)v=d;else if(!(a in k)){if("value"==a&&"defaultValue"in k||"checked"==a&&"defaultChecked"in k)continue;j(u,a,null,d,o);}for(a in k)d=k[a],"children"==a?y=d:"dangerouslySetInnerHTML"==a?h=d:"value"==a?_=d:"checked"==a?m=d:c&&"function"!=typeof d||b[a]===d||j(u,a,d,b[a],o);if(h)c||v&&(h.__html==v.__html||h.__html==u.innerHTML)||(u.innerHTML=h.__html),t.__k=[];else if(v&&(u.innerHTML=""),I("template"==t.type?u.content:u,w(y)?y:[y],t,i,r,"foreignObject"==x?"http://www.w3.org/1999/xhtml":o,e,f,e?e[0]:i.__k&&S(i,0),c,s),null!=e)for(a=e.length;a--;)g(e[a]);c||(a="value","progress"==x&&null==_?u.removeAttribute("value"):null!=_&&(_!==u[a]||"progress"==x&&!_||"option"==x&&_!=b[a])&&j(u,a,_,b[a],o),a="checked",null!=m&&m!=u[a]&&j(u,a,m,b[a],o));}return u}function B(n,u,t){try{if("function"==typeof n){var i="function"==typeof n.__u;i&&n.__u(),i&&null==u||(n.__u=n(u));}else n.current=u;}catch(n){l.__e(n,t);}}function D(n,u,t){var i,r;if(l.unmount&&l.unmount(n),(i=n.ref)&&(i.current&&i.current!=n.__e||B(i,null,u)),null!=(i=n.__c)){if(i.componentWillUnmount)try{i.componentWillUnmount();}catch(n){l.__e(n,u);}i.base=i.__P=null;}if(i=n.__k)for(r=0;r<i.length;r++)i[r]&&D(i[r],u,t||"function"!=typeof n.type);t||g(n.__e),n.__c=n.__=n.__e=void 0;}function E(n,l,u){return this.constructor(n,u)}function G(u,t,i){var r,o,e,f;t==document&&(t=document.documentElement),l.__&&l.__(u,t),o=(r="function"==typeof i)?null:i&&i.__k||t.__k,e=[],f=[],O(t,u=(!r&&i||t).__k=_(k,null,[u]),o||p,p,t.namespaceURI,!r&&i?[i]:o?null:t.firstChild?n$1.call(t.childNodes):null,e,!r&&i?i:o?o.__e:t.firstChild,r,f),N(e,u,f);}n$1=v.slice,l={__e:function(n,l,u,t){for(var i,r,o;l=l.__;)if((i=l.__c)&&!i.__)try{if((r=i.constructor)&&null!=r.getDerivedStateFromError&&(i.setState(r.getDerivedStateFromError(n)),o=i.__d),null!=i.componentDidCatch&&(i.componentDidCatch(n,t||{}),o=i.__d),o)return i.__E=i}catch(l){n=l;}throw n}},u=0,x.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!=this.state?this.__s:this.__s=d({},this.state),"function"==typeof n&&(n=n(d({},u),this.props)),n&&d(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),M(this));},x.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),M(this));},x.prototype.render=k,i=[],o="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,e$1=function(n,l){return n.__v.__b-l.__v.__b},$.__r=0,f=/(PointerCapture)$|Capture$/i,c=0,s=F(!1),a=F(!0);
403
+
404
+ var n=function(t,s,r,e){var u;s[0]=0;for(var h=1;h<s.length;h++){var p=s[h++],a=s[h]?(s[0]|=p?1:2,r[s[h++]]):s[++h];3===p?e[0]=a:4===p?e[1]=Object.assign(e[1]||{},a):5===p?(e[1]=e[1]||{})[s[++h]]=a:6===p?e[1][s[++h]]+=a+"":p?(u=t.apply(a,n(t,a,r,["",null])),e.push(u),a[0]?s[0]|=2:(s[h-2]=0,s[h]=u)):e.push(a);}return e},t=new Map;function e(s){var r=t.get(this);return r||(r=new Map,t.set(this,r)),(r=n(this,r.get(s)||(r.set(s,r=function(n){for(var t,s,r=1,e="",u="",h=[0],p=function(n){1===r&&(n||(e=e.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?h.push(0,n,e):3===r&&(n||e)?(h.push(3,n,e),r=2):2===r&&"..."===e&&n?h.push(4,n,0):2===r&&e&&!n?h.push(5,0,!0,e):r>=5&&((e||!n&&5===r)&&(h.push(r,0,e,s),r=6),n&&(h.push(r,n,0,s),r=6)),e="";},a=0;a<n.length;a++){a&&(1===r&&p(),p(a));for(var l=0;l<n[a].length;l++)t=n[a][l],1===r?"<"===t?(p(),h=[h],r=3):e+=t:4===r?"--"===e&&">"===t?(r=1,e=""):e=t+e[0]:u?t===u?u="":e+=t:'"'===t||"'"===t?u=t:">"===t?(p(),r=1):r&&("="===t?(r=5,s=e,e=""):"/"===t&&(r<5||">"===n[a][l+1])?(p(),3===r&&(h=h[0]),r=h,(h=h[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(p(),r=2):e+=t),3===r&&"!--"===e&&(r=4,h=h[0]);}return p(),h}(s)),r),arguments,[])).length>1?r:r[0]}
405
+
406
+ var m=e.bind(_);
407
+
408
+ function iso(time) {
409
+ return [
410
+ time.getUTCFullYear(),
411
+ (time.getUTCMonth() + 1).toString().padStart(2, "0"),
412
+ time.getUTCDate().toString().padStart(2, "0"),
413
+ ].join("-");
414
+ }
415
+
416
+ /* START methods for fetching data from the server */
417
+
418
+ const dataCache = {};
419
+
420
+ // Fetches data from the server with caching
421
+ async function getData({ n, d, q, endpointBuilder, fetchStats }) {
422
+ const key = [n, d, q].join("_");
423
+ if (dataCache[key]) {
424
+ return dataCache[key];
425
+ }
426
+
427
+ const endpoint = endpointBuilder
428
+ ? endpointBuilder(n, d, q)
429
+ : `/data/${n}/${d}${q ? "?" + q : ""}`;
430
+
431
+ const data = await fetchStats(endpoint);
432
+ dataCache[key] = data;
433
+ return data;
434
+ }
435
+
436
+ // Fetches and builds chart data for a given field/context over a time window
437
+ async function fetchChartData({
438
+ numDays,
439
+ date,
440
+ chartContext,
441
+ chartField,
442
+ getDataFn,
443
+
444
+ onLoadProgress, // (i, n) => void
445
+ onLoadComplete, // (data, max) => void
446
+ }) {
447
+ const n = numDays;
448
+ const d = date;
449
+ const field = chartField;
450
+ const context = chartContext;
451
+
452
+ const [yyyy, mm, dd] = d.split("-");
453
+ const startDate = new Date(yyyy, mm - 1, dd);
454
+
455
+ let data = [];
456
+ let max = 1;
457
+
458
+ for (let i = 0; i < n; i++) {
459
+ let day = new Date(startDate);
460
+ day.setDate(startDate.getDate() - i);
461
+ onLoadProgress(i, n);
462
+
463
+ const json = await getDataFn({ n: 1, d: iso(day) });
464
+ const v = json.metrics[context][field] || 0;
465
+
466
+ data.unshift({ date: iso(day), value: v });
467
+ max = Math.max(max, v);
468
+ }
469
+
470
+ onLoadComplete(data, max);
471
+ }
472
+
473
+ // Fetches and sets the main stats data for a given date/range
474
+ async function fetchData({
475
+ numDays,
476
+ date,
477
+ getDataFn,
478
+ onLoadComplete, // (stats) => void
479
+ }) {
480
+ const n = numDays;
481
+ const d = date;
482
+ const stats = await getDataFn({ n, d });
483
+ onLoadComplete(stats);
484
+ }
485
+
486
+ /* END methods for fetching data from the server */
487
+
488
+ /* START helper methods */
489
+
490
+ const getJson = async (url) => {
491
+ const response = await fetch(url, {
492
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
493
+ });
494
+ return response.json();
495
+ };
496
+
497
+ const deepGet = (obj, path) => {
498
+ const parts = path.split(".");
499
+ let current = obj;
500
+ for (let i = 0; i < parts.length; i += 1) {
501
+ if (current == null) return undefined;
502
+ current = current[parts[i]];
503
+ }
504
+ return current;
505
+ };
506
+
507
+ function numFormatter(n) {
508
+ if (typeof Intl !== "undefined" && Intl.NumberFormat) {
509
+ return new Intl.NumberFormat().format(n);
510
+ }
511
+ return n;
512
+ }
513
+
514
+ /* END helper methods */
515
+
516
+ const prettyNumericFormatter = (val) => {
517
+ const digits = Math.log10(val);
518
+ let truncDigits = 0;
519
+ let decimalPlaces = 0;
520
+ let suffix = "";
521
+ if (digits > 8) {
522
+ truncDigits = 9;
523
+ suffix = "b";
524
+ decimalPlaces = 2;
525
+ } else if (digits > 5) {
526
+ truncDigits = 6;
527
+ suffix = "m";
528
+ decimalPlaces = 2;
529
+ } else if (digits > 3) {
530
+ truncDigits = 3;
531
+ suffix = "k";
532
+ decimalPlaces = 2;
533
+ }
534
+
535
+ const numeric = Math.floor(val / 10 ** truncDigits);
536
+ if (decimalPlaces) {
537
+ const fractional =
538
+ (val % 10 ** decimalPlaces) % 10 ** (truncDigits - decimalPlaces);
539
+ return `${numeric}.${fractional}${suffix}`;
540
+ } else {
541
+ return `${numeric}${suffix}`;
542
+ }
543
+ };
544
+
545
+ const percentFormatter = (val) =>
546
+ `${(Math.round(val * 100) / 100).toFixed(2)}%`;
547
+
548
+ const plainFormatter = (val) => val;
549
+
550
+ const getFormattedValue = (value, percent = false, plain = false) => {
551
+ let formattedValue;
552
+ let formatter = prettyNumericFormatter;
553
+ if (percent) {
554
+ if (plain) {
555
+ console.error("Multiple formatters specified, using percent formatter");
556
+ }
557
+
558
+ formatter = percentFormatter;
559
+ } else if (plain) {
560
+ formatter = plainFormatter;
561
+ }
562
+
563
+ if (isNaN(value)) {
564
+ formattedValue = "(no data)";
565
+ } else {
566
+ formattedValue = formatter(value);
567
+ }
568
+
569
+ return formattedValue;
570
+ };
571
+
572
+ class Tile extends x {
573
+ render({
574
+ title,
575
+ value,
576
+ type,
577
+ percent = false,
578
+ plain = false,
579
+ endRow = false,
580
+ span = null,
581
+ }) {
582
+ return m`
583
+ <div
584
+ class="analytics__tile ${span ? `analytics__tile--span-${span}` : ""}"
585
+ >
586
+ <div class="analytics__tile__header">${title}</div>
587
+ ${type === "histogram"
588
+ ? m`
589
+ <!-- div class="analytics__tile__value" -->
590
+ <ul class="analytics__tile__histogram">
591
+ ${value && value.length
592
+ ? value.map(
593
+ ([label, count]) => m`
594
+ <li class="analytics__tile__histogram__row">
595
+ <div class="analytics__tile__histogram__value">
596
+ ${count}
597
+ </div>
598
+ <div
599
+ class="analytics__tile__histogram__label"
600
+ style="--bar-width:${count / (value[0]?.[1] || 1)}"
601
+ >
602
+ ${label}
603
+ </div>
604
+ </li>
605
+ `
606
+ )
607
+ : m`
608
+ <li class="analytics__tile__histogram__row">No data</li>
609
+ `}
610
+ </ul>
611
+ <!-- /div -->
612
+ `
613
+ : m`
614
+ <div class="analytics__tile__value" data-value=${value}>
615
+ ${getFormattedValue(value, percent, plain)}
616
+ </div>
617
+ `}
618
+ </div>
619
+ ${endRow &&
620
+ m` <div class="analytics__tile analytics__tile--end-row" /> `}
621
+ `;
622
+ }
623
+ }
624
+
625
+ const DATE = new Date().toISOString().split("T")[0];
626
+
627
+ const Logo = () => m`
628
+ <svg
629
+ width="256"
630
+ height="256"
631
+ viewBox="0 0 256 256"
632
+ xmlns="http://www.w3.org/2000/svg"
633
+ role="img"
634
+ aria-labelledby="title desc"
635
+ >
636
+ <title id="title">Hearald</title>
637
+ <desc id="desc">
638
+ Circular seal with a centered Gothic-style H monogram.
639
+ </desc>
640
+
641
+ <style>
642
+ .stroke {
643
+ stroke: white;
644
+ stroke-width: 8;
645
+ stroke-linecap: round;
646
+ stroke-linejoin: round;
647
+ fill: none;
648
+ }
649
+ .fill {
650
+ fill: #127d44;
651
+ }
652
+ </style>
653
+
654
+ <!-- Outer seal circle -->
655
+ <circle class="stroke" cx="128" cy="128" r="112" />
656
+
657
+ <!-- Inner decorative circle -->
658
+ <circle class="stroke" cx="128" cy="128" r="96" stroke-dasharray="14 10" />
659
+
660
+ <!-- Gothic-style H (stylized manually) -->
661
+ <g transform="translate(78,72)">
662
+ <!-- Left vertical stroke with spiked top/bottom -->
663
+ <path class="fill" d="M8 0 L20 0 L20 112 L8 112 L0 100 L0 12 Z" />
664
+ <!-- Right vertical stroke with spiked top/bottom -->
665
+ <path class="fill" d="M80 0 L92 0 L100 12 L100 100 L92 112 L80 112 Z" />
666
+ <!-- Crossbar with gothic curve -->
667
+ <path class="fill" d="M20 48 H80 V64 H20 Z" />
668
+ <!-- Decorative inward serifs -->
669
+ <path class="fill" d="M20 0 q10 8 0 16 Z" />
670
+ <path class="fill" d="M80 0 q-10 8 0 16 Z" />
671
+ <path class="fill" d="M20 96 q10 8 0 16 Z" />
672
+ <path class="fill" d="M80 96 q-10 8 0 16 Z" />
673
+ </g>
674
+ </svg>
675
+ `;
676
+
677
+ const getDataFn = ({ n, d, q }) =>
678
+ getData({
679
+ n,
680
+ d,
681
+ q,
682
+ endpointBuilder: (n, d, q) => {
683
+ return `${window.location.pathname.replace(/\/$/, "")}/data/${n}/${d}${
684
+ q ? "?" + q : ""
685
+ }`;
686
+ },
687
+ fetchStats: getJson,
688
+ });
689
+
690
+ let UI = [];
691
+
692
+ async function fetchUI() {
693
+ try {
694
+ const res = await fetch(window.location.href + "/layout");
695
+ UI = await res.json();
696
+ } catch (e) {
697
+ UI = [];
698
+ }
699
+ }
700
+
701
+ class App extends x {
702
+ constructor(props) {
703
+ super(props);
704
+ this.state = {
705
+ numDays: 30,
706
+ date: DATE,
707
+ chartField: "users",
708
+ chartContext: "all",
709
+ uiSectionsLoaded: false,
710
+ };
711
+ }
712
+
713
+ async componentDidMount() {
714
+ await fetchUI();
715
+ this.setState({ uiSectionsLoaded: true });
716
+ fetchData({
717
+ numDays: this.state.numDays,
718
+ date: this.state.date,
719
+ getDataFn,
720
+ onLoadComplete: (stats) => this.setState({ stats }),
721
+ });
722
+ fetchChartData({
723
+ numDays: this.state.numDays,
724
+ date: this.state.date,
725
+ chartContext: this.state.chartContext,
726
+ chartField: this.state.chartField,
727
+ getDataFn,
728
+ onLoadProgress: (i, n) => {
729
+ this.setState({ chartData: { loading: { max: n, value: i } } });
730
+ },
731
+ onLoadComplete: (data, max) => {
732
+ this.setState({ chartData: { loading: false, data, max } });
733
+ },
734
+ });
735
+ }
736
+
737
+ async fetchChartData({ numDays, date, chartContext, chartField }) {
738
+ await fetchChartData({
739
+ numDays: numDays || this.state.numDays,
740
+ date: date || this.state.date,
741
+ chartContext: chartContext || this.state.chartContext,
742
+ chartField: chartField || this.state.chartField,
743
+ getDataFn,
744
+ onLoadProgress: (i, n) =>
745
+ this.setState({ chartData: { loading: { max: n, value: i } } }),
746
+ onLoadComplete: (data, max) =>
747
+ this.setState({ chartData: { loading: false, data, max } }),
748
+ });
749
+ }
750
+
751
+ async fetchData({ numDays, date }) {
752
+ await fetchData({
753
+ numDays: numDays || this.state.numDays,
754
+ date: date || this.state.date,
755
+ getDataFn,
756
+ onLoadComplete: (stats) => this.setState({ stats }),
757
+ });
758
+ }
759
+
760
+ changeNumDays(e) {
761
+ const numDays = e.target.value;
762
+ this.setState({ numDays });
763
+ this.fetchData({ numDays });
764
+ this.fetchChartData({ numDays });
765
+ }
766
+
767
+ changeDate(e) {
768
+ const date = e.target.value;
769
+ this.setState({ date });
770
+ this.fetchData({ date });
771
+ this.fetchChartData({ date });
772
+ }
773
+
774
+ changeChartField(e) {
775
+ const chartField = e.target.value;
776
+ this.setState({ chartField });
777
+ this.fetchChartData({ chartField });
778
+ }
779
+
780
+ changeChartContext(e) {
781
+ const chartContext = e.target.value;
782
+ this.setState({ chartContext });
783
+ this.fetchChartData({ chartContext });
784
+ }
785
+
786
+ render(
787
+ _props,
788
+ {
789
+ chartField,
790
+ chartContext,
791
+ numDays,
792
+ date,
793
+ stats = null,
794
+ chartData = null,
795
+ uiSectionsLoaded,
796
+ }
797
+ ) {
798
+ return m`
799
+ <div id="analytics">
800
+ <div class="analytics__header">
801
+ <div class="header__logo">
802
+ <${Logo} />
803
+ </div>
804
+ <span>Hearald</span>
805
+ <input
806
+ type="text"
807
+ class="analytics__header__input"
808
+ onchange=${(e) => this.changeNumDays(e)}
809
+ value=${numDays}
810
+ />
811
+ <input
812
+ type="text"
813
+ class="analytics__header__input"
814
+ onchange=${(e) => this.changeDate(e)}
815
+ value=${date}
816
+ />
817
+ </div>
818
+ ${!uiSectionsLoaded || !stats || !stats.metrics
819
+ ? m` <div><section class="analytics">Loading...</section></div> `
820
+ : m`
821
+ <div>
822
+ <section class="analytics analytics--chart">
823
+ <form
824
+ class="analytics__menu"
825
+ onsubmit=${(e) => e.preventDefault()}
826
+ >
827
+ <label>
828
+ Context
829
+ <select
830
+ name="context"
831
+ onchange=${(e) => this.changeChartContext(e)}
832
+ >
833
+ ${UI.chartContexts.map(
834
+ ({ id, menuName }) =>
835
+ m`
836
+ <option
837
+ value="${id}"
838
+ selected=${chartContext === id}
839
+ >
840
+ ${menuName}
841
+ </option>
842
+ `
843
+ )}
844
+ </select>
845
+ </label>
846
+ <label>
847
+ Field
848
+ <select
849
+ name="field"
850
+ onchange=${(e) => this.changeChartField(e)}
851
+ >
852
+ ${stats?.metrics?.all
853
+ ? Object.keys(stats.metrics.all).map(
854
+ (f) =>
855
+ m`
856
+ <option
857
+ value="${f}"
858
+ selected=${chartField === f}
859
+ >
860
+ ${f}
861
+ </option>
862
+ `
863
+ )
864
+ : ""}
865
+ </select>
866
+ </label>
867
+ </form>
868
+ <div class="bar-chart">
869
+ ${!chartData &&
870
+ m` <span class="bar-chart__empty">no data</span> `}
871
+ ${chartData &&
872
+ chartData.loading &&
873
+ m`
874
+ <progress
875
+ class="bar-chart__empty"
876
+ max=${chartData.loading.max}
877
+ value=${chartData.loading.value}
878
+ ></progress>
879
+ `}
880
+ ${chartData &&
881
+ !chartData.loading &&
882
+ m`
883
+ <div class="bar-chart__scale">${chartData.max}</div>
884
+ ${chartData.data.map(
885
+ (v) =>
886
+ m`
887
+ <div
888
+ class="bar-chart__bar"
889
+ style="--bar-height:${v.value / chartData.max}"
890
+ >
891
+ <div class="bar-chart__bar__label">
892
+ ${v.date}
893
+ <br />
894
+ <b>${numFormatter(v.value)}</b>
895
+ </div>
896
+ </div>
897
+ `
898
+ )}
899
+ `}
900
+ </div>
901
+ </section>
902
+ ${UI.sections.map((statsSection) => {
903
+ return m`
904
+ <section class="analytics analytics--${statsSection.id}">
905
+ ${statsSection.menuName !== "" &&
906
+ m`
907
+ <h2 class="analytics__title">
908
+ ${statsSection.menuName}
909
+ </h2>
910
+ `}
911
+ ${statsSection.stats.map((f) => {
912
+ return m`
913
+ <${Tile}
914
+ title="${f.label}"
915
+ type="${f.type}"
916
+ value=${deepGet(stats.metrics, f.key)}
917
+ span=${f.span || null}
918
+ ${f.type === "percent" ? "percent" : ""}
919
+ ${f.type === "histogram" ? "histogram" : ""}
920
+ />
921
+ `;
922
+ })}
923
+ </section>
924
+ `;
925
+ })}
926
+ </div>
927
+ `}
928
+ </div>
929
+ `;
930
+ }
931
+ }
932
+
933
+ G(m` <${App} /> `, document.getElementById("analytics-root"));
934
+
935
+ })();
936
+ </script>
937
+ </body>
938
+ </html>