janela 0.3.0 → 0.4.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.
@@ -0,0 +1,343 @@
1
+ /* Vitral: Janela's optional theme.
2
+
3
+ A vitral is a stained glass window, which is what this makes of a dashboard.
4
+ Each pane holds its own colour, dark leading runs between them, and the
5
+ light comes from behind rather than from a lamp in the room.
6
+
7
+ It is separate from janela.css on purpose. That one is structure: the grid
8
+ classes an analyst's numbers choose from, and enough style that a pane is
9
+ legible on install. This one is taste, and taste is the first thing a host
10
+ will want to replace (ADR 023).
11
+
12
+ <%= stylesheet_link_tag "janela" %>
13
+ <%= stylesheet_link_tag "vitral" %>
14
+
15
+ Order matters: vitral is written to sit on top of janela. Everything here
16
+ is a custom property or a janela-* class, so it touches nothing of yours.
17
+ Set the properties on :root to retheme it without editing this file, and
18
+ put class="vitral" on the element that should carry the light. */
19
+
20
+ :root {
21
+ --vitral-ink: #16202b;
22
+ --vitral-muted: #4a5766;
23
+
24
+ /* The light behind the window. */
25
+ --vitral-light-cobalt: rgba(54, 122, 235, 0.34);
26
+ --vitral-light-teal: rgba(30, 170, 160, 0.26);
27
+ --vitral-light-amber: rgba(242, 178, 74, 0.26);
28
+ --vitral-light-rose: rgba(226, 98, 132, 0.20);
29
+ --vitral-light-violet: rgba(138, 110, 228, 0.22);
30
+ --vitral-ground: #e3eaf3;
31
+ /* The sun outside the window, the shafts it throws across the room, and
32
+ the ghosts a lens makes of it. */
33
+ --vitral-flare: rgba(255, 252, 244, 0.7);
34
+ --vitral-sun-x: 88vw;
35
+ --vitral-sun-y: -2vh;
36
+ --vitral-shaft: rgba(255, 253, 248, 0.92);
37
+
38
+ /* The glass, one hue per pane, laid over white so that only the surface
39
+ takes the colour and the text keeps its contrast. */
40
+ --vitral-pane-1: rgba(54, 122, 235, 0.32);
41
+ --vitral-pane-2: rgba(28, 168, 158, 0.30);
42
+ --vitral-pane-3: rgba(240, 176, 70, 0.34);
43
+ --vitral-pane-4: rgba(224, 96, 130, 0.28);
44
+ --vitral-pane-5: rgba(138, 110, 228, 0.30);
45
+
46
+ --vitral-glass: rgba(255, 255, 255, 0.55);
47
+ --vitral-glass-strong: rgba(255, 255, 255, 0.78);
48
+ --vitral-blur: saturate(160%) blur(14px);
49
+
50
+ /* Came: the dark lead that holds each pane. */
51
+ --vitral-came: rgba(26, 38, 54, 0.38);
52
+ --vitral-came-soft: rgba(26, 38, 54, 0.22);
53
+
54
+ /* The lead's own colour, and how much of it survives behind the reading
55
+ column. Both deliberately faint: this is texture at the edge of sight,
56
+ and it competes with the text the moment it is anything more. */
57
+ --vitral-lattice-ink: rgba(44, 62, 84, 0.14);
58
+ /* Held back only where the words are, which on a wide screen is the left of
59
+ the reading column rather than the middle of the window. The dip used to
60
+ span the whole centre and took the pattern out of a large area that has
61
+ nothing in it to clash with. */
62
+ --vitral-lattice-mask: linear-gradient(90deg,
63
+ #000 0 10%, rgba(0, 0, 0, 0.42) 20%, rgba(0, 0, 0, 0.42) 46%, #000 62% 100%);
64
+
65
+ --vitral-radius: 10px;
66
+ --vitral-shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 16px 34px -22px rgba(16, 24, 40, 0.45);
67
+
68
+ /* Janela's own two properties, restated in the theme's terms so the parts
69
+ janela.css draws agree with the parts this one does. */
70
+ --janela-line: var(--vitral-came-soft);
71
+ --janela-accent: rgb(40, 110, 205);
72
+ }
73
+
74
+ /* Opt in, because a theme that repaints a host's <body> uninvited is not a
75
+ theme, it is a takeover. Janela's own pages carry it themselves. */
76
+ .vitral, .janela-page {
77
+ color: var(--vitral-ink);
78
+ background-color: var(--vitral-ground);
79
+ }
80
+
81
+ .vitral, body:has(> .janela-page) { min-height: 100vh; }
82
+
83
+ .vitral::before, .janela-page::before {
84
+ content: "";
85
+ position: fixed;
86
+ inset: -20vmax;
87
+ z-index: -2;
88
+ background:
89
+ /* Shafts of light, angled and parallel, the way a leadlight window throws
90
+ them across a floor. A linear gradient is already a band at an angle,
91
+ so no rotated element is needed for it. Soft stops on both sides,
92
+ because a hard edge reads as a graphic rather than as light. */
93
+ /* Shafts of light, angled and parallel, the way a leadlight window throws
94
+ them across a floor. A band is perpendicular to its gradient's angle, so
95
+ falling from the top right means an axis pointing down and right. */
96
+ linear-gradient(135deg,
97
+ transparent 36%, rgba(255, 252, 246, 0.22) 41%, var(--vitral-shaft) 45.5%,
98
+ rgba(255, 252, 246, 0.3) 49%, transparent 55%),
99
+ linear-gradient(135deg,
100
+ transparent 54%, rgba(255, 252, 247, 0.55) 58%, rgba(255, 252, 247, 0.16) 61%, transparent 66%),
101
+ linear-gradient(135deg,
102
+ transparent 64%, rgba(255, 251, 245, 0.34) 68%, transparent 74%),
103
+ /* A cooler band between the shafts, because light is only bright next to
104
+ something that is not. */
105
+ linear-gradient(135deg,
106
+ transparent 49%, rgba(104, 128, 166, 0.14) 52.5%, transparent 56%),
107
+
108
+ /* The flare, first so it sits over the colour rather than under it.
109
+ This layer hangs 20vmax outside the window on every side, so a
110
+ percentage here is not a percentage of the screen. Positions are given
111
+ in screen units and shifted by the overhang, which is why every one of
112
+ them carries the same + 20vmax. */
113
+ radial-gradient(4vmax 4vmax at calc(var(--vitral-sun-x) + 20vmax) calc(var(--vitral-sun-y) + 20vmax),
114
+ rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.5) 42%, transparent 66%),
115
+ radial-gradient(22vmax 22vmax at calc(var(--vitral-sun-x) + 20vmax) calc(var(--vitral-sun-y) + 20vmax),
116
+ var(--vitral-flare), rgba(255, 250, 238, 0.24) 40%, transparent 64%),
117
+ radial-gradient(4vmax 4vmax at calc(68vw + 20vmax) calc(24vh + 20vmax),
118
+ rgba(255, 236, 198, 0.42), transparent 62%),
119
+ radial-gradient(2.4vmax 2.4vmax at calc(60vw + 20vmax) calc(35vh + 20vmax),
120
+ rgba(198, 226, 255, 0.4), transparent 64%),
121
+ radial-gradient(6vmax 6vmax at calc(47vw + 20vmax) calc(50vh + 20vmax),
122
+ rgba(255, 224, 212, 0.26), transparent 66%),
123
+ radial-gradient(1.8vmax 1.8vmax at calc(37vw + 20vmax) calc(64vh + 20vmax),
124
+ rgba(206, 238, 222, 0.34), transparent 62%),
125
+ radial-gradient(34vmax 34vmax at 11% 6%, var(--vitral-light-cobalt), transparent 58%),
126
+ radial-gradient(22vmax 22vmax at 38% 27%, var(--vitral-light-teal), transparent 55%),
127
+ radial-gradient(30vmax 30vmax at 83% 14%, var(--vitral-light-rose), transparent 56%),
128
+ radial-gradient(26vmax 26vmax at 64% 47%, var(--vitral-light-violet), transparent 58%),
129
+ radial-gradient(38vmax 38vmax at 91% 69%, var(--vitral-light-amber), transparent 60%),
130
+ radial-gradient(28vmax 28vmax at 23% 81%, var(--vitral-light-teal), transparent 58%),
131
+ radial-gradient(24vmax 24vmax at 57% 96%, var(--vitral-light-cobalt), transparent 55%);
132
+ /* The far layer of the parallax: light moves with the pointer, lead moves
133
+ against it, which is what gives the glass its depth. Both sit still until
134
+ something sets the properties, so this is nothing without the controller
135
+ and nothing for anyone who asked for less motion. */
136
+ transform: translate3d(calc(var(--vitral-shift-x, 0) * 26px),
137
+ calc(var(--vitral-shift-y, 0) * 26px + var(--vitral-drift-far, 0px)), 0);
138
+ transition: transform .45s cubic-bezier(.2, .7, .3, 1);
139
+ }
140
+
141
+ /* The leadlight itself: cut once with a seeded random hand, so no two cells
142
+ are the same shape and nothing repeats across the viewport. Inlined rather
143
+ than shipped as an image, because a stylesheet in a gem cannot rely on a
144
+ host's asset pipeline to resolve a url().
145
+
146
+ Masked away from the middle, which is the part of a real window you put
147
+ clear glass in so that you can see out of it. Text sits there; the leading
148
+ stays in the margins where it belongs. */
149
+ .vitral::after, .janela-page::after {
150
+ content: "";
151
+ position: fixed;
152
+ inset: -6vmax;
153
+ z-index: -1;
154
+ pointer-events: none;
155
+ opacity: 0.5;
156
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1600 1200' preserveAspectRatio='xMidYMid slice'%3E%3Cg stroke='rgba(44,62,84,0.11)' stroke-width='1' stroke-linejoin='round'%3E%3Cpolygon points='0.0,0.0 66.9,0.0 78.6,121.4 0.0,98.3' fill='none'/%3E%3Cpolygon points='66.9,0.0 192.5,0.0 178.8,94.6 78.6,121.4' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='192.5,0.0 290.9,0.0 305.4,64.6' fill='none'/%3E%3Cpolygon points='192.5,0.0 305.4,64.6 178.8,94.6' fill='none'/%3E%3Cpolygon points='290.9,0.0 350.1,0.0 363.5,120.0 305.4,64.6' fill='none'/%3E%3Cpolygon points='350.1,0.0 494.7,0.0 363.5,120.0' fill='none'/%3E%3Cpolygon points='494.7,0.0 457.4,69.4 363.5,120.0' fill='none'/%3E%3Cpolygon points='494.7,0.0 575.6,0.0 457.4,69.4' fill='none'/%3E%3Cpolygon points='575.6,0.0 554.0,78.0 457.4,69.4' fill='none'/%3E%3Cpolygon points='575.6,0.0 637.9,0.0 676.6,71.5 554.0,78.0' fill='none'/%3E%3Cpolygon points='637.9,0.0 776.8,0.0 722.9,91.5 676.6,71.5' fill='none'/%3E%3Cpolygon points='776.8,0.0 846.7,0.0 823.2,107.7' fill='none'/%3E%3Cpolygon points='776.8,0.0 823.2,107.7 722.9,91.5' fill='none'/%3E%3Cpolygon points='846.7,0.0 966.9,0.0 823.2,107.7' fill='none'/%3E%3Cpolygon points='966.9,0.0 932.0,109.5 823.2,107.7' fill='none'/%3E%3Cpolygon points='966.9,0.0 1063.4,0.0 1024.1,73.2 932.0,109.5' fill='none'/%3E%3Cpolygon points='1063.4,0.0 1104.8,0.0 1158.1,71.1 1024.1,73.2' fill='none'/%3E%3Cpolygon points='1104.8,0.0 1226.9,0.0 1198.1,64.3 1158.1,71.1' fill='none'/%3E%3Cpolygon points='1226.9,0.0 1339.6,0.0 1198.1,64.3' fill='none'/%3E%3Cpolygon points='1339.6,0.0 1313.3,67.5 1198.1,64.3' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1339.6,0.0 1386.6,0.0 1426.1,85.2' fill='none'/%3E%3Cpolygon points='1339.6,0.0 1426.1,85.2 1313.3,67.5' fill='none'/%3E%3Cpolygon points='1386.6,0.0 1501.7,0.0 1511.4,112.5 1426.1,85.2' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1501.7,0.0 1600.0,0.0 1600.0,98.7 1511.4,112.5' fill='none'/%3E%3Cpolygon points='0.0,98.3 78.6,121.4 0.0,176.0' fill='none'/%3E%3Cpolygon points='78.6,121.4 84.3,182.0 0.0,176.0' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='78.6,121.4 178.8,94.6 166.6,196.7 84.3,182.0' fill='none'/%3E%3Cpolygon points='178.8,94.6 305.4,64.6 309.4,174.0 166.6,196.7' fill='none'/%3E%3Cpolygon points='305.4,64.6 363.5,120.0 309.4,174.0' fill='none'/%3E%3Cpolygon points='363.5,120.0 384.6,204.4 309.4,174.0' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='363.5,120.0 457.4,69.4 476.7,177.4 384.6,204.4' fill='none'/%3E%3Cpolygon points='457.4,69.4 554.0,78.0 476.7,177.4' fill='none'/%3E%3Cpolygon points='554.0,78.0 554.0,189.5 476.7,177.4' fill='none'/%3E%3Cpolygon points='554.0,78.0 676.6,71.5 675.8,168.7' fill='none'/%3E%3Cpolygon points='554.0,78.0 675.8,168.7 554.0,189.5' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='676.6,71.5 722.9,91.5 774.9,166.4' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='676.6,71.5 774.9,166.4 675.8,168.7' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='722.9,91.5 823.2,107.7 859.6,171.5' fill='none'/%3E%3Cpolygon points='722.9,91.5 859.6,171.5 774.9,166.4' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='823.2,107.7 932.0,109.5 923.8,164.1 859.6,171.5' fill='none'/%3E%3Cpolygon points='932.0,109.5 1024.1,73.2 1044.8,185.9 923.8,164.1' fill='none'/%3E%3Cpolygon points='1024.1,73.2 1158.1,71.1 1143.2,205.1 1044.8,185.9' fill='none'/%3E%3Cpolygon points='1158.1,71.1 1198.1,64.3 1226.6,187.2 1143.2,205.1' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='1198.1,64.3 1313.3,67.5 1328.8,197.5 1226.6,187.2' fill='none'/%3E%3Cpolygon points='1313.3,67.5 1426.1,85.2 1384.3,200.6' fill='none'/%3E%3Cpolygon points='1313.3,67.5 1384.3,200.6 1328.8,197.5' fill='none'/%3E%3Cpolygon points='1426.1,85.2 1511.4,112.5 1527.8,181.8 1384.3,200.6' fill='none'/%3E%3Cpolygon points='1511.4,112.5 1600.0,98.7 1600.0,165.0' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='1511.4,112.5 1600.0,165.0 1527.8,181.8' fill='none'/%3E%3Cpolygon points='0.0,176.0 84.3,182.0 0.0,269.6' fill='none'/%3E%3Cpolygon points='84.3,182.0 121.3,291.5 0.0,269.6' fill='none'/%3E%3Cpolygon points='84.3,182.0 166.6,196.7 160.3,281.3 121.3,291.5' fill='none'/%3E%3Cpolygon points='166.6,196.7 309.4,174.0 287.4,278.9' fill='none'/%3E%3Cpolygon points='166.6,196.7 287.4,278.9 160.3,281.3' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='309.4,174.0 384.6,204.4 389.5,304.1 287.4,278.9' fill='none'/%3E%3Cpolygon points='384.6,204.4 476.7,177.4 474.3,256.5 389.5,304.1' fill='none'/%3E%3Cpolygon points='476.7,177.4 554.0,189.5 569.7,251.7 474.3,256.5' fill='none'/%3E%3Cpolygon points='554.0,189.5 675.8,168.7 672.5,283.9 569.7,251.7' fill='none'/%3E%3Cpolygon points='675.8,168.7 774.9,166.4 734.9,272.3 672.5,283.9' fill='none'/%3E%3Cpolygon points='774.9,166.4 859.6,171.5 846.6,287.4 734.9,272.3' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='859.6,171.5 923.8,164.1 963.1,257.5 846.6,287.4' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='923.8,164.1 1044.8,185.9 1060.5,253.6 963.1,257.5' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1044.8,185.9 1143.2,205.1 1117.1,289.7' fill='none'/%3E%3Cpolygon points='1044.8,185.9 1117.1,289.7 1060.5,253.6' fill='none'/%3E%3Cpolygon points='1143.2,205.1 1226.6,187.2 1117.1,289.7' fill='none'/%3E%3Cpolygon points='1226.6,187.2 1230.4,286.1 1117.1,289.7' fill='none'/%3E%3Cpolygon points='1226.6,187.2 1328.8,197.5 1343.4,275.3 1230.4,286.1' fill='none'/%3E%3Cpolygon points='1328.8,197.5 1384.3,200.6 1343.4,275.3' fill='none'/%3E%3Cpolygon points='1384.3,200.6 1383.9,263.9 1343.4,275.3' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1384.3,200.6 1527.8,181.8 1482.4,282.6 1383.9,263.9' fill='none'/%3E%3Cpolygon points='1527.8,181.8 1600.0,165.0 1600.0,268.9 1482.4,282.6' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='0.0,269.6 121.3,291.5 113.1,370.3 0.0,344.0' fill='none'/%3E%3Cpolygon points='121.3,291.5 160.3,281.3 188.0,371.8 113.1,370.3' fill='none'/%3E%3Cpolygon points='160.3,281.3 287.4,278.9 282.9,349.5' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='160.3,281.3 282.9,349.5 188.0,371.8' fill='none'/%3E%3Cpolygon points='287.4,278.9 389.5,304.1 403.9,354.6 282.9,349.5' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='389.5,304.1 474.3,256.5 452.9,358.5 403.9,354.6' fill='none'/%3E%3Cpolygon points='474.3,256.5 569.7,251.7 547.1,374.2 452.9,358.5' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='569.7,251.7 672.5,283.9 547.1,374.2' fill='none'/%3E%3Cpolygon points='672.5,283.9 664.3,359.3 547.1,374.2' fill='none'/%3E%3Cpolygon points='672.5,283.9 734.9,272.3 765.0,342.7 664.3,359.3' fill='none'/%3E%3Cpolygon points='734.9,272.3 846.6,287.4 822.4,394.5 765.0,342.7' fill='none'/%3E%3Cpolygon points='846.6,287.4 963.1,257.5 915.0,368.7' fill='none'/%3E%3Cpolygon points='846.6,287.4 915.0,368.7 822.4,394.5' fill='none'/%3E%3Cpolygon points='963.1,257.5 1060.5,253.6 1030.4,358.6' fill='none'/%3E%3Cpolygon points='963.1,257.5 1030.4,358.6 915.0,368.7' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1060.5,253.6 1117.1,289.7 1150.6,379.6 1030.4,358.6' fill='none'/%3E%3Cpolygon points='1117.1,289.7 1230.4,286.1 1208.7,362.3 1150.6,379.6' fill='none'/%3E%3Cpolygon points='1230.4,286.1 1343.4,275.3 1208.7,362.3' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='1343.4,275.3 1345.7,347.6 1208.7,362.3' fill='none'/%3E%3Cpolygon points='1343.4,275.3 1383.9,263.9 1416.4,374.4 1345.7,347.6' fill='none'/%3E%3Cpolygon points='1383.9,263.9 1482.4,282.6 1416.4,374.4' fill='none'/%3E%3Cpolygon points='1482.4,282.6 1480.3,354.4 1416.4,374.4' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1482.4,282.6 1600.0,268.9 1600.0,374.1 1480.3,354.4' fill='none'/%3E%3Cpolygon points='0.0,344.0 113.1,370.3 118.3,454.2 0.0,486.0' fill='none'/%3E%3Cpolygon points='113.1,370.3 188.0,371.8 118.3,454.2' fill='none'/%3E%3Cpolygon points='188.0,371.8 198.5,435.2 118.3,454.2' fill='none'/%3E%3Cpolygon points='188.0,371.8 282.9,349.5 292.9,458.6 198.5,435.2' fill='none'/%3E%3Cpolygon points='282.9,349.5 403.9,354.6 376.0,452.4 292.9,458.6' fill='none'/%3E%3Cpolygon points='403.9,354.6 452.9,358.5 466.8,442.9 376.0,452.4' fill='none'/%3E%3Cpolygon points='452.9,358.5 547.1,374.2 568.1,474.3' fill='none'/%3E%3Cpolygon points='452.9,358.5 568.1,474.3 466.8,442.9' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='547.1,374.2 664.3,359.3 654.3,476.5 568.1,474.3' fill='none'/%3E%3Cpolygon points='664.3,359.3 765.0,342.7 740.7,453.5' fill='none'/%3E%3Cpolygon points='664.3,359.3 740.7,453.5 654.3,476.5' fill='none'/%3E%3Cpolygon points='765.0,342.7 822.4,394.5 856.4,442.5 740.7,453.5' fill='none'/%3E%3Cpolygon points='822.4,394.5 915.0,368.7 856.4,442.5' fill='none'/%3E%3Cpolygon points='915.0,368.7 962.7,475.9 856.4,442.5' fill='none'/%3E%3Cpolygon points='915.0,368.7 1030.4,358.6 1024.7,457.0 962.7,475.9' fill='none'/%3E%3Cpolygon points='1030.4,358.6 1150.6,379.6 1132.2,441.4 1024.7,457.0' fill='none'/%3E%3Cpolygon points='1150.6,379.6 1208.7,362.3 1236.3,459.1 1132.2,441.4' fill='none'/%3E%3Cpolygon points='1208.7,362.3 1345.7,347.6 1346.5,441.2 1236.3,459.1' fill='none'/%3E%3Cpolygon points='1345.7,347.6 1416.4,374.4 1346.5,441.2' fill='none'/%3E%3Cpolygon points='1416.4,374.4 1433.8,440.8 1346.5,441.2' fill='none'/%3E%3Cpolygon points='1416.4,374.4 1480.3,354.4 1520.8,465.4' fill='none'/%3E%3Cpolygon points='1416.4,374.4 1520.8,465.4 1433.8,440.8' fill='none'/%3E%3Cpolygon points='1480.3,354.4 1600.0,374.1 1600.0,488.9 1520.8,465.4' fill='none'/%3E%3Cpolygon points='0.0,486.0 118.3,454.2 68.3,539.9 0.0,581.1' fill='none'/%3E%3Cpolygon points='118.3,454.2 198.5,435.2 185.8,554.4 68.3,539.9' fill='none'/%3E%3Cpolygon points='198.5,435.2 292.9,458.6 185.8,554.4' fill='none'/%3E%3Cpolygon points='292.9,458.6 257.8,535.1 185.8,554.4' fill='none'/%3E%3Cpolygon points='292.9,458.6 376.0,452.4 383.9,581.5' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='292.9,458.6 383.9,581.5 257.8,535.1' fill='none'/%3E%3Cpolygon points='376.0,452.4 466.8,442.9 383.9,581.5' fill='none'/%3E%3Cpolygon points='466.8,442.9 493.7,548.1 383.9,581.5' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='466.8,442.9 568.1,474.3 555.8,577.4 493.7,548.1' fill='none'/%3E%3Cpolygon points='568.1,474.3 654.3,476.5 555.8,577.4' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='654.3,476.5 655.8,581.0 555.8,577.4' fill='none'/%3E%3Cpolygon points='654.3,476.5 740.7,453.5 778.0,533.8 655.8,581.0' fill='none'/%3E%3Cpolygon points='740.7,453.5 856.4,442.5 820.0,534.9' fill='none'/%3E%3Cpolygon points='740.7,453.5 820.0,534.9 778.0,533.8' fill='none'/%3E%3Cpolygon points='856.4,442.5 962.7,475.9 928.9,548.1 820.0,534.9' fill='none'/%3E%3Cpolygon points='962.7,475.9 1024.7,457.0 1051.5,558.3 928.9,548.1' fill='none'/%3E%3Cpolygon points='1024.7,457.0 1132.2,441.4 1126.4,575.9 1051.5,558.3' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='1132.2,441.4 1236.3,459.1 1213.6,571.2 1126.4,575.9' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='1236.3,459.1 1346.5,441.2 1333.7,566.1 1213.6,571.2' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='1346.5,441.2 1433.8,440.8 1384.4,570.5' fill='none'/%3E%3Cpolygon points='1346.5,441.2 1384.4,570.5 1333.7,566.1' fill='none'/%3E%3Cpolygon points='1433.8,440.8 1520.8,465.4 1505.3,549.6 1384.4,570.5' fill='none'/%3E%3Cpolygon points='1520.8,465.4 1600.0,488.9 1505.3,549.6' fill='none'/%3E%3Cpolygon points='1600.0,488.9 1600.0,546.9 1505.3,549.6' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='0.0,581.1 68.3,539.9 76.3,643.0 0.0,639.1' fill='none'/%3E%3Cpolygon points='68.3,539.9 185.8,554.4 172.0,657.8 76.3,643.0' fill='none'/%3E%3Cpolygon points='185.8,554.4 257.8,535.1 267.0,658.5 172.0,657.8' fill='none'/%3E%3Cpolygon points='257.8,535.1 383.9,581.5 267.0,658.5' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='383.9,581.5 397.8,675.2 267.0,658.5' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='383.9,581.5 493.7,548.1 469.5,629.4 397.8,675.2' fill='none'/%3E%3Cpolygon points='493.7,548.1 555.8,577.4 469.5,629.4' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='555.8,577.4 577.8,621.2 469.5,629.4' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='555.8,577.4 655.8,581.0 577.8,621.2' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='655.8,581.0 686.5,665.2 577.8,621.2' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='655.8,581.0 778.0,533.8 725.7,671.2 686.5,665.2' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='778.0,533.8 820.0,534.9 871.8,662.1 725.7,671.2' fill='none'/%3E%3Cpolygon points='820.0,534.9 928.9,548.1 962.7,637.1 871.8,662.1' fill='none'/%3E%3Cpolygon points='928.9,548.1 1051.5,558.3 1030.1,669.3 962.7,637.1' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1051.5,558.3 1126.4,575.9 1030.1,669.3' fill='none'/%3E%3Cpolygon points='1126.4,575.9 1135.0,673.2 1030.1,669.3' fill='none'/%3E%3Cpolygon points='1126.4,575.9 1213.6,571.2 1236.0,634.0' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1126.4,575.9 1236.0,634.0 1135.0,673.2' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='1213.6,571.2 1333.7,566.1 1334.9,627.6 1236.0,634.0' fill='none'/%3E%3Cpolygon points='1333.7,566.1 1384.4,570.5 1431.0,636.6 1334.9,627.6' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1384.4,570.5 1505.3,549.6 1535.4,646.5 1431.0,636.6' fill='none'/%3E%3Cpolygon points='1505.3,549.6 1600.0,546.9 1600.0,646.6 1535.4,646.5' fill='none'/%3E%3Cpolygon points='0.0,639.1 76.3,643.0 0.0,717.3' fill='none'/%3E%3Cpolygon points='76.3,643.0 112.6,737.8 0.0,717.3' fill='none'/%3E%3Cpolygon points='76.3,643.0 172.0,657.8 112.6,737.8' fill='none'/%3E%3Cpolygon points='172.0,657.8 185.0,750.9 112.6,737.8' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='172.0,657.8 267.0,658.5 268.5,715.8 185.0,750.9' fill='none'/%3E%3Cpolygon points='267.0,658.5 397.8,675.2 390.3,725.8 268.5,715.8' fill='none'/%3E%3Cpolygon points='397.8,675.2 469.5,629.4 477.7,761.7 390.3,725.8' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='469.5,629.4 577.8,621.2 477.7,761.7' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='577.8,621.2 541.0,765.9 477.7,761.7' fill='none'/%3E%3Cpolygon points='577.8,621.2 686.5,665.2 541.0,765.9' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='686.5,665.2 644.4,724.5 541.0,765.9' fill='none'/%3E%3Cpolygon points='686.5,665.2 725.7,671.2 731.7,744.8' fill='none'/%3E%3Cpolygon points='686.5,665.2 731.7,744.8 644.4,724.5' fill='none'/%3E%3Cpolygon points='725.7,671.2 871.8,662.1 731.7,744.8' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='871.8,662.1 830.2,727.4 731.7,744.8' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='871.8,662.1 962.7,637.1 956.4,736.7 830.2,727.4' fill='none'/%3E%3Cpolygon points='962.7,637.1 1030.1,669.3 956.4,736.7' fill='none'/%3E%3Cpolygon points='1030.1,669.3 1060.9,731.7 956.4,736.7' fill='none'/%3E%3Cpolygon points='1030.1,669.3 1135.0,673.2 1139.7,767.0 1060.9,731.7' fill='none'/%3E%3Cpolygon points='1135.0,673.2 1236.0,634.0 1198.9,759.4 1139.7,767.0' fill='none'/%3E%3Cpolygon points='1236.0,634.0 1334.9,627.6 1305.6,717.6' fill='none'/%3E%3Cpolygon points='1236.0,634.0 1305.6,717.6 1198.9,759.4' fill='none'/%3E%3Cpolygon points='1334.9,627.6 1431.0,636.6 1305.6,717.6' fill='none'/%3E%3Cpolygon points='1431.0,636.6 1389.8,709.2 1305.6,717.6' fill='none'/%3E%3Cpolygon points='1431.0,636.6 1535.4,646.5 1502.2,724.2 1389.8,709.2' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='1535.4,646.5 1600.0,646.6 1502.2,724.2' fill='none'/%3E%3Cpolygon points='1600.0,646.6 1600.0,763.7 1502.2,724.2' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='0.0,717.3 112.6,737.8 0.0,856.8' fill='none'/%3E%3Cpolygon points='112.6,737.8 65.8,859.2 0.0,856.8' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='112.6,737.8 185.0,750.9 179.3,818.4 65.8,859.2' fill='none'/%3E%3Cpolygon points='185.0,750.9 268.5,715.8 264.9,827.5' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='185.0,750.9 264.9,827.5 179.3,818.4' fill='none'/%3E%3Cpolygon points='268.5,715.8 390.3,725.8 264.9,827.5' fill='none'/%3E%3Cpolygon points='390.3,725.8 357.6,840.9 264.9,827.5' fill='none'/%3E%3Cpolygon points='390.3,725.8 477.7,761.7 449.6,828.5 357.6,840.9' fill='none'/%3E%3Cpolygon points='477.7,761.7 541.0,765.9 574.2,810.2' fill='none'/%3E%3Cpolygon points='477.7,761.7 574.2,810.2 449.6,828.5' fill='none'/%3E%3Cpolygon points='541.0,765.9 644.4,724.5 574.2,810.2' fill='none'/%3E%3Cpolygon points='644.4,724.5 681.5,804.6 574.2,810.2' fill='none'/%3E%3Cpolygon points='644.4,724.5 731.7,744.8 681.5,804.6' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='731.7,744.8 745.8,804.4 681.5,804.6' fill='none'/%3E%3Cpolygon points='731.7,744.8 830.2,727.4 840.2,839.6 745.8,804.4' fill='none'/%3E%3Cpolygon points='830.2,727.4 956.4,736.7 947.7,841.2 840.2,839.6' fill='none'/%3E%3Cpolygon points='956.4,736.7 1060.9,731.7 1015.2,857.9' fill='none'/%3E%3Cpolygon points='956.4,736.7 1015.2,857.9 947.7,841.2' fill='none'/%3E%3Cpolygon points='1060.9,731.7 1139.7,767.0 1118.8,802.9 1015.2,857.9' fill='none'/%3E%3Cpolygon points='1139.7,767.0 1198.9,759.4 1230.0,843.7 1118.8,802.9' fill='none'/%3E%3Cpolygon points='1198.9,759.4 1305.6,717.6 1297.0,848.5 1230.0,843.7' fill='none'/%3E%3Cpolygon points='1305.6,717.6 1389.8,709.2 1430.1,810.1 1297.0,848.5' fill='none'/%3E%3Cpolygon points='1389.8,709.2 1502.2,724.2 1534.4,834.9' fill='none'/%3E%3Cpolygon points='1389.8,709.2 1534.4,834.9 1430.1,810.1' fill='none'/%3E%3Cpolygon points='1502.2,724.2 1600.0,763.7 1600.0,816.2 1534.4,834.9' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='0.0,856.8 65.8,859.2 98.7,897.9' fill='none'/%3E%3Cpolygon points='0.0,856.8 98.7,897.9 0.0,930.4' fill='none'/%3E%3Cpolygon points='65.8,859.2 179.3,818.4 188.5,894.9 98.7,897.9' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='179.3,818.4 264.9,827.5 188.5,894.9' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='264.9,827.5 273.2,914.3 188.5,894.9' fill='none'/%3E%3Cpolygon points='264.9,827.5 357.6,840.9 348.8,894.6 273.2,914.3' fill='none'/%3E%3Cpolygon points='357.6,840.9 449.6,828.5 479.8,951.2 348.8,894.6' fill='none'/%3E%3Cpolygon points='449.6,828.5 574.2,810.2 479.8,951.2' fill='none'/%3E%3Cpolygon points='574.2,810.2 554.6,924.2 479.8,951.2' fill='none'/%3E%3Cpolygon points='574.2,810.2 681.5,804.6 679.2,926.8' fill='none'/%3E%3Cpolygon points='574.2,810.2 679.2,926.8 554.6,924.2' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='681.5,804.6 745.8,804.4 679.2,926.8' fill='none'/%3E%3Cpolygon points='745.8,804.4 759.6,900.0 679.2,926.8' fill='none'/%3E%3Cpolygon points='745.8,804.4 840.2,839.6 818.8,926.4 759.6,900.0' fill='%23e06082' fill-opacity='0.04'/%3E%3Cpolygon points='840.2,839.6 947.7,841.2 919.9,948.5' fill='none'/%3E%3Cpolygon points='840.2,839.6 919.9,948.5 818.8,926.4' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='947.7,841.2 1015.2,857.9 919.9,948.5' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1015.2,857.9 1053.4,952.4 919.9,948.5' fill='none'/%3E%3Cpolygon points='1015.2,857.9 1118.8,802.9 1053.4,952.4' fill='none'/%3E%3Cpolygon points='1118.8,802.9 1100.6,902.2 1053.4,952.4' fill='none'/%3E%3Cpolygon points='1118.8,802.9 1230.0,843.7 1230.5,939.1 1100.6,902.2' fill='none'/%3E%3Cpolygon points='1230.0,843.7 1297.0,848.5 1288.9,945.5' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='1230.0,843.7 1288.9,945.5 1230.5,939.1' fill='none'/%3E%3Cpolygon points='1297.0,848.5 1430.1,810.1 1288.9,945.5' fill='none'/%3E%3Cpolygon points='1430.1,810.1 1384.1,935.1 1288.9,945.5' fill='none'/%3E%3Cpolygon points='1430.1,810.1 1534.4,834.9 1487.5,935.5' fill='none'/%3E%3Cpolygon points='1430.1,810.1 1487.5,935.5 1384.1,935.1' fill='none'/%3E%3Cpolygon points='1534.4,834.9 1600.0,816.2 1600.0,906.3 1487.5,935.5' fill='none'/%3E%3Cpolygon points='0.0,930.4 98.7,897.9 94.1,999.7 0.0,1014.5' fill='none'/%3E%3Cpolygon points='98.7,897.9 188.5,894.9 160.7,1019.8' fill='none'/%3E%3Cpolygon points='98.7,897.9 160.7,1019.8 94.1,999.7' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='188.5,894.9 273.2,914.3 160.7,1019.8' fill='none'/%3E%3Cpolygon points='273.2,914.3 261.5,1011.2 160.7,1019.8' fill='none'/%3E%3Cpolygon points='273.2,914.3 348.8,894.6 261.5,1011.2' fill='none'/%3E%3Cpolygon points='348.8,894.6 392.3,999.9 261.5,1011.2' fill='none'/%3E%3Cpolygon points='348.8,894.6 479.8,951.2 470.1,989.1 392.3,999.9' fill='none'/%3E%3Cpolygon points='479.8,951.2 554.6,924.2 588.8,1017.7 470.1,989.1' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='554.6,924.2 679.2,926.8 588.8,1017.7' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='679.2,926.8 653.7,998.0 588.8,1017.7' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='679.2,926.8 759.6,900.0 653.7,998.0' fill='none'/%3E%3Cpolygon points='759.6,900.0 764.9,998.4 653.7,998.0' fill='none'/%3E%3Cpolygon points='759.6,900.0 818.8,926.4 861.4,1005.9 764.9,998.4' fill='none'/%3E%3Cpolygon points='818.8,926.4 919.9,948.5 920.0,1014.1 861.4,1005.9' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='919.9,948.5 1053.4,952.4 1028.7,1001.1 920.0,1014.1' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='1053.4,952.4 1100.6,902.2 1157.4,990.4' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='1053.4,952.4 1157.4,990.4 1028.7,1001.1' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='1100.6,902.2 1230.5,939.1 1233.1,1004.1 1157.4,990.4' fill='none'/%3E%3Cpolygon points='1230.5,939.1 1288.9,945.5 1321.3,1043.1 1233.1,1004.1' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1288.9,945.5 1384.1,935.1 1424.6,992.5 1321.3,1043.1' fill='none'/%3E%3Cpolygon points='1384.1,935.1 1487.5,935.5 1424.6,992.5' fill='none'/%3E%3Cpolygon points='1487.5,935.5 1529.7,1010.4 1424.6,992.5' fill='none'/%3E%3Cpolygon points='1487.5,935.5 1600.0,906.3 1600.0,1004.9 1529.7,1010.4' fill='none'/%3E%3Cpolygon points='0.0,1014.5 94.1,999.7 83.1,1127.7' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='0.0,1014.5 83.1,1127.7 0.0,1123.4' fill='none'/%3E%3Cpolygon points='94.1,999.7 160.7,1019.8 83.1,1127.7' fill='none'/%3E%3Cpolygon points='160.7,1019.8 182.7,1125.6 83.1,1127.7' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='160.7,1019.8 261.5,1011.2 304.2,1130.4' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='160.7,1019.8 304.2,1130.4 182.7,1125.6' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='261.5,1011.2 392.3,999.9 351.1,1114.7 304.2,1130.4' fill='none'/%3E%3Cpolygon points='392.3,999.9 470.1,989.1 351.1,1114.7' fill='none'/%3E%3Cpolygon points='470.1,989.1 452.5,1088.3 351.1,1114.7' fill='none'/%3E%3Cpolygon points='470.1,989.1 588.8,1017.7 546.0,1135.9 452.5,1088.3' fill='none'/%3E%3Cpolygon points='588.8,1017.7 653.7,998.0 684.7,1088.3 546.0,1135.9' fill='none'/%3E%3Cpolygon points='653.7,998.0 764.9,998.4 684.7,1088.3' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='764.9,998.4 762.3,1107.9 684.7,1088.3' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='764.9,998.4 861.4,1005.9 870.9,1093.7 762.3,1107.9' fill='none'/%3E%3Cpolygon points='861.4,1005.9 920.0,1014.1 920.0,1135.3 870.9,1093.7' fill='none'/%3E%3Cpolygon points='920.0,1014.1 1028.7,1001.1 1038.4,1133.2 920.0,1135.3' fill='none'/%3E%3Cpolygon points='1028.7,1001.1 1157.4,990.4 1145.9,1094.5 1038.4,1133.2' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='1157.4,990.4 1233.1,1004.1 1228.3,1087.0 1145.9,1094.5' fill='none'/%3E%3Cpolygon points='1233.1,1004.1 1321.3,1043.1 1228.3,1087.0' fill='none'/%3E%3Cpolygon points='1321.3,1043.1 1335.7,1125.4 1228.3,1087.0' fill='none'/%3E%3Cpolygon points='1321.3,1043.1 1424.6,992.5 1426.0,1098.9' fill='none'/%3E%3Cpolygon points='1321.3,1043.1 1426.0,1098.9 1335.7,1125.4' fill='none'/%3E%3Cpolygon points='1424.6,992.5 1529.7,1010.4 1488.9,1087.4 1426.0,1098.9' fill='none'/%3E%3Cpolygon points='1529.7,1010.4 1600.0,1004.9 1600.0,1117.7' fill='none'/%3E%3Cpolygon points='1529.7,1010.4 1600.0,1117.7 1488.9,1087.4' fill='none'/%3E%3Cpolygon points='0.0,1123.4 83.1,1127.7 112.3,1200.0' fill='none'/%3E%3Cpolygon points='0.0,1123.4 112.3,1200.0 0.0,1200.0' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='83.1,1127.7 182.7,1125.6 112.3,1200.0' fill='none'/%3E%3Cpolygon points='182.7,1125.6 170.3,1200.0 112.3,1200.0' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='182.7,1125.6 304.2,1130.4 304.6,1200.0 170.3,1200.0' fill='%238a6ee4' fill-opacity='0.04'/%3E%3Cpolygon points='304.2,1130.4 351.1,1114.7 387.3,1200.0 304.6,1200.0' fill='none'/%3E%3Cpolygon points='351.1,1114.7 452.5,1088.3 463.3,1200.0 387.3,1200.0' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='452.5,1088.3 546.0,1135.9 572.0,1200.0' fill='none'/%3E%3Cpolygon points='452.5,1088.3 572.0,1200.0 463.3,1200.0' fill='none'/%3E%3Cpolygon points='546.0,1135.9 684.7,1088.3 572.0,1200.0' fill='%23f0b046' fill-opacity='0.04'/%3E%3Cpolygon points='684.7,1088.3 645.3,1200.0 572.0,1200.0' fill='none'/%3E%3Cpolygon points='684.7,1088.3 762.3,1107.9 761.8,1200.0 645.3,1200.0' fill='none'/%3E%3Cpolygon points='762.3,1107.9 870.9,1093.7 867.0,1200.0 761.8,1200.0' fill='%231ca89e' fill-opacity='0.04'/%3E%3Cpolygon points='870.9,1093.7 920.0,1135.3 925.1,1200.0 867.0,1200.0' fill='none'/%3E%3Cpolygon points='920.0,1135.3 1038.4,1133.2 1063.0,1200.0 925.1,1200.0' fill='none'/%3E%3Cpolygon points='1038.4,1133.2 1145.9,1094.5 1063.0,1200.0' fill='none'/%3E%3Cpolygon points='1145.9,1094.5 1103.4,1200.0 1063.0,1200.0' fill='%2338a6eb' fill-opacity='0.04'/%3E%3Cpolygon points='1145.9,1094.5 1228.3,1087.0 1235.6,1200.0 1103.4,1200.0' fill='none'/%3E%3Cpolygon points='1228.3,1087.0 1335.7,1125.4 1340.3,1200.0 1235.6,1200.0' fill='none'/%3E%3Cpolygon points='1335.7,1125.4 1426.0,1098.9 1428.0,1200.0 1340.3,1200.0' fill='none'/%3E%3Cpolygon points='1426.0,1098.9 1488.9,1087.4 1531.3,1200.0 1428.0,1200.0' fill='none'/%3E%3Cpolygon points='1488.9,1087.4 1600.0,1117.7 1600.0,1200.0' fill='none'/%3E%3Cpolygon points='1488.9,1087.4 1600.0,1200.0 1531.3,1200.0' fill='%238a6ee4' fill-opacity='0.04'/%3E%3C/g%3E%3C/svg%3E");
157
+ background-size: cover;
158
+ background-position: center;
159
+ -webkit-mask-image: var(--vitral-lattice-mask);
160
+ mask-image: var(--vitral-lattice-mask);
161
+ /* The near layer of the parallax, so the lead sits in front of the light. */
162
+ transform: translate3d(calc(var(--vitral-shift-x, 0) * -10px),
163
+ calc(var(--vitral-shift-y, 0) * -10px + var(--vitral-drift-near, 0px)), 0);
164
+ transition: transform .2s cubic-bezier(.2, .7, .3, 1);
165
+ }
166
+
167
+ /* A narrow screen is all reading column, so the leading thins out everywhere
168
+ rather than changing shape at a breakpoint. */
169
+ @media (max-width: 64rem) {
170
+ :root { --vitral-lattice-mask: linear-gradient(90deg, rgba(0, 0, 0, 0.5) 0 100%); }
171
+ }
172
+
173
+ /* The live lattice, drawn by the optional controller. Same place, same mask,
174
+ same job as the static one above, except that its nodes lean toward the
175
+ pointer. The static one stands down when this arrives. */
176
+ .vitral-lattice {
177
+ position: fixed;
178
+ left: 0;
179
+ right: 0;
180
+ top: 0;
181
+ z-index: -1;
182
+ pointer-events: none;
183
+ opacity: 0.8;
184
+ -webkit-mask-image: var(--vitral-lattice-mask);
185
+ mask-image: var(--vitral-lattice-mask);
186
+ transform: translate3d(calc(var(--vitral-shift-x, 0) * -10px),
187
+ calc(var(--vitral-shift-y, 0) * -10px + var(--vitral-drift-near, 0px)), 0);
188
+ transition: transform .2s cubic-bezier(.2, .7, .3, 1);
189
+ }
190
+ .vitral-live::after { display: none; }
191
+
192
+ /* One recipe for everything that is meant to read as a pane of glass.
193
+ .vitral-pane is public: put it on your own markup and the page around a
194
+ dashboard is made of the same window. */
195
+ .vitral-pane, .janela-frame > *, .janela-page > .janela-pane, .janela-card, .janela-flash, .janela-list-row {
196
+ position: relative;
197
+ padding: calc(var(--janela-space) * 4) calc(var(--janela-space) * 5);
198
+ background: linear-gradient(var(--vitral-glass), var(--vitral-glass)), var(--vitral-pane-1);
199
+ -webkit-backdrop-filter: var(--vitral-blur);
200
+ backdrop-filter: var(--vitral-blur);
201
+ border: 1px solid var(--vitral-came);
202
+ border-radius: var(--vitral-radius);
203
+ box-shadow: var(--vitral-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.75);
204
+ }
205
+
206
+ /* A window worth looking at cycles its colours rather than repeating one, so a
207
+ pane takes its hue from where it sits. .vitral-panes on a container does the
208
+ same for your own markup. */
209
+ .vitral-panes > *:nth-child(5n + 2),
210
+ .janela-frame > *:nth-child(5n + 2),
211
+ .janela-page > .janela-pane:nth-child(5n + 2),
212
+ .janela-list-row:nth-child(5n + 2), .janela-card:nth-child(5n + 2) {
213
+ background: linear-gradient(var(--vitral-glass), var(--vitral-glass)), var(--vitral-pane-2);
214
+ }
215
+ .vitral-panes > *:nth-child(5n + 3),
216
+ .janela-frame > *:nth-child(5n + 3),
217
+ .janela-page > .janela-pane:nth-child(5n + 3),
218
+ .janela-list-row:nth-child(5n + 3), .janela-card:nth-child(5n + 3) {
219
+ background: linear-gradient(var(--vitral-glass), var(--vitral-glass)), var(--vitral-pane-3);
220
+ }
221
+ .vitral-panes > *:nth-child(5n + 4),
222
+ .janela-frame > *:nth-child(5n + 4),
223
+ .janela-page > .janela-pane:nth-child(5n + 4),
224
+ .janela-list-row:nth-child(5n + 4), .janela-card:nth-child(5n + 4) {
225
+ background: linear-gradient(var(--vitral-glass), var(--vitral-glass)), var(--vitral-pane-4);
226
+ }
227
+ .vitral-panes > *:nth-child(5n + 5),
228
+ .janela-frame > *:nth-child(5n + 5),
229
+ .janela-page > .janela-pane:nth-child(5n + 5),
230
+ .janela-list-row:nth-child(5n + 5), .janela-card:nth-child(5n + 5) {
231
+ background: linear-gradient(var(--vitral-glass), var(--vitral-glass)), var(--vitral-pane-5);
232
+ }
233
+
234
+ /* The line of light along the top edge, where real glass catches it. A canvas
235
+ is a replaced element and takes no pseudo-element, so charts go without. */
236
+ .vitral-pane::before, .janela-frame > *::before,
237
+ .janela-card::before, .janela-list-row::before {
238
+ content: "";
239
+ position: absolute;
240
+ inset: 0 0 auto;
241
+ height: 1px;
242
+ border-radius: var(--vitral-radius) var(--vitral-radius) 0 0;
243
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.9), transparent);
244
+ }
245
+
246
+ /* Inside a panel the pane is content: the grid item carries the glass, since
247
+ a table's caption renders outside the table's own border box and would
248
+ otherwise float above the colour. */
249
+ .janela-frame .janela-pane { background: none; border: 0; box-shadow: none; padding: 0; }
250
+
251
+ .vitral-pane::after, .janela-frame > *::after,
252
+ .janela-card::after, .janela-list-row::after {
253
+ content: "";
254
+ position: absolute;
255
+ inset: 0;
256
+ z-index: -1;
257
+ border-radius: inherit;
258
+ pointer-events: none;
259
+ background: linear-gradient(116deg,
260
+ rgba(255, 255, 255, 0.5) 0 10%,
261
+ rgba(255, 255, 255, 0.16) 24%,
262
+ rgba(255, 255, 255, 0) 44%);
263
+ }
264
+
265
+ /* A collapsed table drops its corner radius, and glass without corners is a
266
+ pane of a different shape. */
267
+ table.janela-pane { border-collapse: separate; border-spacing: 0; }
268
+ table.janela-pane caption { padding-bottom: calc(var(--janela-space) * 2); }
269
+ table.janela-pane td:first-child { padding-left: 0; }
270
+
271
+ .janela-value-number { letter-spacing: -0.02em; }
272
+ .janela-value-label { opacity: 0.75; }
273
+
274
+ table.janela-pane button[aria-pressed="true"] {
275
+ background: var(--janela-accent);
276
+ color: white;
277
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
278
+ }
279
+
280
+ .janela-empty { color: var(--vitral-muted); }
281
+ .janela-error { border-color: rgba(190, 60, 60, 0.5); }
282
+
283
+ /* Janela's own pages, which carry the theme themselves because the engine owns
284
+ that layout (ADR 011). */
285
+ .janela-page { padding-block: calc(var(--janela-space) * 10); }
286
+ .janela-heading { letter-spacing: -0.02em; }
287
+ .janela-list { display: grid; gap: calc(var(--janela-space) * 2); }
288
+ .janela-list-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
289
+ .janela-field input, .janela-field select {
290
+ background: var(--vitral-glass-strong);
291
+ border-color: var(--vitral-came-soft);
292
+ border-radius: 8px;
293
+ }
294
+ .janela-actions { display: flex; gap: calc(var(--janela-space) * 3); align-items: center; flex-wrap: wrap; }
295
+
296
+ /* A control made of the same glass. */
297
+ .vitral-button {
298
+ display: inline-block;
299
+ padding: .62rem 1.25rem;
300
+ font: inherit;
301
+ font-weight: 550;
302
+ color: var(--vitral-ink);
303
+ text-decoration: none;
304
+ cursor: pointer;
305
+ background: var(--vitral-glass-strong);
306
+ -webkit-backdrop-filter: var(--vitral-blur);
307
+ backdrop-filter: var(--vitral-blur);
308
+ border: 1px solid var(--vitral-came);
309
+ border-radius: 10px;
310
+ box-shadow: var(--vitral-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.75);
311
+ }
312
+ .vitral-button:hover { border-color: rgba(26, 38, 54, 0.62); }
313
+ .vitral-button-primary {
314
+ background: linear-gradient(180deg, rgb(86, 180, 242), rgb(25, 112, 176));
315
+ color: white;
316
+ box-shadow: 0 10px 24px -12px rgba(25, 112, 176, 0.9), inset 0 1px 0 rgba(255, 255, 255, 0.45);
317
+ }
318
+
319
+ /* Glass is an effect, not a requirement. Anyone who asked for less of it, or
320
+ whose browser cannot do it, gets the same window in plain panels. */
321
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
322
+ .vitral-pane, .janela-pane, .janela-card, .janela-flash, .janela-list-row, .vitral-button {
323
+ background: rgba(255, 255, 255, 0.94);
324
+ }
325
+ }
326
+
327
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
328
+ .vitral-pane, .janela-pane, .janela-card, .janela-flash, .janela-list-row, .vitral-button {
329
+ background: #ffffff;
330
+ -webkit-backdrop-filter: none;
331
+ backdrop-filter: none;
332
+ border-color: var(--vitral-came-soft);
333
+ }
334
+ .vitral::before, .vitral::after, .janela-page::before, .janela-page::after { display: none; }
335
+ }
336
+
337
+ @media (prefers-reduced-motion: reduce) {
338
+ .vitral::before, .vitral::after, .vitral-lattice,
339
+ .janela-page::before, .janela-page::after {
340
+ transform: none;
341
+ transition: none;
342
+ }
343
+ }
@@ -1,5 +1,12 @@
1
1
  module Janela
