hearald 0.0.1
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/.eslintrc +14 -0
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/TODO.md +34 -0
- package/package.json +45 -0
- package/public/analytics.html +20 -0
- package/public/css/analytics.css +381 -0
- package/public/js/.eslintrc +9 -0
- package/public/js/App.js +321 -0
- package/public/js/Tile.js +112 -0
- package/public/js/index.js +9 -0
- package/public/js/util.js +111 -0
- package/rollup.config.js +59 -0
- package/server/analytics.js +321 -0
- package/server/server.js +48 -0
- package/server/serverEvents.js +105 -0
- package/server/serverTrackError.js +47 -0
- package/shared/events.js +44 -0
- package/shared/utils.js +51 -0
package/.eslintrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Andrew Pariser
|
|
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
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# hearald
|
|
2
|
+
|
|
3
|
+
A whimsical event bus and evented utility for client and server, with event tracking, error logging, and analytics/statistics helpers.
|
|
4
|
+
|
|
5
|
+
## Status (August 2025)
|
|
6
|
+
|
|
7
|
+
- ✅ Event bus/evented logic fully extracted and modularized
|
|
8
|
+
- ✅ Server-side event file logic (read/write/close/load) extracted
|
|
9
|
+
- ✅ Client/server event emit logic and `/e` endpoint extracted
|
|
10
|
+
- ✅ Admin stats endpoint and client fetch utility extracted
|
|
11
|
+
- ✅ Analytics client helpers (iso, getData, fetchChartData, fetchData, numFormatter, dataCache) extracted
|
|
12
|
+
- ✅ Client/server/shared subfolders created and migration in progress
|
|
13
|
+
- ✅ Error tracking utilities and Express error middleware added
|
|
14
|
+
- ✅ Integration examples and usage documentation provided
|
|
15
|
+
- ✅ `.eslintrc` added to declare `Intl` as a global for linting
|
|
16
|
+
- ✅ Lint passes and all functionality verified
|
|
17
|
+
- ⏳ Migration of all helpers to client/server/shared nearly complete
|
|
18
|
+
- ⏳ Custom event tracking, configurable stat definitions, embeddable admin UI, and further documentation/testing are next
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Tracking User Visits (Client)
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { trackVisit } from "hearald/client/trackVisit.js";
|
|
26
|
+
trackVisit({ userId: "user-123" });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Tracking Errors (Client)
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { trackError } from "hearald/client/trackError.js";
|
|
33
|
+
try {
|
|
34
|
+
// ...code...
|
|
35
|
+
} catch (err) {
|
|
36
|
+
trackError(err);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Tracking Errors (Server)
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import {
|
|
44
|
+
trackServerError,
|
|
45
|
+
errorTrackingMiddleware,
|
|
46
|
+
} from "hearald/server/serverTrackError.js";
|
|
47
|
+
app.use(errorTrackingMiddleware);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Analytics Helpers
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import {
|
|
54
|
+
iso,
|
|
55
|
+
getData,
|
|
56
|
+
fetchChartData,
|
|
57
|
+
fetchData,
|
|
58
|
+
numFormatter,
|
|
59
|
+
} from "hearald/shared/analyticsClientHelpers.js";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Admin Stats Endpoint
|
|
63
|
+
|
|
64
|
+
The adminStatsEndpoint has been removed. For admin statistics, use the analytics router provided by hearald/server/analytics.js.
|
|
65
|
+
Example:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { createAnalyticsRouter } from 'hearald/server/analytics.js';
|
|
69
|
+
app.use('/admin/stats', createAnalyticsRouter({...}));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Admin Stats Client Fetcher
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
import { fetchAdminStats } from "hearald/client/adminStatsClient.js";
|
|
76
|
+
const stats = await fetchAdminStats();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Roadmap
|
|
80
|
+
|
|
81
|
+
See TODO.md for next steps and roadmap.
|
package/TODO.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# hearald TODO
|
|
2
|
+
|
|
3
|
+
## Status (August 2025)
|
|
4
|
+
|
|
5
|
+
- Event bus/evented logic, event file helpers, emit logic, admin stats, analytics helpers, and error tracking utilities have all been extracted and modularized.
|
|
6
|
+
- hearald now uses client/server/shared subfolders and has a working .eslintrc for Intl.
|
|
7
|
+
- Lint passes and all functionality is verified.
|
|
8
|
+
- Migration of all helpers to client/server/shared is nearly complete.
|
|
9
|
+
|
|
10
|
+
## Next Steps for hearald
|
|
11
|
+
|
|
12
|
+
1. **Tracking Custom Events**
|
|
13
|
+
|
|
14
|
+
- Make the event schema/config easy to extend (document how to define custom events)
|
|
15
|
+
- Provide a clear API for emitting custom events (e.g., `trackEvent('my_event', {...})`)
|
|
16
|
+
|
|
17
|
+
2. **Generating Statistical Summary Views**
|
|
18
|
+
|
|
19
|
+
- Make stat definitions configurable (allow user to provide their own stat_definition.json)
|
|
20
|
+
- Expose a summary API (e.g., `generateStats(endDate, timeWindow)`)
|
|
21
|
+
- Document how to add new metrics and computed stats
|
|
22
|
+
|
|
23
|
+
3. **Admin UI to See Stats**
|
|
24
|
+
- Provide a basic, embeddable React/Preact/Vue component for admin stats (or a template)
|
|
25
|
+
- Document how to use the endpoint and client fetcher in any admin UI
|
|
26
|
+
- Optionally, provide a standalone admin page as part of the package
|
|
27
|
+
|
|
28
|
+
## General Improvements
|
|
29
|
+
|
|
30
|
+
- Write clear documentation and usage examples
|
|
31
|
+
- Add configuration options for stat definitions and event types
|
|
32
|
+
- Provide ready-to-use middleware and client helpers for common event types
|
|
33
|
+
- Package a basic admin UI or template
|
|
34
|
+
- Test in a clean, new project to validate ease of adoption
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hearald",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A single server drop-in file system based metrics and analytics library.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "rollup --config"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"herald",
|
|
12
|
+
"analytics"
|
|
13
|
+
],
|
|
14
|
+
"author": "pariser",
|
|
15
|
+
"homepage": "https://github.com/pariser/hearald",
|
|
16
|
+
"repository": "https://github.com/pariser/hearald",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"bussin-js": "^1.0.1",
|
|
20
|
+
"chalk": "^4.1.0",
|
|
21
|
+
"express": "^4.17.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@rollup/plugin-commonjs": "^15.1.0",
|
|
25
|
+
"@rollup/plugin-json": "^4.1.0",
|
|
26
|
+
"@rollup/plugin-node-resolve": "^13.3.0",
|
|
27
|
+
"@rollup/plugin-replace": "^2.3.3",
|
|
28
|
+
"@web/rollup-plugin-html": "^2.3.0",
|
|
29
|
+
"eslint": "^8.27.0",
|
|
30
|
+
"eslint-plugin-import": "^2.26.0",
|
|
31
|
+
"eslint-plugin-jsx-a11y": "^6.6.1",
|
|
32
|
+
"eslint-plugin-react": "^7.31.10",
|
|
33
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
34
|
+
"htm": "^3.0.3",
|
|
35
|
+
"preact": "^10.3.3",
|
|
36
|
+
"prettier": "2.2.1",
|
|
37
|
+
"rollup": "^2.79.2",
|
|
38
|
+
"rollup-plugin-embed-css": "^1.0.26",
|
|
39
|
+
"rollup-plugin-generate-html-template": "^1.7.0",
|
|
40
|
+
"rollup-plugin-output-manifest": "^2.0.0",
|
|
41
|
+
"rollup-plugin-postcss": "^4.0.2",
|
|
42
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
43
|
+
"style-inject": "^0.3.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
<link rel="stylesheet" href="./css/analytics.css" />
|
|
14
|
+
</head>
|
|
15
|
+
|
|
16
|
+
<body>
|
|
17
|
+
<div id="analytics-root"></div>
|
|
18
|
+
<script src="./js/index.js" defer></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--global-max-width: 480px;
|
|
3
|
+
|
|
4
|
+
--border-radius: 4px;
|
|
5
|
+
|
|
6
|
+
/* Fonts */
|
|
7
|
+
--font-sans-serif: Raleway, sans-serif;
|
|
8
|
+
|
|
9
|
+
/* Colors: start point https://color.adobe.com/Architecture-color-theme-20477657 */
|
|
10
|
+
--color-black: hsla(214, 94%, 7%, 1);
|
|
11
|
+
--color-dark: hsla(209, 37%, 18%, 1);
|
|
12
|
+
--color-mid: hsla(196, 34%, 48%, 1);
|
|
13
|
+
--color-light: hsla(191, 20%, 68%, 1);
|
|
14
|
+
--color-accent: hsla(38, 85%, 64%, 1);
|
|
15
|
+
--color-white: hsla(38, 38%, 90%, 1);
|
|
16
|
+
|
|
17
|
+
--color-text: var(--color-white);
|
|
18
|
+
--color-label: var(--color-light);
|
|
19
|
+
|
|
20
|
+
--color-link: var(--color-accent);
|
|
21
|
+
--color-link-hover: var(--color-light);
|
|
22
|
+
--color-title: var(--color-accent);
|
|
23
|
+
|
|
24
|
+
--color-bg: var(--color-black);
|
|
25
|
+
--color-menu: var(--color-dark);
|
|
26
|
+
|
|
27
|
+
--color-input-bg: var(--color-white);
|
|
28
|
+
--color-input-text: var(--color-dark);
|
|
29
|
+
|
|
30
|
+
--color-button-bg: var(--color-mid);
|
|
31
|
+
--color-button-bg-hover: var(--color-light);
|
|
32
|
+
--color-button-text: var(--color-black);
|
|
33
|
+
|
|
34
|
+
--color-logo-sign: var(--color-success);
|
|
35
|
+
|
|
36
|
+
--color-success: #127d44;
|
|
37
|
+
--color-error: #ad2e41;
|
|
38
|
+
|
|
39
|
+
--animation-plop: plop 300ms ease-out forwards;
|
|
40
|
+
|
|
41
|
+
--hero-box-shadow: 0 0 0.75em rgba(0, 0, 0, 0.75);
|
|
42
|
+
|
|
43
|
+
--carousel-hover-transition: 80ms ease-out transform;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@media (prefers-color-scheme: light) {
|
|
47
|
+
:root {
|
|
48
|
+
--color-text: var(--color-dark);
|
|
49
|
+
--color-label: hsla(196, 34%, 38%, 1);
|
|
50
|
+
|
|
51
|
+
--color-link: var(--color-mid);
|
|
52
|
+
--color-link-hover: var(--color-light);
|
|
53
|
+
--color-title: var(--color-dark);
|
|
54
|
+
|
|
55
|
+
--color-bg: #fff;
|
|
56
|
+
--color-menu: #fff;
|
|
57
|
+
|
|
58
|
+
--color-input-bg: var(--color-white);
|
|
59
|
+
--color-input-text: var(--color-dark);
|
|
60
|
+
|
|
61
|
+
--color-button-bg: var(--color-mid);
|
|
62
|
+
--color-button-bg-hover: var(--color-light);
|
|
63
|
+
--color-button-text: var(--color-black);
|
|
64
|
+
|
|
65
|
+
--color-logo-sign: var(--color-success);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
html {
|
|
70
|
+
height: 100%;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
* {
|
|
74
|
+
box-sizing: border-box;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
body {
|
|
78
|
+
font-family: var(--font-sans-serif);
|
|
79
|
+
line-height: 1.6;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
background: var(--color-bg);
|
|
82
|
+
margin: 0;
|
|
83
|
+
height: 100%;
|
|
84
|
+
-webkit-text-size-adjust: none;
|
|
85
|
+
text-size-adjust: none;
|
|
86
|
+
color: var(--color-text);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* need this to override the user style sheet in chrome, which has higher
|
|
90
|
+
specificity than inheriting the font style tag from the body */
|
|
91
|
+
input {
|
|
92
|
+
font-family: var(--font-sans-serif);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
body.lock-zoom {
|
|
96
|
+
touch-action: none;
|
|
97
|
+
overscroll-behavior-y: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
h1 {
|
|
101
|
+
font-family: var(--font-sans-serif);
|
|
102
|
+
font-size: 2.5em;
|
|
103
|
+
margin: 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
h2 {
|
|
107
|
+
font-family: var(--font-sans-serif);
|
|
108
|
+
font-size: 1.5em;
|
|
109
|
+
margin: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
td,
|
|
113
|
+
th {
|
|
114
|
+
padding: 0 0.5em;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
th {
|
|
118
|
+
text-align: left;
|
|
119
|
+
font-size: 0.8em;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media screen and (max-width: 640px) {
|
|
123
|
+
body {
|
|
124
|
+
font-size: 12px;
|
|
125
|
+
font-feature-settings: 'lnum';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.hidden {
|
|
130
|
+
display: none !important;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.header__logo,
|
|
134
|
+
.header__button {
|
|
135
|
+
width: 2rem;
|
|
136
|
+
height: 2rem;
|
|
137
|
+
font-size: 2rem;
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
padding: 0;
|
|
142
|
+
text-decoration: none;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.header__button:hover {
|
|
146
|
+
text-decoration: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.header__button--has-menu {
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* ---- */
|
|
154
|
+
|
|
155
|
+
.analytics__header {
|
|
156
|
+
grid-area: header;
|
|
157
|
+
padding: 2em;
|
|
158
|
+
background-color: var(--color-dark);
|
|
159
|
+
display: flex;
|
|
160
|
+
gap: 2em;
|
|
161
|
+
align-items: center;
|
|
162
|
+
grid-template-columns: auto auto auto auto 1fr;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.analytics {
|
|
166
|
+
--analytics-grid-columns: 4;
|
|
167
|
+
|
|
168
|
+
display: grid;
|
|
169
|
+
grid-template-columns: repeat(var(--analytics-grid-columns), 1fr);
|
|
170
|
+
grid-gap: 0.5em;
|
|
171
|
+
padding: 1em;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.analytics__title {
|
|
175
|
+
grid-column: span var(--analytics-grid-columns);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.analytics__tile {
|
|
179
|
+
display: flex;
|
|
180
|
+
/* flex: 1fr 5fr; */
|
|
181
|
+
flex: 1 1 auto;
|
|
182
|
+
flex-direction: column;
|
|
183
|
+
border: 1px solid var(--color-accent);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.analytics__tile--end-row {
|
|
187
|
+
border: 0;
|
|
188
|
+
grid-column: span var(--analytics-grid-columns);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.analytics__tile__header {
|
|
192
|
+
padding: 0.2em 0.5em;
|
|
193
|
+
border-bottom: 1px solid var(--color-accent);
|
|
194
|
+
}
|
|
195
|
+
.analytics__tile__value {
|
|
196
|
+
padding: 0.2em 0.5em;
|
|
197
|
+
margin: 0 auto;
|
|
198
|
+
font-size: 2.5em;
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.analytics__menu {
|
|
203
|
+
grid-column: 1 /1;
|
|
204
|
+
display: flex;
|
|
205
|
+
gap: 1em;
|
|
206
|
+
align-items: center;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.analytics__menu > label {
|
|
210
|
+
display: flex;
|
|
211
|
+
gap: 0.5em;
|
|
212
|
+
align-items: center;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.analytics__menu > button {
|
|
216
|
+
font-size: 9px;
|
|
217
|
+
line-height: 9px;
|
|
218
|
+
height: 18px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.bar-chart {
|
|
222
|
+
grid-column: 1/-1;
|
|
223
|
+
height: 200px;
|
|
224
|
+
display: grid;
|
|
225
|
+
grid-auto-flow: column;
|
|
226
|
+
grid-auto-columns: 1fr;
|
|
227
|
+
grid-template-columns: 1fr;
|
|
228
|
+
gap: 2px;
|
|
229
|
+
position: relative;
|
|
230
|
+
align-items: end;
|
|
231
|
+
margin-top: 1em;
|
|
232
|
+
padding: 0 1.5em 0 0;
|
|
233
|
+
border-bottom: 1px solid var(--color-light);
|
|
234
|
+
}
|
|
235
|
+
.bar-chart__bar {
|
|
236
|
+
background: var(--color-mid);
|
|
237
|
+
height: calc(var(--bar-height) * 100%);
|
|
238
|
+
position: relative;
|
|
239
|
+
}
|
|
240
|
+
.bar-chart__bar__label {
|
|
241
|
+
display: none;
|
|
242
|
+
position: absolute;
|
|
243
|
+
bottom: calc(100% + 0.75em);
|
|
244
|
+
left: 50%;
|
|
245
|
+
transform: translateX(-50%);
|
|
246
|
+
z-index: 2;
|
|
247
|
+
background: var(--color-label);
|
|
248
|
+
padding: 0.25em 0.5em;
|
|
249
|
+
border-radius: 0.25em;
|
|
250
|
+
text-align: center;
|
|
251
|
+
white-space: nowrap;
|
|
252
|
+
word-break: keep-all;
|
|
253
|
+
}
|
|
254
|
+
.bar-chart__bar__label:after {
|
|
255
|
+
content: '';
|
|
256
|
+
width: 12px;
|
|
257
|
+
height: 12px;
|
|
258
|
+
background: var(--color-label);
|
|
259
|
+
position: absolute;
|
|
260
|
+
margin: auto;
|
|
261
|
+
left: 0;
|
|
262
|
+
right: 0;
|
|
263
|
+
bottom: 0;
|
|
264
|
+
transform: translateY(50%) rotate(45deg);
|
|
265
|
+
z-index: 1;
|
|
266
|
+
}
|
|
267
|
+
.bar-chart__bar:hover {
|
|
268
|
+
background: var(--color-light);
|
|
269
|
+
}
|
|
270
|
+
.bar-chart__bar:hover > .bar-chart__bar__label {
|
|
271
|
+
display: block;
|
|
272
|
+
}
|
|
273
|
+
.bar-chart__empty {
|
|
274
|
+
place-self: center;
|
|
275
|
+
}
|
|
276
|
+
.bar-chart__scale {
|
|
277
|
+
align-self: start;
|
|
278
|
+
border-top: 1px solid var(--color-light);
|
|
279
|
+
border-right: 1px solid var(--color-light);
|
|
280
|
+
height: 100%;
|
|
281
|
+
padding-right: 0.2em;
|
|
282
|
+
text-align: right;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.analytics__tile__histogram {
|
|
286
|
+
list-style-type: none;
|
|
287
|
+
padding: 0;
|
|
288
|
+
margin: 0;
|
|
289
|
+
display: grid;
|
|
290
|
+
grid-template-columns: auto 1fr;
|
|
291
|
+
gap: 1em;
|
|
292
|
+
grid-column: 1/-1;
|
|
293
|
+
min-height: 10em;
|
|
294
|
+
max-height: 20em;
|
|
295
|
+
overflow: auto;
|
|
296
|
+
align-items: start;
|
|
297
|
+
grid-auto-rows: max-content;
|
|
298
|
+
padding: 0.5em 0.5em;
|
|
299
|
+
}
|
|
300
|
+
.analytics__tile__histogram__value {
|
|
301
|
+
text-align: right;
|
|
302
|
+
}
|
|
303
|
+
.analytics__tile__histogram__row {
|
|
304
|
+
display: grid;
|
|
305
|
+
grid-template-columns: subgrid;
|
|
306
|
+
grid-column: 1/-1;
|
|
307
|
+
align-self: start;
|
|
308
|
+
}
|
|
309
|
+
.analytics__tile__histogram__label {
|
|
310
|
+
position: relative;
|
|
311
|
+
background-image: linear-gradient(
|
|
312
|
+
to right,
|
|
313
|
+
var(--color-mid) calc(var(--bar-width) * 100%),
|
|
314
|
+
transparent calc(var(--bar-width) * 100%)
|
|
315
|
+
);
|
|
316
|
+
background-size: 100%;
|
|
317
|
+
padding: 0 0.5em;
|
|
318
|
+
min-width: 0;
|
|
319
|
+
min-height: 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.analytics__tile--span-2 {
|
|
323
|
+
grid-column: span 2;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
@media screen and (max-width: 640px) {
|
|
327
|
+
.analytics {
|
|
328
|
+
--analytics-grid-columns: 2;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.analytics__tile--end-row {
|
|
332
|
+
display: none !important;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.analytics__tile--span-2 {
|
|
336
|
+
grid-column: span 1;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@media screen and (max-width: 380px) {
|
|
341
|
+
.analytics {
|
|
342
|
+
--analytics-grid-columns: 1;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.analytics__header__input {
|
|
346
|
+
width: 35%;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.analytics__menu > label {
|
|
350
|
+
display: grid;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.bar-chart {
|
|
354
|
+
grid-auto-flow: row;
|
|
355
|
+
height: auto;
|
|
356
|
+
border-bottom: 0;
|
|
357
|
+
border-left: 1px solid var(--color-light);
|
|
358
|
+
padding: 0;
|
|
359
|
+
}
|
|
360
|
+
.bar-chart__scale {
|
|
361
|
+
height: auto;
|
|
362
|
+
width: 100%;
|
|
363
|
+
border-top: 0;
|
|
364
|
+
border-bottom: 1px solid var(--color-light);
|
|
365
|
+
border-right: 1px solid var(--color-light);
|
|
366
|
+
}
|
|
367
|
+
.bar-chart__bar {
|
|
368
|
+
height: 0.5em;
|
|
369
|
+
width: calc(var(--bar-height) * 100%);
|
|
370
|
+
}
|
|
371
|
+
.bar-chart__bar__label {
|
|
372
|
+
left: 0.5em;
|
|
373
|
+
transform: translateX(0);
|
|
374
|
+
}
|
|
375
|
+
.bar-chart__bar__label:after {
|
|
376
|
+
left: 3px;
|
|
377
|
+
right: auto;
|
|
378
|
+
bottom: 0;
|
|
379
|
+
transform: translateY(50%) rotate(45deg);
|
|
380
|
+
}
|
|
381
|
+
}
|