@genesislcap/ai-assistant 14.458.3 → 14.459.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +37 -10
- package/dist/ai-assistant.d.ts +46 -4
- package/dist/dts/components/flowing-waves-indicator.d.ts +32 -0
- package/dist/dts/components/flowing-waves-indicator.d.ts.map +1 -0
- package/dist/dts/components/plasma-orb-indicator.d.ts +22 -0
- package/dist/dts/components/plasma-orb-indicator.d.ts.map +1 -0
- package/dist/dts/components/waves-indicator.d.ts +30 -0
- package/dist/dts/components/waves-indicator.d.ts.map +1 -0
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/main/main.types.d.ts +44 -4
- package/dist/dts/main/main.types.d.ts.map +1 -1
- package/dist/dts/utils/animation-exclusivity.d.ts +23 -0
- package/dist/dts/utils/animation-exclusivity.d.ts.map +1 -0
- package/dist/dts/utils/animation-exclusivity.test.d.ts +2 -0
- package/dist/dts/utils/animation-exclusivity.test.d.ts.map +1 -0
- package/dist/esm/components/flowing-waves-indicator.js +222 -0
- package/dist/esm/components/plasma-orb-indicator.js +280 -0
- package/dist/esm/components/waves-indicator.js +189 -0
- package/dist/esm/main/main.js +20 -9
- package/dist/esm/main/main.styles.js +40 -6
- package/dist/esm/main/main.template.js +58 -17
- package/dist/esm/main/main.types.js +46 -3
- package/dist/esm/utils/animation-exclusivity.js +33 -0
- package/dist/esm/utils/animation-exclusivity.test.js +52 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/flowing-waves-indicator.ts +260 -0
- package/src/components/plasma-orb-indicator.ts +281 -0
- package/src/components/waves-indicator.ts +221 -0
- package/src/main/main.styles.ts +40 -6
- package/src/main/main.template.ts +60 -18
- package/src/main/main.ts +24 -8
- package/src/main/main.types.ts +56 -5
- package/src/utils/animation-exclusivity.test.ts +72 -0
- package/src/utils/animation-exclusivity.ts +40 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
var AiFlowingWavesIndicator_1;
|
|
2
|
+
import { __decorate } from "tslib";
|
|
3
|
+
import { css, customElement, GenesisElement, html } from '@genesislcap/web-core';
|
|
4
|
+
import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
|
|
5
|
+
/** SVG coordinate space the bands are drawn in (aspect matches the host box). */
|
|
6
|
+
const VIEW_W = 225;
|
|
7
|
+
const VIEW_H = 90;
|
|
8
|
+
const CENTRE_Y = VIEW_H / 2;
|
|
9
|
+
/** Number of samples traced across each band. */
|
|
10
|
+
const POINTS = 90;
|
|
11
|
+
/** Fraction of the width over which thickness ramps from 0 up to full. */
|
|
12
|
+
const EDGE = 0.16;
|
|
13
|
+
/** Global multiplier on the harmonic drift speeds — raise to morph faster. */
|
|
14
|
+
const MORPH_SPEED = 3;
|
|
15
|
+
/** Global multiplier on the harmonic frequencies — raise for narrower peaks. */
|
|
16
|
+
const FREQ_SCALE = 2;
|
|
17
|
+
/**
|
|
18
|
+
* Half-thickness (viewBox units) each band keeps even between peaks, so the
|
|
19
|
+
* waves emerge from a continuous line rather than leaving gaps. The four bands
|
|
20
|
+
* overlap along this line and screen-blend to white.
|
|
21
|
+
*/
|
|
22
|
+
const LINE_THICKNESS = 1.2;
|
|
23
|
+
/**
|
|
24
|
+
* Amplitude boost added toward the middle of the width (a hump that is 0 at the
|
|
25
|
+
* ends, max in the centre). Makes peaks more likely to pop up centrally, while
|
|
26
|
+
* still allowing tall crests to appear nearer the edges.
|
|
27
|
+
*/
|
|
28
|
+
const CENTRE_BIAS = 4;
|
|
29
|
+
const BANDS = [
|
|
30
|
+
{
|
|
31
|
+
baseline: -4,
|
|
32
|
+
harmonics: [
|
|
33
|
+
{ freq: 1.0, amp: 11, speed: 0.03, phase: 0.0 },
|
|
34
|
+
{ freq: 2.3, amp: 6, speed: -0.021, phase: 1.3 },
|
|
35
|
+
{ freq: 3.4, amp: 3, speed: 0.041, phase: 2.7 },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
baseline: -4,
|
|
40
|
+
harmonics: [
|
|
41
|
+
{ freq: 1.3, amp: 10, speed: -0.026, phase: 0.8 },
|
|
42
|
+
{ freq: 2.0, amp: 7, speed: 0.034, phase: 2.1 },
|
|
43
|
+
{ freq: 3.1, amp: 3, speed: -0.045, phase: 0.4 },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
baseline: -4,
|
|
48
|
+
harmonics: [
|
|
49
|
+
{ freq: 0.9, amp: 9, speed: 0.038, phase: 1.9 },
|
|
50
|
+
{ freq: 2.6, amp: 6, speed: 0.019, phase: 3.0 },
|
|
51
|
+
{ freq: 3.7, amp: 3, speed: -0.03, phase: 0.6 },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
baseline: -4,
|
|
56
|
+
harmonics: [
|
|
57
|
+
{ freq: 1.1, amp: 8, speed: -0.034, phase: 2.4 },
|
|
58
|
+
{ freq: 2.4, amp: 5, speed: 0.027, phase: 0.2 },
|
|
59
|
+
{ freq: 3.2, amp: 3, speed: 0.048, phase: 1.5 },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
// Each band is its own stacked <svg> LAYER. The white-on-overlap effect uses
|
|
64
|
+
// `mix-blend-mode: screen`, which composites reliably between HTML-level layers
|
|
65
|
+
// but NOT between sibling SVG <path>/<g> elements in many browsers — hence one
|
|
66
|
+
// svg per band rather than four paths in a single svg.
|
|
67
|
+
const bandLayersMarkup = BANDS.map((_b, i) => `<svg class="layer band-layer" viewBox="0 0 ${VIEW_W} ${VIEW_H}">` +
|
|
68
|
+
`<path class="band b${i + 1}" data-band="${i}" />` +
|
|
69
|
+
`</svg>`).join('');
|
|
70
|
+
/**
|
|
71
|
+
* Animated "flowing waves" loading indicator — coloured organic waves (in the
|
|
72
|
+
* four brand colours) whose bumps swell, shrink, merge and split across a line,
|
|
73
|
+
* tapering to nothing at both ends. Where the colours overlap they blend
|
|
74
|
+
* additively toward white.
|
|
75
|
+
*
|
|
76
|
+
* Visual sibling of the dots loading indicator; interchangeable with the other
|
|
77
|
+
* loading styles for the "assistant is working" state.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```html
|
|
81
|
+
* <ai-flowing-waves-indicator></ai-flowing-waves-indicator>
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @beta
|
|
85
|
+
*/
|
|
86
|
+
let AiFlowingWavesIndicator = AiFlowingWavesIndicator_1 = class AiFlowingWavesIndicator extends GenesisElement {
|
|
87
|
+
constructor() {
|
|
88
|
+
// A rAF loop morphs the band shapes: pure CSS can't continuously reshape the
|
|
89
|
+
// bumps (different sizes appearing at different times), and the per-frame sine
|
|
90
|
+
// sums are cheap. Same approach as the sine-tracing waves indicator.
|
|
91
|
+
super(...arguments);
|
|
92
|
+
this.frame = 0;
|
|
93
|
+
}
|
|
94
|
+
connectedCallback() {
|
|
95
|
+
super.connectedCallback();
|
|
96
|
+
// Guard against a reconnect starting a second concurrent loop.
|
|
97
|
+
if (this.animFrame === undefined) {
|
|
98
|
+
this.tick();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
disconnectedCallback() {
|
|
102
|
+
super.disconnectedCallback();
|
|
103
|
+
if (this.animFrame !== undefined) {
|
|
104
|
+
cancelAnimationFrame(this.animFrame);
|
|
105
|
+
this.animFrame = undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
tick() {
|
|
109
|
+
var _a, _c;
|
|
110
|
+
// Stop ticking once disconnected (belt-and-braces alongside the cancel in
|
|
111
|
+
// disconnectedCallback) so no frames are scheduled while detached.
|
|
112
|
+
if (!this.isConnected) {
|
|
113
|
+
this.animFrame = undefined;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!this.bandPaths) {
|
|
117
|
+
const paths = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.band');
|
|
118
|
+
if (paths === null || paths === void 0 ? void 0 : paths.length)
|
|
119
|
+
this.bandPaths = Array.from(paths);
|
|
120
|
+
}
|
|
121
|
+
(_c = this.bandPaths) === null || _c === void 0 ? void 0 : _c.forEach((path, i) => {
|
|
122
|
+
const cfg = BANDS[i];
|
|
123
|
+
if (cfg)
|
|
124
|
+
path.setAttribute('d', AiFlowingWavesIndicator_1.buildBand(cfg, this.frame));
|
|
125
|
+
});
|
|
126
|
+
this.frame += 1;
|
|
127
|
+
this.animFrame = requestAnimationFrame(() => this.tick());
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Trace one band for the given frame: a baseline-plus-harmonics half-thickness
|
|
131
|
+
* profile, clamped at 0 (so the band pinches to nothing between bumps) and
|
|
132
|
+
* tapered to 0 at both ends.
|
|
133
|
+
*/
|
|
134
|
+
static buildBand(cfg, frame) {
|
|
135
|
+
const top = [];
|
|
136
|
+
const bottom = [];
|
|
137
|
+
for (let i = 0; i <= POINTS; i += 1) {
|
|
138
|
+
const u = i / POINTS;
|
|
139
|
+
const x = u * VIEW_W;
|
|
140
|
+
// Plateau edge taper: 0 at both ends, full across the middle.
|
|
141
|
+
const edge = Math.min(1, Math.min(u, 1 - u) / EDGE);
|
|
142
|
+
// Sum the drifting harmonics, then clamp so dips pinch the band to nothing.
|
|
143
|
+
let wave = 0;
|
|
144
|
+
for (const h of cfg.harmonics) {
|
|
145
|
+
wave +=
|
|
146
|
+
h.amp *
|
|
147
|
+
Math.sin(u * Math.PI * 2 * h.freq * FREQ_SCALE + frame * h.speed * MORPH_SPEED + h.phase);
|
|
148
|
+
}
|
|
149
|
+
// Hump that favours the middle (0 at the ends, 1 at the centre).
|
|
150
|
+
const centre = Math.sin(Math.PI * u);
|
|
151
|
+
const env = edge * (LINE_THICKNESS + Math.max(0, cfg.baseline + wave + CENTRE_BIAS * centre));
|
|
152
|
+
top.push([x, CENTRE_Y - env]);
|
|
153
|
+
bottom.push([x, CENTRE_Y + env]);
|
|
154
|
+
}
|
|
155
|
+
let d = `M${top[0][0].toFixed(1)} ${top[0][1].toFixed(1)}`;
|
|
156
|
+
for (let i = 1; i < top.length; i += 1)
|
|
157
|
+
d += ` L${top[i][0].toFixed(1)} ${top[i][1].toFixed(1)}`;
|
|
158
|
+
for (let i = bottom.length - 1; i >= 0; i -= 1) {
|
|
159
|
+
d += ` L${bottom[i][0].toFixed(1)} ${bottom[i][1].toFixed(1)}`;
|
|
160
|
+
}
|
|
161
|
+
return `${d} Z`;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
AiFlowingWavesIndicator = AiFlowingWavesIndicator_1 = __decorate([
|
|
165
|
+
customElement({
|
|
166
|
+
name: 'ai-flowing-waves-indicator',
|
|
167
|
+
template: html `
|
|
168
|
+
<div class="flow" role="img" aria-label="Assistant is working">${bandLayersMarkup}</div>
|
|
169
|
+
`,
|
|
170
|
+
styles: css `
|
|
171
|
+
:host {
|
|
172
|
+
display: inline-block;
|
|
173
|
+
width: 180px;
|
|
174
|
+
height: 72px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Transparent — sits directly on the chat surface (no backdrop). 'isolation:
|
|
178
|
+
isolate' scopes the band layers' 'screen' blend to each other, so where
|
|
179
|
+
colours overlap they add toward white, while the group still composites
|
|
180
|
+
normally onto the page (works on light and dark, no wash-out). */
|
|
181
|
+
.flow {
|
|
182
|
+
position: relative;
|
|
183
|
+
width: 100%;
|
|
184
|
+
height: 100%;
|
|
185
|
+
isolation: isolate;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.layer {
|
|
189
|
+
position: absolute;
|
|
190
|
+
inset: 0;
|
|
191
|
+
width: 100%;
|
|
192
|
+
height: 100%;
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.band-layer {
|
|
197
|
+
mix-blend-mode: screen;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.band {
|
|
201
|
+
filter: drop-shadow(0 0 1.5px currentColor);
|
|
202
|
+
}
|
|
203
|
+
.band.b1 {
|
|
204
|
+
color: ${AI_COLOUR_AMBER};
|
|
205
|
+
fill: ${AI_COLOUR_AMBER};
|
|
206
|
+
}
|
|
207
|
+
.band.b2 {
|
|
208
|
+
color: ${AI_COLOUR_PINK};
|
|
209
|
+
fill: ${AI_COLOUR_PINK};
|
|
210
|
+
}
|
|
211
|
+
.band.b3 {
|
|
212
|
+
color: ${AI_COLOUR_CYAN};
|
|
213
|
+
fill: ${AI_COLOUR_CYAN};
|
|
214
|
+
}
|
|
215
|
+
.band.b4 {
|
|
216
|
+
color: ${AI_COLOUR_VIOLET};
|
|
217
|
+
fill: ${AI_COLOUR_VIOLET};
|
|
218
|
+
}
|
|
219
|
+
`,
|
|
220
|
+
})
|
|
221
|
+
], AiFlowingWavesIndicator);
|
|
222
|
+
export { AiFlowingWavesIndicator };
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { css, customElement, GenesisElement, html } from '@genesislcap/web-core';
|
|
3
|
+
import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
|
|
4
|
+
/**
|
|
5
|
+
* Animated "plasma orb" loading indicator — a fixed glowing energy sphere whose
|
|
6
|
+
* luminous filaments and swirls drift and rotate inside it, like a plasma ball.
|
|
7
|
+
* The globe never resizes; only the energy moves.
|
|
8
|
+
*
|
|
9
|
+
* Brand-coloured: a violet energy core, a slow cyan/pink tint wash, and a warm
|
|
10
|
+
* amber hot-spot — covering all four brand colours.
|
|
11
|
+
*
|
|
12
|
+
* Visual sibling of the dots loading indicator; interchangeable with the other
|
|
13
|
+
* loading styles for the "assistant is working" state.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```html
|
|
17
|
+
* <ai-plasma-orb-indicator></ai-plasma-orb-indicator>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @beta
|
|
21
|
+
*/
|
|
22
|
+
let AiPlasmaOrbIndicator = class AiPlasmaOrbIndicator extends GenesisElement {
|
|
23
|
+
};
|
|
24
|
+
AiPlasmaOrbIndicator = __decorate([
|
|
25
|
+
customElement({
|
|
26
|
+
name: 'ai-plasma-orb-indicator',
|
|
27
|
+
template: html `
|
|
28
|
+
<div class="pulse" role="img" aria-label="Assistant is working">
|
|
29
|
+
<div class="plasma">
|
|
30
|
+
<div class="swirl1"></div>
|
|
31
|
+
<div class="swirl2"></div>
|
|
32
|
+
<svg class="filaments" viewBox="0 0 100 100">
|
|
33
|
+
<g>
|
|
34
|
+
<path d="M8 50 Q50 18 92 50" />
|
|
35
|
+
<path d="M50 8 Q82 50 50 92" />
|
|
36
|
+
<path d="M18 22 Q52 54 82 80" />
|
|
37
|
+
<path d="M82 20 Q48 46 20 82" />
|
|
38
|
+
<path d="M12 64 Q50 38 88 34" />
|
|
39
|
+
<path d="M30 12 Q55 50 72 88" />
|
|
40
|
+
<path d="M14 38 Q50 60 86 62" />
|
|
41
|
+
<path d="M66 10 Q44 48 24 90" />
|
|
42
|
+
<path d="M10 28 Q56 44 90 72" />
|
|
43
|
+
<path d="M88 44 Q46 52 16 76" />
|
|
44
|
+
<path d="M40 6 Q58 50 88 86" />
|
|
45
|
+
<path d="M22 86 Q46 44 60 8" />
|
|
46
|
+
</g>
|
|
47
|
+
</svg>
|
|
48
|
+
<div class="tint"></div>
|
|
49
|
+
<div class="hot"></div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="ring-emit"></div>
|
|
52
|
+
<div class="ring-emit r2"></div>
|
|
53
|
+
</div>
|
|
54
|
+
`,
|
|
55
|
+
styles: css `
|
|
56
|
+
:host {
|
|
57
|
+
position: relative;
|
|
58
|
+
display: inline-block;
|
|
59
|
+
width: 56px;
|
|
60
|
+
height: 56px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Authored at the gallery's native 100px size and scaled down so the px
|
|
64
|
+
glow/filament geometry keeps its proportions. Absolutely centred so the
|
|
65
|
+
overflowing glow/rings don't affect the host's layout box. */
|
|
66
|
+
.pulse {
|
|
67
|
+
position: absolute;
|
|
68
|
+
top: 50%;
|
|
69
|
+
left: 50%;
|
|
70
|
+
width: 100px;
|
|
71
|
+
height: 100px;
|
|
72
|
+
transform: translate(-50%, -50%) scale(0.56);
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.plasma {
|
|
79
|
+
position: relative;
|
|
80
|
+
width: 86px;
|
|
81
|
+
height: 86px;
|
|
82
|
+
border-radius: 50%;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
isolation: isolate;
|
|
85
|
+
background: radial-gradient(
|
|
86
|
+
circle at 50% 40%,
|
|
87
|
+
#fdf2ff 0%,
|
|
88
|
+
#f3c7fd 14%,
|
|
89
|
+
#e362ff 38%,
|
|
90
|
+
${AI_COLOUR_VIOLET} 62%,
|
|
91
|
+
#8a0a9c 84%,
|
|
92
|
+
#3a0044 100%
|
|
93
|
+
);
|
|
94
|
+
box-shadow:
|
|
95
|
+
0 0 20px 5px color-mix(in srgb, ${AI_COLOUR_VIOLET}, transparent 20%),
|
|
96
|
+
0 0 48px 12px color-mix(in srgb, ${AI_COLOUR_VIOLET}, transparent 58%),
|
|
97
|
+
inset 0 0 22px rgb(255 255 255 / 22%),
|
|
98
|
+
inset 0 0 8px rgb(255 255 255 / 50%);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* bright rim light */
|
|
102
|
+
.plasma::after {
|
|
103
|
+
content: '';
|
|
104
|
+
position: absolute;
|
|
105
|
+
inset: 0;
|
|
106
|
+
border-radius: 50%;
|
|
107
|
+
z-index: 5;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
box-shadow:
|
|
110
|
+
inset 0 0 6px 1px rgb(245 220 255 / 90%),
|
|
111
|
+
inset 0 0 16px color-mix(in srgb, ${AI_COLOUR_VIOLET}, transparent 35%);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* slow colour shift: washes the orb toward cyan/pink and back */
|
|
115
|
+
.tint {
|
|
116
|
+
position: absolute;
|
|
117
|
+
inset: 0;
|
|
118
|
+
border-radius: 50%;
|
|
119
|
+
z-index: 4;
|
|
120
|
+
pointer-events: none;
|
|
121
|
+
background: radial-gradient(
|
|
122
|
+
circle at 50% 40%,
|
|
123
|
+
color-mix(in srgb, ${AI_COLOUR_CYAN}, white 35%) 0%,
|
|
124
|
+
${AI_COLOUR_CYAN} 45%,
|
|
125
|
+
${AI_COLOUR_PINK} 100%
|
|
126
|
+
);
|
|
127
|
+
mix-blend-mode: color;
|
|
128
|
+
opacity: 0;
|
|
129
|
+
animation: plasma-tint 9s ease-in-out infinite;
|
|
130
|
+
}
|
|
131
|
+
@keyframes plasma-tint {
|
|
132
|
+
0%,
|
|
133
|
+
100% {
|
|
134
|
+
opacity: 0;
|
|
135
|
+
}
|
|
136
|
+
50% {
|
|
137
|
+
opacity: 0.85;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* warm white-hot core highlight (shifts, never resizes the orb) */
|
|
142
|
+
.hot {
|
|
143
|
+
position: absolute;
|
|
144
|
+
left: 50%;
|
|
145
|
+
top: 38%;
|
|
146
|
+
width: 42px;
|
|
147
|
+
height: 36px;
|
|
148
|
+
z-index: 4;
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
filter: blur(3px);
|
|
152
|
+
background: radial-gradient(
|
|
153
|
+
circle,
|
|
154
|
+
rgb(255 255 255 / 90%) 0%,
|
|
155
|
+
color-mix(in srgb, ${AI_COLOUR_AMBER}, white 30%) 42%,
|
|
156
|
+
rgb(255 255 255 / 0%) 70%
|
|
157
|
+
);
|
|
158
|
+
animation: plasma-hot 5s ease-in-out infinite;
|
|
159
|
+
}
|
|
160
|
+
@keyframes plasma-hot {
|
|
161
|
+
0%,
|
|
162
|
+
100% {
|
|
163
|
+
opacity: 0.65;
|
|
164
|
+
transform: translate(-50%, -50%) translate(-2px, 1px);
|
|
165
|
+
}
|
|
166
|
+
50% {
|
|
167
|
+
opacity: 1;
|
|
168
|
+
transform: translate(-50%, -50%) translate(3px, -2px);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* swirling radial filaments */
|
|
173
|
+
.swirl1,
|
|
174
|
+
.swirl2 {
|
|
175
|
+
position: absolute;
|
|
176
|
+
inset: -25%;
|
|
177
|
+
border-radius: 50%;
|
|
178
|
+
z-index: 2;
|
|
179
|
+
mix-blend-mode: screen;
|
|
180
|
+
}
|
|
181
|
+
.swirl1 {
|
|
182
|
+
background: conic-gradient(
|
|
183
|
+
from 0deg,
|
|
184
|
+
transparent 0 4deg,
|
|
185
|
+
rgb(225 240 255 / 55%) 7deg,
|
|
186
|
+
transparent 11deg 58deg,
|
|
187
|
+
rgb(190 225 255 / 45%) 64deg,
|
|
188
|
+
transparent 70deg 138deg,
|
|
189
|
+
rgb(255 255 255 / 50%) 144deg,
|
|
190
|
+
transparent 150deg 220deg,
|
|
191
|
+
rgb(200 230 255 / 45%) 226deg,
|
|
192
|
+
transparent 232deg 300deg,
|
|
193
|
+
rgb(255 255 255 / 45%) 306deg,
|
|
194
|
+
transparent 312deg 360deg
|
|
195
|
+
);
|
|
196
|
+
filter: blur(3px);
|
|
197
|
+
animation: plasma-spin-cw 9s linear infinite;
|
|
198
|
+
}
|
|
199
|
+
.swirl2 {
|
|
200
|
+
background: conic-gradient(
|
|
201
|
+
from 40deg,
|
|
202
|
+
transparent 0 30deg,
|
|
203
|
+
rgb(180 215 255 / 40%) 36deg,
|
|
204
|
+
transparent 42deg 110deg,
|
|
205
|
+
rgb(255 255 255 / 40%) 116deg,
|
|
206
|
+
transparent 122deg 190deg,
|
|
207
|
+
rgb(190 225 255 / 40%) 196deg,
|
|
208
|
+
transparent 202deg 280deg,
|
|
209
|
+
rgb(255 255 255 / 40%) 286deg,
|
|
210
|
+
transparent 292deg 360deg
|
|
211
|
+
);
|
|
212
|
+
filter: blur(5px);
|
|
213
|
+
animation: plasma-spin-ccw 13s linear infinite;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* curved web filaments */
|
|
217
|
+
.filaments {
|
|
218
|
+
position: absolute;
|
|
219
|
+
inset: 0;
|
|
220
|
+
width: 100%;
|
|
221
|
+
height: 100%;
|
|
222
|
+
z-index: 3;
|
|
223
|
+
overflow: visible;
|
|
224
|
+
mix-blend-mode: screen;
|
|
225
|
+
filter: blur(0.5px);
|
|
226
|
+
}
|
|
227
|
+
.filaments g {
|
|
228
|
+
transform-box: fill-box;
|
|
229
|
+
transform-origin: center;
|
|
230
|
+
animation: plasma-spin-ccw 18s linear infinite;
|
|
231
|
+
}
|
|
232
|
+
.filaments path {
|
|
233
|
+
fill: none;
|
|
234
|
+
stroke: rgb(230 245 255 / 50%);
|
|
235
|
+
stroke-width: 0.7;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* soft light rings emitting outward from the orb */
|
|
239
|
+
.ring-emit {
|
|
240
|
+
position: absolute;
|
|
241
|
+
left: 50%;
|
|
242
|
+
top: 50%;
|
|
243
|
+
width: 86px;
|
|
244
|
+
height: 86px;
|
|
245
|
+
transform: translate(-50%, -50%);
|
|
246
|
+
border-radius: 50%;
|
|
247
|
+
border: 2px solid color-mix(in srgb, ${AI_COLOUR_VIOLET}, transparent 25%);
|
|
248
|
+
box-shadow: 0 0 8px color-mix(in srgb, ${AI_COLOUR_VIOLET}, transparent 35%);
|
|
249
|
+
pointer-events: none;
|
|
250
|
+
z-index: 0;
|
|
251
|
+
animation: plasma-emit 2.8s ease-out infinite;
|
|
252
|
+
}
|
|
253
|
+
.ring-emit.r2 {
|
|
254
|
+
animation-delay: 1.4s;
|
|
255
|
+
}
|
|
256
|
+
@keyframes plasma-emit {
|
|
257
|
+
0% {
|
|
258
|
+
transform: translate(-50%, -50%) scale(1);
|
|
259
|
+
opacity: 0.7;
|
|
260
|
+
}
|
|
261
|
+
100% {
|
|
262
|
+
transform: translate(-50%, -50%) scale(1.45);
|
|
263
|
+
opacity: 0;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@keyframes plasma-spin-cw {
|
|
268
|
+
to {
|
|
269
|
+
transform: rotate(360deg);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
@keyframes plasma-spin-ccw {
|
|
273
|
+
to {
|
|
274
|
+
transform: rotate(-360deg);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`,
|
|
278
|
+
})
|
|
279
|
+
], AiPlasmaOrbIndicator);
|
|
280
|
+
export { AiPlasmaOrbIndicator };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
var AiWavesIndicator_1;
|
|
2
|
+
import { __decorate } from "tslib";
|
|
3
|
+
import { avoidTreeShaking } from '@genesislcap/foundation-utils';
|
|
4
|
+
import { attr, css, customElement, GenesisElement, html } from '@genesislcap/web-core';
|
|
5
|
+
import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
|
|
6
|
+
import { AiHaloOverlay } from './halo-overlay';
|
|
7
|
+
const WAVES_DEFAULT_SIZE = 56;
|
|
8
|
+
/** CSS-ready form of `WAVES_DEFAULT_SIZE` (the `css` tag rejects raw numbers). */
|
|
9
|
+
const WAVES_DEFAULT_SIZE_CSS = `${WAVES_DEFAULT_SIZE}px`;
|
|
10
|
+
/** SVG coordinate space the waves are drawn in (square; the circle fills it). */
|
|
11
|
+
const VIEWBOX = 120;
|
|
12
|
+
const CENTRE = VIEWBOX / 2;
|
|
13
|
+
/** Horizontal sampling step when tracing each wave path. Lower = smoother. */
|
|
14
|
+
const SAMPLE_STEP = 6;
|
|
15
|
+
const WAVES = [
|
|
16
|
+
{
|
|
17
|
+
colour: AI_COLOUR_AMBER,
|
|
18
|
+
amplitude: 11,
|
|
19
|
+
frequency: 0.085,
|
|
20
|
+
phaseSpeed: 0.05,
|
|
21
|
+
verticalOffset: -6,
|
|
22
|
+
slosh: 6,
|
|
23
|
+
sloshFrequency: 0.018,
|
|
24
|
+
sloshSpeed: 0.021,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
colour: AI_COLOUR_PINK,
|
|
28
|
+
amplitude: 14,
|
|
29
|
+
frequency: 0.07,
|
|
30
|
+
phaseSpeed: -0.043,
|
|
31
|
+
verticalOffset: -2,
|
|
32
|
+
slosh: 7,
|
|
33
|
+
sloshFrequency: 0.022,
|
|
34
|
+
sloshSpeed: -0.017,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
colour: AI_COLOUR_CYAN,
|
|
38
|
+
amplitude: 13,
|
|
39
|
+
frequency: 0.095,
|
|
40
|
+
phaseSpeed: 0.037,
|
|
41
|
+
verticalOffset: 2,
|
|
42
|
+
slosh: 5,
|
|
43
|
+
sloshFrequency: 0.015,
|
|
44
|
+
sloshSpeed: 0.025,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
colour: AI_COLOUR_VIOLET,
|
|
48
|
+
amplitude: 10,
|
|
49
|
+
frequency: 0.06,
|
|
50
|
+
phaseSpeed: -0.055,
|
|
51
|
+
verticalOffset: 6,
|
|
52
|
+
slosh: 8,
|
|
53
|
+
sloshFrequency: 0.025,
|
|
54
|
+
sloshSpeed: -0.013,
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
const wavePathsMarkup = WAVES.map((w, i) => `<path class="wave" data-wave="${i}" stroke="${w.colour}" />`).join('');
|
|
58
|
+
/**
|
|
59
|
+
* Animated "waves inside a circle" loading indicator — coloured sine waves that
|
|
60
|
+
* slosh like glowing liquid inside a circular window, ringed by a rotating
|
|
61
|
+
* gradient halo (the same effect as `<ai-halo-overlay>`).
|
|
62
|
+
*
|
|
63
|
+
* Visual sibling of the dots loading indicator; the two are interchangeable
|
|
64
|
+
* styles of the same "assistant is working" state.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```html
|
|
68
|
+
* <ai-waves-indicator size="56"></ai-waves-indicator>
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @beta
|
|
72
|
+
*/
|
|
73
|
+
let AiWavesIndicator = AiWavesIndicator_1 = class AiWavesIndicator extends GenesisElement {
|
|
74
|
+
constructor() {
|
|
75
|
+
super(...arguments);
|
|
76
|
+
/** Diameter of the circular window in px. Default: 56. */
|
|
77
|
+
this.size = WAVES_DEFAULT_SIZE;
|
|
78
|
+
// A rAF loop drives the wave paths for the same reason `<ai-halo-overlay>`
|
|
79
|
+
// hand-drives its rotation: a pure-CSS approach can't produce per-frame sine
|
|
80
|
+
// geometry, and SMIL/`<animate>` can't express the combined travel + slosh.
|
|
81
|
+
this.frame = 0;
|
|
82
|
+
}
|
|
83
|
+
sizeChanged() {
|
|
84
|
+
this.style.setProperty('--waves-size', `${this.size}px`);
|
|
85
|
+
}
|
|
86
|
+
connectedCallback() {
|
|
87
|
+
super.connectedCallback();
|
|
88
|
+
// Guard against a reconnect starting a second concurrent loop.
|
|
89
|
+
if (this.animFrame === undefined) {
|
|
90
|
+
this.tick();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
disconnectedCallback() {
|
|
94
|
+
super.disconnectedCallback();
|
|
95
|
+
if (this.animFrame !== undefined) {
|
|
96
|
+
cancelAnimationFrame(this.animFrame);
|
|
97
|
+
this.animFrame = undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
tick() {
|
|
101
|
+
var _a, _b;
|
|
102
|
+
// Stop ticking once disconnected (belt-and-braces alongside the cancel in
|
|
103
|
+
// disconnectedCallback) so no frames are scheduled while detached.
|
|
104
|
+
if (!this.isConnected) {
|
|
105
|
+
this.animFrame = undefined;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!this.wavePaths) {
|
|
109
|
+
const paths = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.wave');
|
|
110
|
+
if (paths === null || paths === void 0 ? void 0 : paths.length)
|
|
111
|
+
this.wavePaths = Array.from(paths);
|
|
112
|
+
}
|
|
113
|
+
(_b = this.wavePaths) === null || _b === void 0 ? void 0 : _b.forEach((path, i) => {
|
|
114
|
+
path.setAttribute('d', AiWavesIndicator_1.buildWavePath(WAVES[i], this.frame));
|
|
115
|
+
});
|
|
116
|
+
this.frame += 1;
|
|
117
|
+
this.animFrame = requestAnimationFrame(() => this.tick());
|
|
118
|
+
}
|
|
119
|
+
/** Trace one wave's polyline `d` attribute for the given frame. */
|
|
120
|
+
static buildWavePath(cfg, frame) {
|
|
121
|
+
const segments = [];
|
|
122
|
+
for (let x = 0; x <= VIEWBOX; x += SAMPLE_STEP) {
|
|
123
|
+
const y = CENTRE +
|
|
124
|
+
cfg.verticalOffset +
|
|
125
|
+
cfg.amplitude * Math.sin(x * cfg.frequency + frame * cfg.phaseSpeed) +
|
|
126
|
+
cfg.slosh * Math.sin(x * cfg.sloshFrequency + frame * cfg.sloshSpeed);
|
|
127
|
+
segments.push(`${x},${y.toFixed(2)}`);
|
|
128
|
+
}
|
|
129
|
+
return `M ${segments.join(' L ')}`;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
__decorate([
|
|
133
|
+
attr({ converter: { fromView: Number, toView: String } })
|
|
134
|
+
], AiWavesIndicator.prototype, "size", void 0);
|
|
135
|
+
AiWavesIndicator = AiWavesIndicator_1 = __decorate([
|
|
136
|
+
customElement({
|
|
137
|
+
name: 'ai-waves-indicator',
|
|
138
|
+
template: html `
|
|
139
|
+
<div class="window" role="img" aria-label="Assistant is working">
|
|
140
|
+
<svg class="waves" viewBox="0 0 ${VIEWBOX} ${VIEWBOX}" preserveAspectRatio="none">
|
|
141
|
+
<defs>
|
|
142
|
+
<filter id="wave-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
143
|
+
<feGaussianBlur stdDeviation="1.6" result="blur" />
|
|
144
|
+
<feMerge>
|
|
145
|
+
<feMergeNode in="blur" />
|
|
146
|
+
<feMergeNode in="SourceGraphic" />
|
|
147
|
+
</feMerge>
|
|
148
|
+
</filter>
|
|
149
|
+
</defs>
|
|
150
|
+
<g filter="url(#wave-glow)">${wavePathsMarkup}</g>
|
|
151
|
+
</svg>
|
|
152
|
+
<ai-halo-overlay active border-size="2" glow-opacity="0.5" glow-spread="55"></ai-halo-overlay>
|
|
153
|
+
</div>
|
|
154
|
+
`,
|
|
155
|
+
styles: css `
|
|
156
|
+
:host {
|
|
157
|
+
display: inline-block;
|
|
158
|
+
width: var(--waves-size, ${WAVES_DEFAULT_SIZE_CSS});
|
|
159
|
+
height: var(--waves-size, ${WAVES_DEFAULT_SIZE_CSS});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.window {
|
|
163
|
+
position: relative;
|
|
164
|
+
width: 100%;
|
|
165
|
+
height: 100%;
|
|
166
|
+
border-radius: 50%;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
background: radial-gradient(circle at 50% 32%, #2b3140 0%, #11141c 55%, #05070c 100%);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.waves {
|
|
172
|
+
position: absolute;
|
|
173
|
+
inset: 0;
|
|
174
|
+
width: 100%;
|
|
175
|
+
height: 100%;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.wave {
|
|
179
|
+
fill: none;
|
|
180
|
+
stroke-width: 2;
|
|
181
|
+
stroke-linecap: round;
|
|
182
|
+
stroke-linejoin: round;
|
|
183
|
+
}
|
|
184
|
+
`,
|
|
185
|
+
})
|
|
186
|
+
], AiWavesIndicator);
|
|
187
|
+
export { AiWavesIndicator };
|
|
188
|
+
// Ensure the halo overlay used for the ring is registered alongside this component.
|
|
189
|
+
avoidTreeShaking(AiHaloOverlay);
|