2
2
  class ApplicationController < Janela.parent_controller.constantize
3
+ # A host's own route helpers, so host code inherited from its
4
+ # ApplicationController works in here without knowing it is in an engine
5
+ # (ADR 022). Included before the engine's own helpers are used, and it
6
+ # defines none of their names, so nothing of Janela's is shadowed.
7
+ include Janela::HostRoutes
8
+ helper Janela::HostRoutes
9
+
3
10
  # A frame request needs no layout, since Turbo keeps only the matching
4
11
  # frame. A direct request gets Janela's own minimal layout, because a host
5
12
  # layout's route helpers cannot resolve inside an isolated engine (ADR 011).
@@ -9,7 +9,8 @@ module Janela
9
9
  def janela_frame(frame = nil, charts: true, &block)
10
10
  return render("janela/frames/frame", frame: frame, filters: janela_page_filters, charts: charts) if frame
11
11
 
12
- tag.div(data: { controller: "janela--frame", janela__frame_filters_value: janela_page_filters.to_json }, &block)
12
+ tag.div(data: { controller: "janela--frame", action: "keydown.esc->janela--frame#clear",
13
+ janela__frame_filters_value: janela_page_filters.to_json }, &block)
13
14
  end
14
15
 
15
16
  def janela_pane(model, measure, by: nil, as: :table, granularity: nil, limit: nil)
