hearald 0.0.1 → 0.0.2

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,941 @@
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
+ console.log("endpoint", endpoint);
431
+
432
+ const data = await fetchStats(endpoint);
433
+ dataCache[key] = data;
434
+ return data;
435
+ }
436
+
437
+ // Fetches and builds chart data for a given field/context over a time window
438
+ async function fetchChartData({
439
+ numDays,
440
+ date,
441
+ chartContext,
442
+ chartField,
443
+ getDataFn,
444
+
445
+ onLoadProgress, // (i, n) => void
446
+ onLoadComplete, // (data, max) => void
447
+ }) {
448
+ const n = numDays;
449
+ const d = date;
450
+ const field = chartField;
451
+ const context = chartContext;
452
+
453
+ const [yyyy, mm, dd] = d.split("-");
454
+ const startDate = new Date(yyyy, mm - 1, dd);
455
+
456
+ let data = [];
457
+ let max = 1;
458
+
459
+ for (let i = 0; i < n; i++) {
460
+ let day = new Date(startDate);
461
+ day.setDate(startDate.getDate() - i);
462
+ onLoadProgress(i, n);
463
+
464
+ const json = await getDataFn({ n: 1, d: iso(day) });
465
+ const v = json.metrics[context][field] || 0;
466
+
467
+ data.unshift({ date: iso(day), value: v });
468
+ max = Math.max(max, v);
469
+ }
470
+
471
+ onLoadComplete(data, max);
472
+ }
473
+
474
+ // Fetches and sets the main stats data for a given date/range
475
+ async function fetchData({
476
+ numDays,
477
+ date,
478
+ getDataFn,
479
+ onLoadComplete, // (stats) => void
480
+ }) {
481
+ const n = numDays;
482
+ const d = date;
483
+ const stats = await getDataFn({ n, d });
484
+ onLoadComplete(stats);
485
+ }
486
+
487
+ /* END methods for fetching data from the server */
488
+
489
+ /* START helper methods */
490
+
491
+ const getJson = async (url) => {
492
+ const response = await fetch(url, {
493
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
494
+ });
495
+ return response.json();
496
+ };
497
+
498
+ const deepGet = (obj, path) => {
499
+ const parts = path.split(".");
500
+ let current = obj;
501
+ for (let i = 0; i < parts.length; i += 1) {
502
+ if (current == null) return undefined;
503
+ current = current[parts[i]];
504
+ }
505
+ return current;
506
+ };
507
+
508
+ function numFormatter(n) {
509
+ if (typeof Intl !== "undefined" && Intl.NumberFormat) {
510
+ return new Intl.NumberFormat().format(n);
511
+ }
512
+ return n;
513
+ }
514
+
515
+ /* END helper methods */
516
+
517
+ const prettyNumericFormatter = (val) => {
518
+ const digits = Math.log10(val);
519
+ let truncDigits = 0;
520
+ let decimalPlaces = 0;
521
+ let suffix = "";
522
+ if (digits > 8) {
523
+ truncDigits = 9;
524
+ suffix = "b";
525
+ decimalPlaces = 2;
526
+ } else if (digits > 5) {
527
+ truncDigits = 6;
528
+ suffix = "m";
529
+ decimalPlaces = 2;
530
+ } else if (digits > 3) {
531
+ truncDigits = 3;
532
+ suffix = "k";
533
+ decimalPlaces = 2;
534
+ }
535
+
536
+ const numeric = Math.floor(val / 10 ** truncDigits);
537
+ if (decimalPlaces) {
538
+ const fractional =
539
+ (val % 10 ** decimalPlaces) % 10 ** (truncDigits - decimalPlaces);
540
+ return `${numeric}.${fractional}${suffix}`;
541
+ } else {
542
+ return `${numeric}${suffix}`;
543
+ }
544
+ };
545
+
546
+ const percentFormatter = (val) =>
547
+ `${(Math.round(val * 100) / 100).toFixed(2)}%`;
548
+
549
+ const plainFormatter = (val) => val;
550
+
551
+ const getFormattedValue = (value, percent = false, plain = false) => {
552
+ let formattedValue;
553
+ let formatter = prettyNumericFormatter;
554
+ if (percent) {
555
+ if (plain) {
556
+ console.error("Multiple formatters specified, using percent formatter");
557
+ }
558
+
559
+ formatter = percentFormatter;
560
+ } else if (plain) {
561
+ formatter = plainFormatter;
562
+ }
563
+
564
+ if (isNaN(value)) {
565
+ formattedValue = "(no data)";
566
+ } else {
567
+ formattedValue = formatter(value);
568
+ }
569
+
570
+ return formattedValue;
571
+ };
572
+
573
+ class Tile extends x {
574
+ render({
575
+ title,
576
+ value,
577
+ type,
578
+ percent = false,
579
+ plain = false,
580
+ endRow = false,
581
+ span = null,
582
+ }) {
583
+ return m`
584
+ <div
585
+ class="analytics__tile ${span ? `analytics__tile--span-${span}` : ""}"
586
+ >
587
+ <div class="analytics__tile__header">${title}</div>
588
+ ${type === "histogram"
589
+ ? m`
590
+ <!-- div class="analytics__tile__value" -->
591
+ <ul class="analytics__tile__histogram">
592
+ ${value && value.length
593
+ ? value.map(
594
+ ([label, count]) => m`
595
+ <li class="analytics__tile__histogram__row">
596
+ <div class="analytics__tile__histogram__value">
597
+ ${count}
598
+ </div>
599
+ <div
600
+ class="analytics__tile__histogram__label"
601
+ style="--bar-width:${count / (value[0]?.[1] || 1)}"
602
+ >
603
+ ${label}
604
+ </div>
605
+ </li>
606
+ `
607
+ )
608
+ : m`
609
+ <li class="analytics__tile__histogram__row">No data</li>
610
+ `}
611
+ </ul>
612
+ <!-- /div -->
613
+ `
614
+ : m`
615
+ <div class="analytics__tile__value" data-value=${value}>
616
+ ${getFormattedValue(value, percent, plain)}
617
+ </div>
618
+ `}
619
+ </div>
620
+ ${endRow &&
621
+ m` <div class="analytics__tile analytics__tile--end-row" /> `}
622
+ `;
623
+ }
624
+ }
625
+
626
+ const DATE = new Date().toISOString().split("T")[0];
627
+
628
+ const Logo = () => m`
629
+ <svg
630
+ width="256"
631
+ height="256"
632
+ viewBox="0 0 256 256"
633
+ xmlns="http://www.w3.org/2000/svg"
634
+ role="img"
635
+ aria-labelledby="title desc"
636
+ >
637
+ <title id="title">Hearald</title>
638
+ <desc id="desc">
639
+ Circular seal with a centered Gothic-style H monogram.
640
+ </desc>
641
+
642
+ <style>
643
+ .stroke {
644
+ stroke: white;
645
+ stroke-width: 8;
646
+ stroke-linecap: round;
647
+ stroke-linejoin: round;
648
+ fill: none;
649
+ }
650
+ .fill {
651
+ fill: #127d44;
652
+ }
653
+ </style>
654
+
655
+ <!-- Outer seal circle -->
656
+ <circle class="stroke" cx="128" cy="128" r="112" />
657
+
658
+ <!-- Inner decorative circle -->
659
+ <circle class="stroke" cx="128" cy="128" r="96" stroke-dasharray="14 10" />
660
+
661
+ <!-- Gothic-style H (stylized manually) -->
662
+ <g transform="translate(78,72)">
663
+ <!-- Left vertical stroke with spiked top/bottom -->
664
+ <path class="fill" d="M8 0 L20 0 L20 112 L8 112 L0 100 L0 12 Z" />
665
+ <!-- Right vertical stroke with spiked top/bottom -->
666
+ <path class="fill" d="M80 0 L92 0 L100 12 L100 100 L92 112 L80 112 Z" />
667
+ <!-- Crossbar with gothic curve -->
668
+ <path class="fill" d="M20 48 H80 V64 H20 Z" />
669
+ <!-- Decorative inward serifs -->
670
+ <path class="fill" d="M20 0 q10 8 0 16 Z" />
671
+ <path class="fill" d="M80 0 q-10 8 0 16 Z" />
672
+ <path class="fill" d="M20 96 q10 8 0 16 Z" />
673
+ <path class="fill" d="M80 96 q-10 8 0 16 Z" />
674
+ </g>
675
+ </svg>
676
+ `;
677
+
678
+ const getDataFn = ({ n, d, q }) =>
679
+ getData({
680
+ n,
681
+ d,
682
+ q,
683
+ endpointBuilder: (n, d, q) => {
684
+ return `${window.location.pathname.replace(/\/$/, "")}/data/${n}/${d}${
685
+ q ? "?" + q : ""
686
+ }`;
687
+ },
688
+ fetchStats: getJson,
689
+ });
690
+
691
+ let UI = [];
692
+
693
+ async function fetchUI() {
694
+ try {
695
+ const res = await fetch(window.location.href + "/layout");
696
+ UI = await res.json();
697
+ } catch (e) {
698
+ UI = [];
699
+ }
700
+ }
701
+
702
+ class App extends x {
703
+ constructor(props) {
704
+ super(props);
705
+ this.state = {
706
+ numDays: 30,
707
+ date: DATE,
708
+ chartField: "users",
709
+ chartContext: "all",
710
+ uiSectionsLoaded: false,
711
+ };
712
+ }
713
+
714
+ async componentDidMount() {
715
+ await fetchUI();
716
+ this.setState({ uiSectionsLoaded: true });
717
+ fetchData({
718
+ numDays: this.state.numDays,
719
+ date: this.state.date,
720
+ getDataFn,
721
+ onLoadComplete: (stats) => this.setState({ stats }),
722
+ });
723
+ fetchChartData({
724
+ numDays: this.state.numDays,
725
+ date: this.state.date,
726
+ chartContext: this.state.chartContext,
727
+ chartField: this.state.chartField,
728
+ getDataFn,
729
+ onLoadProgress: (i, n) => {
730
+ this.setState({ chartData: { loading: { max: n, value: i } } });
731
+ },
732
+ onLoadComplete: (data, max) => {
733
+ this.setState({ chartData: { loading: false, data, max } });
734
+ },
735
+ });
736
+ }
737
+
738
+ async fetchChartData({ numDays, date, chartContext, chartField }) {
739
+ await fetchChartData({
740
+ numDays: numDays || this.state.numDays,
741
+ date: date || this.state.date,
742
+ chartContext: chartContext || this.state.chartContext,
743
+ chartField: chartField || this.state.chartField,
744
+ getDataFn,
745
+ onLoadProgress: (i, n) =>
746
+ this.setState({ chartData: { loading: { max: n, value: i } } }),
747
+ onLoadComplete: (data, max) =>
748
+ this.setState({ chartData: { loading: false, data, max } }),
749
+ });
750
+ }
751
+
752
+ async fetchData({ numDays, date }) {
753
+ await fetchData({
754
+ numDays: numDays || this.state.numDays,
755
+ date: date || this.state.date,
756
+ getDataFn,
757
+ onLoadComplete: (stats) => this.setState({ stats }),
758
+ });
759
+ }
760
+
761
+ changeNumDays(e) {
762
+ const numDays = e.target.value;
763
+ this.setState({ numDays });
764
+ this.fetchData({ numDays });
765
+ this.fetchChartData({ numDays });
766
+ }
767
+
768
+ changeDate(e) {
769
+ const date = e.target.value;
770
+ this.setState({ date });
771
+ this.fetchData({ date });
772
+ this.fetchChartData({ date });
773
+ }
774
+
775
+ changeChartField(e) {
776
+ const chartField = e.target.value;
777
+ this.setState({ chartField });
778
+ this.fetchChartData({ chartField });
779
+ }
780
+
781
+ changeChartContext(e) {
782
+ const chartContext = e.target.value;
783
+ this.setState({ chartContext });
784
+ this.fetchChartData({ chartContext });
785
+ }
786
+
787
+ render(
788
+ _props,
789
+ {
790
+ chartField,
791
+ chartContext,
792
+ numDays,
793
+ date,
794
+ stats = null,
795
+ chartData = null,
796
+ uiSectionsLoaded,
797
+ }
798
+ ) {
799
+ return m`
800
+ <div id="analytics">
801
+ <div class="analytics__header">
802
+ <div class="header__logo">
803
+ <${Logo} />
804
+ </div>
805
+ <span>Hearald</span>
806
+ <input
807
+ type="text"
808
+ class="analytics__header__input"
809
+ onchange=${(e) => this.changeNumDays(e)}
810
+ value=${numDays}
811
+ />
812
+ <input
813
+ type="text"
814
+ class="analytics__header__input"
815
+ onchange=${(e) => this.changeDate(e)}
816
+ value=${date}
817
+ />
818
+ </div>
819
+ ${!uiSectionsLoaded || !stats || !stats.metrics
820
+ ? m` <div><section class="analytics">Loading...</section></div> `
821
+ : m`
822
+ <div>
823
+ <section class="analytics analytics--chart">
824
+ <form
825
+ class="analytics__menu"
826
+ onsubmit=${(e) => e.preventDefault()}
827
+ >
828
+ <label>
829
+ Context
830
+ <select
831
+ name="context"
832
+ onchange=${(e) => this.changeChartContext(e)}
833
+ >
834
+ ${UI.chartContexts.map(
835
+ ({ id, menuName }) =>
836
+ m`
837
+ <option
838
+ value="${id}"
839
+ selected=${chartContext === id}
840
+ >
841
+ ${menuName}
842
+ </option>
843
+ `
844
+ )}
845
+ </select>
846
+ </label>
847
+ <label>
848
+ Field
849
+ <select
850
+ name="field"
851
+ onchange=${(e) => this.changeChartField(e)}
852
+ >
853
+ ${stats?.metrics?.all
854
+ ? Object.keys(stats.metrics.all).map(
855
+ (f) =>
856
+ m`
857
+ <option
858
+ value="${f}"
859
+ selected=${chartField === f}
860
+ >
861
+ ${f}
862
+ </option>
863
+ `
864
+ )
865
+ : ""}
866
+ </select>
867
+ </label>
868
+ </form>
869
+ <div class="bar-chart">
870
+ ${!chartData &&
871
+ m` <span class="bar-chart__empty">no data</span> `}
872
+ ${chartData &&
873
+ chartData.loading &&
874
+ m`
875
+ <progress
876
+ class="bar-chart__empty"
877
+ max=${chartData.loading.max}
878
+ value=${chartData.loading.value}
879
+ ></progress>
880
+ `}
881
+ ${chartData &&
882
+ !chartData.loading &&
883
+ m`
884
+ <div class="bar-chart__scale">${chartData.max}</div>
885
+ ${chartData.data.map(
886
+ (v) =>
887
+ m`
888
+ <div
889
+ class="bar-chart__bar"
890
+ style="--bar-height:${v.value / chartData.max}"
891
+ >
892
+ <div class="bar-chart__bar__label">
893
+ ${v.date}
894
+ <br />
895
+ <b>${numFormatter(v.value)}</b>
896
+ </div>
897
+ </div>
898
+ `
899
+ )}
900
+ `}
901
+ </div>
902
+ </section>
903
+ ${UI.sections.map((statsSection) => {
904
+ return m`
905
+ <section class="analytics analytics--${statsSection.id}">
906
+ ${statsSection.menuName !== "" &&
907
+ m`
908
+ <h2 class="analytics__title">
909
+ ${statsSection.menuName}
910
+ </h2>
911
+ `}
912
+ ${statsSection.stats.map((f) => {
913
+ return m`
914
+ <${Tile}
915
+ title="${f.label}"
916
+ type="${f.type}"
917
+ value=${deepGet(stats.metrics, f.key)}
918
+ span=${f.span || null}
919
+ ${f.type === "percent" ? "percent" : ""}
920
+ ${f.type === "histogram" ? "histogram" : ""}
921
+ />
922
+ `;
923
+ })}
924
+ </section>
925
+ `;
926
+ })}
927
+ </div>
928
+ `}
929
+ </div>
930
+ `;
931
+ }
932
+ }
933
+
934
+ console.log("index.js loaded");
935
+
936
+ G(m` <${App} /> `, document.getElementById("analytics-root"));
937
+
938
+ })();
939
+ </script>
940
+ </body>
941
+ </html>