dreamboard 0.1.25 → 0.1.26
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/{chunk-H3O43F5P.js → chunk-3JGO32FA.js} +1229 -348
- package/dist/chunk-3JGO32FA.js.map +1 -0
- package/dist/{chunk-MG6EVR7P.js → chunk-VRJ4FOH2.js} +1898 -519
- package/dist/chunk-VRJ4FOH2.js.map +1 -0
- package/dist/{chunk-2RCUHMGL.js → chunk-YI5DUODD.js} +191 -61
- package/dist/chunk-YI5DUODD.js.map +1 -0
- package/dist/dev-host/dev-api-proxy-plugin.ts +27 -1
- package/dist/dev-host/dev-author-dom-warnings.ts +101 -0
- package/dist/dev-host/dev-hmr-guard-plugin.ts +47 -0
- package/dist/dev-host/dev-host-controller.ts +21 -5
- package/dist/dev-host/host-main.css +131 -89
- package/dist/dev-host/host-main.tsx +320 -136
- package/dist/dev-host/plugin-main.ts +9 -3
- package/dist/dev-host/start-dev-server.ts +2 -0
- package/dist/{dist-FRURQI7Q.js → dist-JNA2CLKS.js} +2 -2
- package/dist/{dist-FEPN3BDN.js → dist-JO2OGKJD.js} +35 -15
- package/dist/dist-JO2OGKJD.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/internal.js +142 -53
- package/dist/internal.js.map +1 -1
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +20 -9
- package/package.json +3 -3
- package/dist/chunk-2RCUHMGL.js.map +0 -1
- package/dist/chunk-H3O43F5P.js.map +0 -1
- package/dist/chunk-MG6EVR7P.js.map +0 -1
- package/dist/dist-FEPN3BDN.js.map +0 -1
- /package/dist/{dist-FRURQI7Q.js.map → dist-JNA2CLKS.js.map} +0 -0
|
@@ -31,8 +31,13 @@ import type { Plugin } from "vite";
|
|
|
31
31
|
import {
|
|
32
32
|
DEFAULT_REFRESH_WINDOW_MS,
|
|
33
33
|
ensureFreshAccessToken,
|
|
34
|
+
getAccessTokenExpiry,
|
|
34
35
|
} from "../auth/refresh-coordinator.js";
|
|
35
36
|
import { PermanentRefreshError } from "../auth/refresh-error.js";
|
|
37
|
+
import {
|
|
38
|
+
repairLocalDevSession,
|
|
39
|
+
shouldRepairLocalDevSession,
|
|
40
|
+
} from "../auth/local-dev-session.js";
|
|
36
41
|
import type { ResolvedConfig } from "../types.js";
|
|
37
42
|
|
|
38
43
|
export type ResolvedBearerOk = {
|
|
@@ -259,11 +264,32 @@ export async function resolveDevBearer(
|
|
|
259
264
|
case "unchanged":
|
|
260
265
|
case "rotated":
|
|
261
266
|
return { kind: "ok", token: result.credentials.accessToken };
|
|
262
|
-
case "transient_failure":
|
|
267
|
+
case "transient_failure": {
|
|
268
|
+
const expiry = getAccessTokenExpiry(config.authToken);
|
|
269
|
+
const isExpired = expiry !== null && expiry.getTime() <= Date.now();
|
|
270
|
+
if (isExpired) {
|
|
271
|
+
if (shouldRepairLocalDevSession(config)) {
|
|
272
|
+
const repaired = await repairLocalDevSession();
|
|
273
|
+
if (repaired) {
|
|
274
|
+
return { kind: "ok", token: repaired.accessToken };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
kind: "permanent_invalid",
|
|
279
|
+
message: `Access token refresh failed: ${result.message}. Run \`dreamboard login\` to authenticate again.`,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
263
282
|
return { kind: "ok", token: config.authToken ?? null };
|
|
283
|
+
}
|
|
264
284
|
}
|
|
265
285
|
} catch (err) {
|
|
266
286
|
if (err instanceof PermanentRefreshError) {
|
|
287
|
+
if (shouldRepairLocalDevSession(config)) {
|
|
288
|
+
const repaired = await repairLocalDevSession();
|
|
289
|
+
if (repaired) {
|
|
290
|
+
return { kind: "ok", token: repaired.accessToken };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
267
293
|
return { kind: "permanent_invalid", message: err.message };
|
|
268
294
|
}
|
|
269
295
|
throw err;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
type ZoneItemElement = Pick<Element, "getAttribute" | "querySelector">;
|
|
2
|
+
|
|
3
|
+
type ZoneItemRoot = Pick<
|
|
4
|
+
ParentNode,
|
|
5
|
+
"querySelectorAll"
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
export interface PlayableZoneItemWarning {
|
|
9
|
+
key: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PLAYABLE_ZONE_ITEM_SELECTOR =
|
|
14
|
+
'[data-dreamboard-zone-item][data-playable="true"]';
|
|
15
|
+
const INTERACTION_CARD_INPUT_SELECTOR =
|
|
16
|
+
"[data-dreamboard-interaction-card-input]";
|
|
17
|
+
|
|
18
|
+
export function collectPlayableZoneItemWarnings(
|
|
19
|
+
root: ZoneItemRoot,
|
|
20
|
+
): PlayableZoneItemWarning[] {
|
|
21
|
+
const warnings: PlayableZoneItemWarning[] = [];
|
|
22
|
+
for (const item of root.querySelectorAll(PLAYABLE_ZONE_ITEM_SELECTOR)) {
|
|
23
|
+
const element = item as ZoneItemElement;
|
|
24
|
+
if (element.querySelector(INTERACTION_CARD_INPUT_SELECTOR)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const zone = element.getAttribute("data-zone") ?? "unknown";
|
|
29
|
+
const cardId = element.getAttribute("data-card-id") ?? "unknown";
|
|
30
|
+
const cardType = element.getAttribute("data-card-type") ?? null;
|
|
31
|
+
const key = `${zone}:${cardId}`;
|
|
32
|
+
const cardLabel =
|
|
33
|
+
cardType && cardType !== cardId
|
|
34
|
+
? `'${cardId}' (${cardType})`
|
|
35
|
+
: `'${cardId}'`;
|
|
36
|
+
warnings.push({
|
|
37
|
+
key,
|
|
38
|
+
message: [
|
|
39
|
+
`[dreamboard] Playable card ${cardLabel} in zone '${zone}' rendered without an interaction card input.`,
|
|
40
|
+
"This usually means the UI rendered a raw Card/custom tile instead of <handSurface.Card>.",
|
|
41
|
+
"Render the surface card consistently and let Dreamboard disable unavailable interactions.",
|
|
42
|
+
].join(" "),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return warnings;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function installPlayableZoneItemWarnings(options: {
|
|
49
|
+
root?: Document;
|
|
50
|
+
warn?: (...args: unknown[]) => void;
|
|
51
|
+
MutationObserverImpl?: typeof MutationObserver;
|
|
52
|
+
schedule?: (callback: () => void) => void;
|
|
53
|
+
} = {}): () => void {
|
|
54
|
+
const root = options.root ?? document;
|
|
55
|
+
const warn = options.warn ?? console.warn.bind(console);
|
|
56
|
+
const MutationObserverImpl =
|
|
57
|
+
options.MutationObserverImpl ?? window.MutationObserver;
|
|
58
|
+
const schedule =
|
|
59
|
+
options.schedule ??
|
|
60
|
+
((callback) => {
|
|
61
|
+
window.requestAnimationFrame(callback);
|
|
62
|
+
});
|
|
63
|
+
const emitted = new Set<string>();
|
|
64
|
+
let scheduled = false;
|
|
65
|
+
|
|
66
|
+
const scan = () => {
|
|
67
|
+
scheduled = false;
|
|
68
|
+
for (const warning of collectPlayableZoneItemWarnings(root)) {
|
|
69
|
+
if (emitted.has(warning.key)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
emitted.add(warning.key);
|
|
73
|
+
warn(warning.message);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const requestScan = () => {
|
|
78
|
+
if (scheduled) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
scheduled = true;
|
|
82
|
+
schedule(scan);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const observer = new MutationObserverImpl(requestScan);
|
|
86
|
+
observer.observe(root.documentElement, {
|
|
87
|
+
childList: true,
|
|
88
|
+
subtree: true,
|
|
89
|
+
attributes: true,
|
|
90
|
+
attributeFilter: [
|
|
91
|
+
"data-dreamboard-zone-item",
|
|
92
|
+
"data-playable",
|
|
93
|
+
"data-dreamboard-interaction-card-input",
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
requestScan();
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
observer.disconnect();
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { Plugin } from "vite";
|
|
3
|
+
|
|
4
|
+
export function createDevHmrGuardPlugin(options: {
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
}): Plugin {
|
|
7
|
+
return {
|
|
8
|
+
name: "dreamboard-dev-hmr-guard",
|
|
9
|
+
handleHotUpdate(context) {
|
|
10
|
+
if (shouldHandleProjectHotUpdate(options.projectRoot, context.file)) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
context.server.config.logger.info(
|
|
15
|
+
`[dreamboard] ignored HMR outside project: ${path.relative(
|
|
16
|
+
options.projectRoot,
|
|
17
|
+
context.file,
|
|
18
|
+
)}`,
|
|
19
|
+
);
|
|
20
|
+
return [];
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function shouldHandleProjectHotUpdate(
|
|
26
|
+
projectRoot: string,
|
|
27
|
+
file: string,
|
|
28
|
+
): boolean {
|
|
29
|
+
const normalizedProjectRoot = path.resolve(projectRoot);
|
|
30
|
+
const normalizedFile = path.resolve(file);
|
|
31
|
+
const relativePath = path.relative(normalizedProjectRoot, normalizedFile);
|
|
32
|
+
const isInsideProject =
|
|
33
|
+
relativePath === "" ||
|
|
34
|
+
(relativePath.length > 0 &&
|
|
35
|
+
!relativePath.startsWith("..") &&
|
|
36
|
+
!path.isAbsolute(relativePath));
|
|
37
|
+
|
|
38
|
+
if (!isInsideProject) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const pathSegments = relativePath.split(path.sep);
|
|
43
|
+
return (
|
|
44
|
+
!pathSegments.includes(".dreamboard") &&
|
|
45
|
+
!pathSegments.includes("node_modules")
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -183,7 +183,7 @@ export class DevHostController {
|
|
|
183
183
|
if (preferredPlayerId) {
|
|
184
184
|
this.logger.warn(
|
|
185
185
|
"[DevHost] Failed to bootstrap the requested player selection; retrying with the default player:",
|
|
186
|
-
|
|
186
|
+
formatErrorForLog(error),
|
|
187
187
|
);
|
|
188
188
|
this.storage.persistPreferredPlayerId(null);
|
|
189
189
|
try {
|
|
@@ -198,7 +198,13 @@ export class DevHostController {
|
|
|
198
198
|
}
|
|
199
199
|
this.logger.error(
|
|
200
200
|
"[DevHost] Failed to bootstrap the backend session:",
|
|
201
|
-
|
|
201
|
+
formatErrorForLog(error),
|
|
202
|
+
);
|
|
203
|
+
this.setRuntimeError(
|
|
204
|
+
convertProblemDetailsToRuntimeError(
|
|
205
|
+
error,
|
|
206
|
+
"Failed to bootstrap the backend session.",
|
|
207
|
+
),
|
|
202
208
|
);
|
|
203
209
|
}
|
|
204
210
|
this.notify();
|
|
@@ -262,7 +268,7 @@ export class DevHostController {
|
|
|
262
268
|
);
|
|
263
269
|
this.logger.error(
|
|
264
270
|
"[DevHost] Failed to start the backend session:",
|
|
265
|
-
|
|
271
|
+
formatErrorForLog(error),
|
|
266
272
|
);
|
|
267
273
|
}
|
|
268
274
|
}
|
|
@@ -613,7 +619,7 @@ export class DevHostController {
|
|
|
613
619
|
}
|
|
614
620
|
this.logger.error(
|
|
615
621
|
"[DevHost] Failed to switch player:",
|
|
616
|
-
|
|
622
|
+
formatErrorForLog(error),
|
|
617
623
|
);
|
|
618
624
|
this.setRuntimeError(
|
|
619
625
|
convertProblemDetailsToRuntimeError(
|
|
@@ -639,7 +645,13 @@ export class DevHostController {
|
|
|
639
645
|
}
|
|
640
646
|
}
|
|
641
647
|
|
|
648
|
+
function formatErrorForLog(error: unknown): string {
|
|
649
|
+
return error instanceof Error ? error.message : formatConsoleArgs([error]);
|
|
650
|
+
}
|
|
651
|
+
|
|
642
652
|
type ApiErrorPayload = {
|
|
653
|
+
error?: string;
|
|
654
|
+
message?: string;
|
|
643
655
|
title?: string;
|
|
644
656
|
detail?: string;
|
|
645
657
|
status?: number;
|
|
@@ -672,9 +684,13 @@ function convertProblemDetailsToRuntimeError(
|
|
|
672
684
|
code: typeof violation.code === "string" ? violation.code : undefined,
|
|
673
685
|
}));
|
|
674
686
|
|
|
675
|
-
const title =
|
|
687
|
+
const title =
|
|
688
|
+
payload.title?.trim() ||
|
|
689
|
+
(payload.error === "session_invalid" ? "Session expired" : "") ||
|
|
690
|
+
"Game failed to start";
|
|
676
691
|
const summary =
|
|
677
692
|
payload.detail?.trim() ||
|
|
693
|
+
payload.message?.trim() ||
|
|
678
694
|
(error instanceof Error ? error.message : fallbackMessage);
|
|
679
695
|
|
|
680
696
|
return {
|
|
@@ -89,94 +89,136 @@ h6 {
|
|
|
89
89
|
box-shadow: 8px 8px 0 0 #2d2d2d;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.dev-drawer-header {
|
|
106
|
-
background-color: rgba(255, 252, 244, 0.96);
|
|
107
|
-
color: #1f2937;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.dev-drawer-label {
|
|
111
|
-
background-color: #e5e0d8;
|
|
112
|
-
color: #6b5a45;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.dev-drawer-session-pill {
|
|
116
|
-
background-color: #fff3b5;
|
|
117
|
-
color: #1f2937;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.dev-drawer-session-dot {
|
|
121
|
-
background-color: #d97706;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.dev-drawer-close {
|
|
125
|
-
background-color: #fff;
|
|
126
|
-
color: #6b7280;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.dev-drawer-close:hover {
|
|
130
|
-
background-color: #e5e0d8;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.dev-drawer-note {
|
|
134
|
-
background-color: rgba(255, 255, 255, 0.72);
|
|
135
|
-
color: rgba(31, 41, 55, 0.8);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.dev-drawer-card {
|
|
139
|
-
background-color: #fffdf7;
|
|
140
|
-
color: #1f2937;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.dev-drawer-card-title {
|
|
144
|
-
color: #7a6956;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.dev-drawer-controls-card {
|
|
148
|
-
background-color: #fff3b5;
|
|
149
|
-
color: #1f2937;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.dev-drawer-feedback-card {
|
|
153
|
-
background-color: #fffaf0;
|
|
154
|
-
color: #1f2937;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.dev-drawer-input {
|
|
158
|
-
background-color: #fff;
|
|
159
|
-
color: #1f2937;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.dev-drawer-primary-button {
|
|
163
|
-
background-color: #1f2937;
|
|
92
|
+
/* Calm secondary CTA inside the dev drawer — the player switcher is
|
|
93
|
+
* already the panel's single wobbly+shadow stamp, so the primary action
|
|
94
|
+
* here is intentionally quiet. */
|
|
95
|
+
.dev-calm-button {
|
|
96
|
+
display: inline-flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
height: 2.5rem;
|
|
100
|
+
padding: 0 1rem;
|
|
101
|
+
border-radius: 0.5rem;
|
|
102
|
+
border: 2px solid #2d2d2d;
|
|
103
|
+
background-color: #2d2d2d;
|
|
164
104
|
color: #fff8e8;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
105
|
+
font-family: var(--font-sans);
|
|
106
|
+
font-size: 0.875rem;
|
|
107
|
+
font-weight: 700;
|
|
108
|
+
letter-spacing: 0.02em;
|
|
109
|
+
transition:
|
|
110
|
+
background-color 0.15s ease,
|
|
111
|
+
transform 0.05s ease;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.dev-calm-button:hover:not(:disabled) {
|
|
115
|
+
background-color: #1f1f1f;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.dev-calm-button:active:not(:disabled) {
|
|
119
|
+
transform: translateY(1px);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.dev-calm-button:disabled {
|
|
123
|
+
opacity: 0.5;
|
|
124
|
+
cursor: not-allowed;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Tame HostHistoryNavigator into a text link inside the dev drawer.
|
|
128
|
+
*
|
|
129
|
+
* The upstream Button ships with `wobbly-border-md` + `hard-shadow`
|
|
130
|
+
* baked into its base classes; twMerge cannot strip custom utilities so
|
|
131
|
+
* we brute-force a flat link-style treatment with !important on multiple
|
|
132
|
+
* selector forms. */
|
|
133
|
+
.dev-history-shell button,
|
|
134
|
+
.dev-history-shell [data-slot="button"],
|
|
135
|
+
.dev-history-shell .wobbly-border-md,
|
|
136
|
+
.dev-history-shell .hard-shadow {
|
|
137
|
+
height: auto !important;
|
|
138
|
+
padding: 0.25rem 0 !important;
|
|
139
|
+
background: transparent !important;
|
|
140
|
+
border: none !important;
|
|
141
|
+
box-shadow: none !important;
|
|
142
|
+
border-radius: 0 !important;
|
|
143
|
+
font-size: 0.75rem !important;
|
|
144
|
+
font-weight: 700 !important;
|
|
145
|
+
color: oklch(0.5 0 0) !important;
|
|
146
|
+
gap: 0.4rem !important;
|
|
147
|
+
text-transform: uppercase;
|
|
148
|
+
letter-spacing: 0.12em;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.dev-history-shell button:hover,
|
|
152
|
+
.dev-history-shell [data-slot="button"]:hover {
|
|
153
|
+
background: transparent !important;
|
|
154
|
+
color: oklch(0.25 0 0) !important;
|
|
155
|
+
box-shadow: none !important;
|
|
156
|
+
transform: none !important;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.dev-history-shell [data-slot="badge"] {
|
|
160
|
+
background-color: rgba(45, 45, 45, 0.08);
|
|
161
|
+
color: oklch(0.25 0 0);
|
|
162
|
+
border-color: transparent;
|
|
163
|
+
padding: 0 0.4rem;
|
|
164
|
+
font-size: 0.65rem;
|
|
165
|
+
letter-spacing: 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Calm copy buttons inside the Debug fold so they're text-tabular, not
|
|
169
|
+
* three wobbly stamps. */
|
|
170
|
+
.dev-host-debug-shell button,
|
|
171
|
+
.dev-host-debug-shell .wobbly-border-md,
|
|
172
|
+
.dev-host-debug-shell .hard-shadow {
|
|
173
|
+
border-radius: 0.375rem !important;
|
|
174
|
+
box-shadow: none !important;
|
|
175
|
+
border-width: 1px !important;
|
|
176
|
+
border-color: rgba(45, 45, 45, 0.12) !important;
|
|
177
|
+
background-color: transparent !important;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.dev-host-debug-shell button:hover {
|
|
181
|
+
background-color: rgba(45, 45, 45, 0.04) !important;
|
|
182
|
+
transform: none !important;
|
|
183
|
+
box-shadow: none !important;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Seed input — pull off the upstream wobbly+thick border in favour of a
|
|
187
|
+
* calm flat rectangle that matches the rest of the panel chrome. */
|
|
188
|
+
.dev-seed-input {
|
|
189
|
+
border-radius: 0.5rem !important;
|
|
190
|
+
border-width: 1.5px !important;
|
|
191
|
+
border-color: rgba(45, 45, 45, 0.18) !important;
|
|
192
|
+
background-color: #fff !important;
|
|
193
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.dev-seed-input:focus,
|
|
197
|
+
.dev-seed-input:focus-visible {
|
|
198
|
+
border-color: rgba(45, 45, 45, 0.4) !important;
|
|
199
|
+
box-shadow: 0 0 0 3px rgba(45, 93, 161, 0.12) !important;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Edge handle — calm strip at rest, soft glow on hover. */
|
|
203
|
+
.dev-edge-handle .dev-edge-strip {
|
|
204
|
+
transition:
|
|
205
|
+
background-color 200ms ease,
|
|
206
|
+
box-shadow 200ms ease,
|
|
207
|
+
width 200ms ease,
|
|
208
|
+
height 200ms ease;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.dev-edge-handle:hover .dev-edge-strip,
|
|
212
|
+
.dev-edge-handle:focus-visible .dev-edge-strip {
|
|
213
|
+
width: 6px;
|
|
214
|
+
height: 7rem;
|
|
215
|
+
background-color: #2d5da1;
|
|
216
|
+
box-shadow:
|
|
217
|
+
0 0 0 2px rgba(45, 93, 161, 0.18),
|
|
218
|
+
0 0 14px 4px rgba(45, 93, 161, 0.55),
|
|
219
|
+
0 0 28px 8px rgba(45, 93, 161, 0.25);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.dev-edge-handle:focus-visible {
|
|
223
|
+
outline: none;
|
|
182
224
|
}
|