@@ -103,11 +103,15 @@ module Janela
103
103
  # The Ransack key and value a click on this label should toggle. A null
104
104
  # group filters with the null predicate, not an empty string (ADR 009 has
105
105
  # no say here; see issue #21).
106
+ # A click writes _in whether one value is selected or five, so there is one
107
+ # shape in the controller, the view and a stored snapshot. A hand written
108
+ # _eq link is still read, since a URL somebody already sent should not stop
109
+ # working to suit us (ADR 024).
106
110
  def filter_params(label)
107
111
  return [ nil, nil ] unless clickable?
108
112
  return [ "#{ransack_name}_null", "1" ] if label.to_s == Dimension::NONE
109
113
 
110
- [ "#{ransack_name}_eq", label.to_s ]
114
+ [ "#{ransack_name}_in", label.to_s ]
111
115
  end
112
116
 
113
117
  # Every label in this pane paired with the filter it toggles, for a chart
@@ -120,11 +124,20 @@ module Janela
120
124
 
121
125
  # The filter on this pane's own dimension is not applied to its query, but
122
126
  # it is what the user clicked here, so the view highlights it.
123
- def selected_value
124
- return unless clickable?
125
- return Dimension::NONE if filter("#{ransack_name}_null").present?
127
+ # Every value of this pane's own dimension the filters name. A set, because
128
+ # a dimension can hold more than one (ADR 024), and the null group is in it
129
+ # like any other label.
130
+ def selected_values
131
+ return [] unless clickable?
126
132
 
