codebay 0.10.1 → 0.11.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/.mochi/manifest.json +196 -170
- package/.mochi/svelte-client/HydratableIsland-dammyce5.js +1 -0
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-v65ctd99.js +2 -0
- package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-htjfv8q9.js +14 -0
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-v07seqge.js +1 -0
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-hj9yvqc4.js +38 -0
- package/.mochi/svelte-client/chunk-0zkdsrth.js +2 -0
- package/.mochi/svelte-client/{chunk-bn731rda.js → chunk-566rqq4c.js} +2 -2
- package/.mochi/svelte-client/chunk-79aj59e0.js +2 -0
- package/.mochi/svelte-client/{chunk-wvtkwfsh.js → chunk-czkxd74n.js} +1 -1
- package/.mochi/svelte-client/{chunk-p20tkpde.js → chunk-hxydjtda.js} +1 -1
- package/.mochi/svelte-client/chunk-qx1defbm.js +2 -0
- package/.mochi/svelte-client/chunk-r3wxmmc0.js +2 -0
- package/.mochi/svelte-client/{chunk-j72r8pkk.js → chunk-vs6pd2b4.js} +2 -2
- package/.mochi/svelte-client/{chunk-t0ap7q89.js → chunk-wjagstre.js} +1 -1
- package/.mochi/svelte-client/{chunk-0fjx3z3r.js → chunk-y9yy1004.js} +1 -1
- package/.mochi/svelte-compile/{App-59cdj86f.server.js → App-mzn3dqjm.server.js} +182 -72
- package/.mochi/svelte-compile/{Avatars-fmagfyj2.server.js → Avatars-c0k5jx8q.server.js} +3 -6
- package/.mochi/svelte-compile/{Instance-s1gqwzmx.server.js → Instance-hj76qnha.server.js} +4 -4
- package/.mochi/svelte-compile/{Settings-k447mt0x.server.js → Settings-tvc5yp08.server.js} +39 -28
- package/.mochi/svelte-compile/chunk-ed46m58e.js +22 -0
- package/.mochi/svelte-compile/{chunk-th2dbqvc.js → chunk-gz6hv1h5.js} +5 -1
- package/.mochi/svelte-compile/chunk-zd49axkf.js +65 -0
- package/.mochi/svelte-css/{AppBar-3bxinv4pabv8l.css → AppBar-1gtu2me3fni8e.css} +1 -1
- package/.mochi/svelte-css/AvatarEditor-1mjvec6bwdkv8.css +1 -0
- package/.mochi/svelte-css/{AvatarsView-2llo48w0wt0xn.css → AvatarsView-26ya4ep5n1ze7.css} +1 -1
- package/.mochi/svelte-css/Button-208u50wkms8ya.css +1 -0
- package/.mochi/svelte-css/CoinButton-2727devjtsn10.css +1 -0
- package/.mochi/svelte-css/CredMenu-7qowd1oxujet.css +1 -0
- package/.mochi/svelte-css/DashboardView-17a0b8epjurhb.css +1 -0
- package/.mochi/svelte-css/FolderBrowser-i0uj58dph27f.css +1 -0
- package/.mochi/svelte-css/HealthBox-r0giuhjn2wj4.css +1 -0
- package/.mochi/svelte-css/IdeBar-1mtgemqfuvqzi.css +1 -0
- package/.mochi/svelte-css/InstanceCard-25z7nas7y98zk.css +1 -0
- package/.mochi/svelte-css/InstanceFilter-2vglmyy0yrbbc.css +1 -0
- package/.mochi/svelte-css/InstanceView-32j12hyg8v9u0.css +1 -0
- package/.mochi/svelte-css/SettingsCog-1jdeprd3ilczz.css +1 -0
- package/.mochi/svelte-css/{SettingsView-n3oy75uonzp2.css → SettingsView-bccw34agd2bj.css} +1 -1
- package/.mochi/svelte-css/StatusBadge-2eisdtnrsnfdc.css +1 -0
- package/.mochi/svelte-css/TerminalPane-2ywc86rhtmr9l.css +1 -0
- package/.mochi/svelte-css/ThemePicker-3fw4synubier0.css +1 -0
- package/migrations/0008_config_migrated.sql +1 -0
- package/package.json +8 -8
- package/src/components/AppBar.svelte +2 -2
- package/src/components/AppShell.svelte +71 -7
- package/src/components/AvatarEditor.svelte +10 -10
- package/src/components/AvatarsView.svelte +0 -0
- package/src/components/Button.svelte +13 -9
- package/src/components/CoinButton.svelte +2 -2
- package/src/components/CredMenu.svelte +8 -6
- package/src/components/DashboardView.svelte +2 -2
- package/src/components/FolderBrowser.svelte +39 -36
- package/src/components/HealthBox.svelte +5 -4
- package/src/components/IdeBar.svelte +286 -112
- package/src/components/InstanceCard.svelte +7 -7
- package/src/components/InstanceFilter.svelte +6 -6
- package/src/components/InstanceView.svelte +28 -22
- package/src/components/SettingsCog.svelte +4 -3
- package/src/components/SettingsView.svelte +7 -11
- package/src/components/StatusBadge.svelte +8 -6
- package/src/components/TerminalPane.svelte +19 -7
- package/src/components/ThemePicker.svelte +150 -8
- package/src/components/TopBar.svelte +1 -1
- package/src/components/UiShowcase.svelte +303 -64
- package/src/lib/db.server.ts +7 -3
- package/src/lib/devcontainer.server.ts +187 -33
- package/src/lib/docker.server.ts +10 -0
- package/src/lib/git.server.ts +28 -0
- package/src/lib/instances.server.ts +49 -6
- package/src/lib/tab-nav.ts +30 -0
- package/src/routes.ts +12 -0
- package/src/shell.html +60 -23
- package/src/theme.ts +8 -1
- package/src/toast.ts +9 -0
- package/src/types.ts +7 -0
- package/.mochi/svelte-client/HydratableIsland-aebn1fa1.js +0 -1
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-rx6arrv5.js +0 -2
- package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-jpjdmgpe.js +0 -14
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-c9y47dat.js +0 -1
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-d61feq89.js +0 -38
- package/.mochi/svelte-client/chunk-8xvr02wh.js +0 -2
- package/.mochi/svelte-client/chunk-9143wy3j.js +0 -2
- package/.mochi/svelte-client/chunk-k37wj1jm.js +0 -2
- package/.mochi/svelte-client/chunk-pc0fm37c.js +0 -2
- package/.mochi/svelte-compile/chunk-3t7v4hk0.js +0 -52
- package/.mochi/svelte-compile/chunk-gkjgybb2.js +0 -15
- package/.mochi/svelte-css/AvatarEditor-2mdq8ss346drw.css +0 -1
- package/.mochi/svelte-css/Button-oharop2xz6am.css +0 -1
- package/.mochi/svelte-css/CoinButton-1rej3sh3tkjud.css +0 -1
- package/.mochi/svelte-css/CredMenu-9hwgeptp7crb.css +0 -1
- package/.mochi/svelte-css/DashboardView-1cy4xpqhja9sx.css +0 -1
- package/.mochi/svelte-css/FolderBrowser-2pxp9z1o7zixk.css +0 -1
- package/.mochi/svelte-css/HealthBox-er2her0jukwl.css +0 -1
- package/.mochi/svelte-css/IdeBar-3bgr3m5z0lpeg.css +0 -1
- package/.mochi/svelte-css/InstanceCard-3vnltxnbxkvay.css +0 -1
- package/.mochi/svelte-css/InstanceFilter-3q6x0jt0hcgpz.css +0 -1
- package/.mochi/svelte-css/InstanceView-1pd7j16bd2b16.css +0 -1
- package/.mochi/svelte-css/SettingsCog-jftyadvo64qo.css +0 -1
- package/.mochi/svelte-css/StatusBadge-3mq0j7z6aq4j.css +0 -1
- package/.mochi/svelte-css/TerminalPane-3bwgk6ulzq6b3.css +0 -1
- package/.mochi/svelte-css/ThemePicker-1ir8jyocm8mvi.css +0 -1
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import Boxes from '@lucide/svelte/icons/boxes';
|
|
24
24
|
import Puzzle from '@lucide/svelte/icons/puzzle';
|
|
25
25
|
import { Toaster } from 'svelte-french-toast';
|
|
26
|
+
import { TOAST_OPTIONS } from '../toast.ts';
|
|
26
27
|
import { flushSync } from 'svelte';
|
|
27
28
|
import { enhance } from 'mochi-framework';
|
|
28
29
|
import type { MochiEnhanceOptions } from 'mochi-framework';
|
|
@@ -2189,12 +2190,7 @@
|
|
|
2189
2190
|
</div>
|
|
2190
2191
|
|
|
2191
2192
|
<!-- Settings renders outside AppShell, which hosts the app-wide Toaster. -->
|
|
2192
|
-
<Toaster
|
|
2193
|
-
toastOptions={{
|
|
2194
|
-
style:
|
|
2195
|
-
'border:1px solid var(--ink); background:var(--bg-card); color:var(--ink); box-shadow:4px 4px 0 var(--ink); font-family:var(--font-mono); font-size:13px;'
|
|
2196
|
-
}}
|
|
2197
|
-
/>
|
|
2193
|
+
<Toaster toastOptions={TOAST_OPTIONS} />
|
|
2198
2194
|
|
|
2199
2195
|
<style>
|
|
2200
2196
|
.page {
|
|
@@ -2437,7 +2433,7 @@
|
|
|
2437
2433
|
.mode-toggle {
|
|
2438
2434
|
display: flex;
|
|
2439
2435
|
flex: none;
|
|
2440
|
-
border: 1px solid var(--
|
|
2436
|
+
border: 1px solid var(--edge);
|
|
2441
2437
|
}
|
|
2442
2438
|
.mode-btn {
|
|
2443
2439
|
display: inline-flex;
|
|
@@ -2455,11 +2451,11 @@
|
|
|
2455
2451
|
cursor: pointer;
|
|
2456
2452
|
}
|
|
2457
2453
|
.mode-btn + .mode-btn {
|
|
2458
|
-
border-left: 1px solid var(--
|
|
2454
|
+
border-left: 1px solid var(--edge);
|
|
2459
2455
|
}
|
|
2460
2456
|
.mode-btn.active {
|
|
2461
|
-
background: var(--
|
|
2462
|
-
color: var(--
|
|
2457
|
+
background: var(--fill);
|
|
2458
|
+
color: var(--fill-ink);
|
|
2463
2459
|
}
|
|
2464
2460
|
.mode-btn:disabled {
|
|
2465
2461
|
cursor: not-allowed;
|
|
@@ -2589,7 +2585,7 @@
|
|
|
2589
2585
|
border-color: var(--danger);
|
|
2590
2586
|
}
|
|
2591
2587
|
.var-remove:disabled {
|
|
2592
|
-
opacity:
|
|
2588
|
+
opacity: var(--dim);
|
|
2593
2589
|
cursor: not-allowed;
|
|
2594
2590
|
}
|
|
2595
2591
|
.var-empty {
|
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
text-transform: uppercase;
|
|
23
23
|
letter-spacing: 0.1em;
|
|
24
24
|
padding: 3px 7px;
|
|
25
|
-
border: 1px solid var(--
|
|
25
|
+
border: 1px solid var(--edge);
|
|
26
26
|
white-space: nowrap;
|
|
27
27
|
}
|
|
28
28
|
.status.running {
|
|
29
|
-
background: var(--
|
|
30
|
-
color: var(--
|
|
29
|
+
background: var(--fill);
|
|
30
|
+
color: var(--fill-ink);
|
|
31
|
+
border-color: var(--fill);
|
|
31
32
|
}
|
|
32
33
|
.status.stopped {
|
|
33
34
|
background: transparent;
|
|
@@ -35,8 +36,9 @@
|
|
|
35
36
|
border-color: var(--ink-faint);
|
|
36
37
|
}
|
|
37
38
|
.status.creating {
|
|
38
|
-
background: var(--
|
|
39
|
-
color: var(--
|
|
39
|
+
background: var(--fill);
|
|
40
|
+
color: var(--fill-ink);
|
|
41
|
+
border-color: var(--fill);
|
|
40
42
|
animation: lcd-blink 1.1s steps(1) infinite;
|
|
41
43
|
}
|
|
42
44
|
.status.error {
|
|
@@ -48,7 +50,7 @@
|
|
|
48
50
|
@keyframes lcd-blink {
|
|
49
51
|
50% {
|
|
50
52
|
background: transparent;
|
|
51
|
-
color: var(--
|
|
53
|
+
color: var(--fill);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
</style>
|
|
@@ -129,16 +129,24 @@
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/* Reads the *resolved* background/color off `.term` rather than the tokens behind
|
|
133
|
+
them: getPropertyValue('--screen-bg') hands back the literal "light-dark(…)"
|
|
134
|
+
token stream, which xterm can't parse and silently drops on the floor. */
|
|
132
135
|
function applyTheme() {
|
|
133
|
-
if (!term) return;
|
|
134
|
-
const cs = getComputedStyle(
|
|
135
|
-
const bg = cs.
|
|
136
|
-
const fg = cs.
|
|
136
|
+
if (!term || !el) return;
|
|
137
|
+
const cs = getComputedStyle(el);
|
|
138
|
+
const bg = cs.backgroundColor || '#0d0e0a';
|
|
139
|
+
const fg = cs.color || '#d8d9cf';
|
|
137
140
|
term.options.theme = { background: bg, foreground: fg, cursor: fg, cursorAccent: bg };
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
onMount(() => {
|
|
141
144
|
let themeObs: MutationObserver | undefined;
|
|
145
|
+
// In "auto" there is no data-theme attribute to mutate, so the OS preference
|
|
146
|
+
// flipping is the only signal the mutation observer below would never see.
|
|
147
|
+
const scheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
148
|
+
const onScheme = () => applyTheme();
|
|
149
|
+
scheme.addEventListener('change', onScheme);
|
|
142
150
|
(async () => {
|
|
143
151
|
const [{ Terminal }, { FitAddon }] = await Promise.all([
|
|
144
152
|
import('@xterm/xterm'),
|
|
@@ -178,6 +186,7 @@
|
|
|
178
186
|
disposed = true;
|
|
179
187
|
sizeObs.disconnect();
|
|
180
188
|
themeObs?.disconnect();
|
|
189
|
+
scheme.removeEventListener('change', onScheme);
|
|
181
190
|
if (retry) clearTimeout(retry);
|
|
182
191
|
try {
|
|
183
192
|
ws?.close();
|
|
@@ -212,10 +221,13 @@
|
|
|
212
221
|
</div>
|
|
213
222
|
|
|
214
223
|
<style>
|
|
224
|
+
/* Also the source `applyTheme` reads xterm's colors back out of — resolved
|
|
225
|
+
standard properties, since a custom property's own value is unresolved. */
|
|
215
226
|
.term {
|
|
216
227
|
width: 100%;
|
|
217
228
|
height: 100%;
|
|
218
|
-
background: var(--bg);
|
|
229
|
+
background: var(--screen-bg);
|
|
230
|
+
color: var(--screen-ink);
|
|
219
231
|
padding: 6px 8px;
|
|
220
232
|
box-sizing: border-box;
|
|
221
233
|
}
|
|
@@ -263,8 +275,8 @@
|
|
|
263
275
|
.overlay :global(button:focus-visible),
|
|
264
276
|
.overlay :global(button[aria-pressed='true']) {
|
|
265
277
|
opacity: 1;
|
|
266
|
-
background: var(--
|
|
267
|
-
color: var(--
|
|
278
|
+
background: var(--fill);
|
|
279
|
+
color: var(--fill-ink);
|
|
268
280
|
}
|
|
269
281
|
@media (prefers-reduced-motion: reduce) {
|
|
270
282
|
.overlay :global(button) {
|
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
import Moon from '@lucide/svelte/icons/moon';
|
|
4
4
|
import Monitor from '@lucide/svelte/icons/monitor';
|
|
5
5
|
import { getTheme, setTheme, applyTheme, type Theme } from '../theme.ts';
|
|
6
|
+
import { apiPost } from '../api.ts';
|
|
6
7
|
|
|
7
8
|
let theme: Theme = $state(getTheme());
|
|
9
|
+
/** Which half of the CRT power-cycle is playing; null when idle. */
|
|
10
|
+
let phase = $state<'collapse' | 'restore' | null>(null);
|
|
11
|
+
|
|
12
|
+
// Must match the CSS below: the theme swap has to land while the tube is dark.
|
|
13
|
+
const COLLAPSE_MS = 320;
|
|
14
|
+
const RESTORE_MS = 380;
|
|
8
15
|
|
|
9
16
|
const options: { value: Theme; label: string; icon: typeof Sun }[] = [
|
|
10
17
|
{ value: 'auto', label: 'Auto', icon: Monitor },
|
|
@@ -12,10 +19,29 @@
|
|
|
12
19
|
{ value: 'dark', label: 'Dark', icon: Moon }
|
|
13
20
|
];
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
|
|
24
|
+
async function select(value: Theme) {
|
|
25
|
+
if (value === theme || phase) return;
|
|
26
|
+
// Persist first: an interrupted transition still lands the choice.
|
|
17
27
|
setTheme(value);
|
|
28
|
+
// Settings is a popup, so the tabs behind it only repaint if the stream tells them to.
|
|
29
|
+
void apiPost('/api/settings/theme', { value }).catch(() => {});
|
|
30
|
+
|
|
31
|
+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
32
|
+
theme = value;
|
|
33
|
+
applyTheme(value);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
phase = 'collapse';
|
|
38
|
+
await wait(COLLAPSE_MS);
|
|
39
|
+
// Swapped behind the black, so the repaint itself is never on screen.
|
|
40
|
+
theme = value;
|
|
18
41
|
applyTheme(value);
|
|
42
|
+
phase = 'restore';
|
|
43
|
+
await wait(RESTORE_MS);
|
|
44
|
+
phase = null;
|
|
19
45
|
}
|
|
20
46
|
</script>
|
|
21
47
|
|
|
@@ -35,11 +61,19 @@
|
|
|
35
61
|
{/each}
|
|
36
62
|
</div>
|
|
37
63
|
|
|
64
|
+
{#if phase}
|
|
65
|
+
<div class="crt" class:collapse={phase === 'collapse'} class:restore={phase === 'restore'}>
|
|
66
|
+
<span class="bar top"></span>
|
|
67
|
+
<span class="bar bottom"></span>
|
|
68
|
+
<span class="line"></span>
|
|
69
|
+
</div>
|
|
70
|
+
{/if}
|
|
71
|
+
|
|
38
72
|
<style>
|
|
39
73
|
.picker {
|
|
40
74
|
display: inline-flex;
|
|
41
75
|
flex: none;
|
|
42
|
-
border: 1px solid var(--
|
|
76
|
+
border: 1px solid var(--edge);
|
|
43
77
|
background: var(--bg-card);
|
|
44
78
|
}
|
|
45
79
|
.segment {
|
|
@@ -58,20 +92,128 @@
|
|
|
58
92
|
cursor: pointer;
|
|
59
93
|
}
|
|
60
94
|
.segment + .segment {
|
|
61
|
-
border-left: 1px solid var(--
|
|
95
|
+
border-left: 1px solid var(--edge);
|
|
62
96
|
}
|
|
63
97
|
.segment:hover:not(.active) {
|
|
64
|
-
background: var(--
|
|
65
|
-
color: var(--
|
|
98
|
+
background: var(--fill);
|
|
99
|
+
color: var(--fill-ink);
|
|
66
100
|
}
|
|
67
101
|
.segment.active {
|
|
68
102
|
font-weight: 700;
|
|
69
|
-
background: var(--
|
|
70
|
-
color: var(--
|
|
103
|
+
background: var(--fill);
|
|
104
|
+
color: var(--fill-ink);
|
|
71
105
|
cursor: default;
|
|
72
106
|
}
|
|
73
107
|
.segment:focus-visible {
|
|
74
108
|
outline: 2px solid var(--ink);
|
|
75
109
|
outline-offset: -2px;
|
|
76
110
|
}
|
|
111
|
+
|
|
112
|
+
/* Switching themes power-cycles the monitor. `position: fixed` is viewport-relative
|
|
113
|
+
only while no ancestor is transformed/filtered — giving one of Settings' wrappers
|
|
114
|
+
a transform would re-root this to that ancestor instead. */
|
|
115
|
+
.crt {
|
|
116
|
+
position: fixed;
|
|
117
|
+
inset: 0;
|
|
118
|
+
z-index: 9999;
|
|
119
|
+
pointer-events: none;
|
|
120
|
+
}
|
|
121
|
+
/* Black closing in from both edges, squeezing the picture toward the centre —
|
|
122
|
+
the deflection yoke losing the vertical sweep. */
|
|
123
|
+
.bar {
|
|
124
|
+
position: absolute;
|
|
125
|
+
left: 0;
|
|
126
|
+
right: 0;
|
|
127
|
+
height: 50%;
|
|
128
|
+
background: #000;
|
|
129
|
+
}
|
|
130
|
+
.bar.top {
|
|
131
|
+
top: 0;
|
|
132
|
+
transform-origin: top center;
|
|
133
|
+
}
|
|
134
|
+
.bar.bottom {
|
|
135
|
+
bottom: 0;
|
|
136
|
+
transform-origin: bottom center;
|
|
137
|
+
}
|
|
138
|
+
/* Tracks --screen-ink, so the tube warms back up in the phosphor colour of
|
|
139
|
+
whichever theme it is coming back as. */
|
|
140
|
+
.line {
|
|
141
|
+
position: absolute;
|
|
142
|
+
top: 50%;
|
|
143
|
+
left: 0;
|
|
144
|
+
right: 0;
|
|
145
|
+
height: 2px;
|
|
146
|
+
margin-top: -1px;
|
|
147
|
+
background: var(--screen-ink);
|
|
148
|
+
box-shadow: 0 0 14px 3px var(--screen-ink);
|
|
149
|
+
opacity: 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.crt.collapse .bar {
|
|
153
|
+
animation: crt-collapse 200ms cubic-bezier(0.7, 0, 0.84, 0) forwards;
|
|
154
|
+
}
|
|
155
|
+
.crt.collapse .line {
|
|
156
|
+
animation: crt-line-out 320ms linear forwards;
|
|
157
|
+
}
|
|
158
|
+
/* Held closed through the delay so the line can bloom before the picture opens. */
|
|
159
|
+
.crt.restore .bar {
|
|
160
|
+
animation: crt-open 260ms cubic-bezier(0.16, 1, 0.3, 1) 120ms both;
|
|
161
|
+
}
|
|
162
|
+
.crt.restore .line {
|
|
163
|
+
animation: crt-line-in 380ms linear both;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@keyframes crt-collapse {
|
|
167
|
+
from {
|
|
168
|
+
transform: scaleY(0);
|
|
169
|
+
}
|
|
170
|
+
to {
|
|
171
|
+
transform: scaleY(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
@keyframes crt-open {
|
|
175
|
+
from {
|
|
176
|
+
transform: scaleY(1);
|
|
177
|
+
}
|
|
178
|
+
to {
|
|
179
|
+
transform: scaleY(0);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/* Picture gone: the residual charge flares as a line, pinches to a dot, dies. */
|
|
183
|
+
@keyframes crt-line-out {
|
|
184
|
+
0%,
|
|
185
|
+
55% {
|
|
186
|
+
opacity: 0;
|
|
187
|
+
transform: scaleX(1);
|
|
188
|
+
}
|
|
189
|
+
62%,
|
|
190
|
+
75% {
|
|
191
|
+
opacity: 1;
|
|
192
|
+
transform: scaleX(1);
|
|
193
|
+
}
|
|
194
|
+
100% {
|
|
195
|
+
opacity: 0;
|
|
196
|
+
transform: scaleX(0.015);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/* Dot blooms back into a line, then the picture opens around it. */
|
|
200
|
+
@keyframes crt-line-in {
|
|
201
|
+
0% {
|
|
202
|
+
opacity: 0;
|
|
203
|
+
transform: scaleX(0.015);
|
|
204
|
+
}
|
|
205
|
+
12% {
|
|
206
|
+
opacity: 1;
|
|
207
|
+
transform: scaleX(0.015);
|
|
208
|
+
}
|
|
209
|
+
32%,
|
|
210
|
+
55% {
|
|
211
|
+
opacity: 1;
|
|
212
|
+
transform: scaleX(1);
|
|
213
|
+
}
|
|
214
|
+
100% {
|
|
215
|
+
opacity: 0;
|
|
216
|
+
transform: scaleX(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
77
219
|
</style>
|