127
- filter("#{ransack_name}_eq")
133
+ values = Array(filter("#{ransack_name}_in")) + Array(filter("#{ransack_name}_eq"))
134
+ values = values.map(&:to_s)
135
+ values << Dimension::NONE if filter("#{ransack_name}_null").present?
136
+ values
137
+ end
138
+
139
+ def selected?(label)
140
+ selected_values.include?(label.to_s)
128
141
  end
129
142
 
130
143
  private
@@ -1,4 +1,5 @@
1
1
  <%= tag.div class: janela_frame_classes(frame),
2
- data: { controller: "janela--frame", janela__frame_filters_value: filters.to_json } do %>
2
+ data: { controller: "janela--frame", action: "keydown.esc->janela--frame#clear",
3
+ janela__frame_filters_value: filters.to_json } do %>
3
4
  <%= render partial: "janela/frames/pane", collection: frame.panes, as: :pane, locals: { filters: filters, charts: charts } %>
4
5
  <% end %>
@@ -13,7 +13,7 @@
13
13
  data-action="janela--chart:toggle->janela--frame#toggle"
14
14
  data-janela--chart-type-value="<%= query.renderer %>"
15
15
  data-janela--chart-title-value="<%= query.title %>"
16
- data-janela--chart-selected-value="<%= query.selected_value %>"
16
+ data-janela--chart-selected-value="<%= query.selected_values.to_json %>"
17
17
  data-janela--chart-labels-value="<%= result.keys.to_json %>"
18
18
  data-janela--chart-values-value="<%= result.values.map(&:to_f).to_json %>"
19
19
  data-janela--chart-formatted-value="<%= result.values.map { |measured| query.format(measured) }.to_json %>"
@@ -29,7 +29,7 @@
29
29
  <td>
30
30
  <% if key %>
31
31
  <button type="button"
32
- aria-pressed="<%= label.to_s == query.selected_value.to_s && query.selected_value.present? %>"
32
+ aria-pressed="<%= query.selected?(label) %>"
33
33
  data-action="janela--frame#toggle"
34
34
  data-janela--frame-key-param="<%= key %>"
35
35
  data-janela--frame-value-param="<%= value %>">
@@ -2,7 +2,7 @@
2
2
  Deliberately minimal: the engine cannot know the host's asset names or
3
3
  bundler, and a host layout's route helpers do not resolve inside an
4
4
  isolated engine (ADR 011). It loads Janela's own stylesheet, which the gem
5
- ships and can therefore name, and no JavaScript: Turbo and Stimulus come
5
+ ships and can therefore name, plus a theme if one is set, and no JavaScript: Turbo and Stimulus come
6
6
  from the host, so these pages are static dashboards rather than broken
7
7
  ones. A host can override this file, or point the controller at its own
8
8
  layout. %>
@@ -15,9 +15,18 @@
15
15
  <%= csrf_meta_tags %>
16
16
  <%= csp_meta_tag %>
17
17
  <%= stylesheet_link_tag "janela" %>
18
+ <%= stylesheet_link_tag Janela.theme if Janela.theme.present? %>
18
19
  <%= yield :head %>
19
20
  </head>
20
21
  <body class="janela-page">
22
+ <%# Janela's own pages are reached from somewhere, and until ADR 022 the
23
+ engine could not name a host route to get back to. It can now, so the
24
+ layout stops being a dead end. Only when the host has a root, and
25
+ never a guess at what to call the place it goes. %>
26
+ <% if main_app.respond_to?(:root_path) %>
27
+ <p class="janela-exit"><%= link_to t("janela.actions.home"), main_app.root_path %></p>
28
+ <% end %>
29
+
21
30
  <% flash.each do |_kind, message| %>
22
31
  <p class="janela-flash"><%= message %></p>
23
32
  <% end %>
data/config/importmap.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  pin "janela/frame_controller", to: "janela/frame_controller.js"
2
2
  pin "janela/chart_controller", to: "janela/chart_controller.js"
3
+ pin "janela/vitral_controller", to: "janela/vitral_controller.js"
3
4
 
4
5
  # Hosts that already pin their own Chart.js keep it.
5
6
  pin "chart.js", to: "janela/vendor/chart.js" unless packages.key?("chart.js")
@@ -29,6 +29,7 @@ en:
29
29
  delete: Remove
30
30
  delete_frame: "Delete this %{name}"
31
31
  edit: Edit
32
+ home: "Back to the site"
32
33
  move_down: Down
33
34
  move_up: Up
34
35
  new_frame: "New %{name}"