@xfey/tutti 0.1.123 → 0.1.125
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/server-shell/local-console/browser-bridge.d.ts +10 -0
- package/dist/server-shell/local-console/browser-bridge.js +286 -0
- package/dist/server-shell/local-console/project-service.d.ts +1 -0
- package/dist/server-shell/local-console/project-service.js +13 -0
- package/dist/server-shell/local-console/server.js +6 -0
- package/dist/server-shell/local-console/session.d.ts +5 -1
- package/dist/server-shell/local-console/session.js +20 -4
- package/node_modules/@tutti/shared/dist/schemas/api/account-projects.d.ts +14 -0
- package/node_modules/@tutti/shared/dist/schemas/api/account-projects.js +19 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -0
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-OK0IlBO1.js → homepage-motion-scene-Ctt45F9o.js} +1 -1
- package/web/assets/index-BJpTcX_E.js +70 -0
- package/web/assets/{index-Ii1DXtvi.css → index-DTuz8Hb5.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-BcWfL_Uk.js +0 -70
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import { type LocalConsoleSessionRegistry } from "./session.js";
|
|
3
|
+
/** No caller-controlled destinations, control routes, session cookies or header forwarding. */
|
|
4
|
+
export declare function isConsoleBridgeOperation(method: string, path: string): boolean;
|
|
5
|
+
export declare function registerConsoleBrowserBridge(app: FastifyInstance, options: {
|
|
6
|
+
sessions: LocalConsoleSessionRegistry;
|
|
7
|
+
now?: () => Date;
|
|
8
|
+
tuttiHome?: string;
|
|
9
|
+
}): void;
|
|
10
|
+
//# sourceMappingURL=browser-bridge.d.ts.map
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { resolveRelayUrl } from "../cli/launch.js";
|
|
3
|
+
import { serializeRuntimeLogLine } from "@tutti/shared/utils";
|
|
4
|
+
import { appendHostLogLine, getHostLogFilePath } from "../cli/machine-local.js";
|
|
5
|
+
import { LOCAL_CONSOLE_SESSION_COOKIE } from "./session.js";
|
|
6
|
+
const BASE = "/local-console/bridge/v1";
|
|
7
|
+
const IDLE_MS = 15 * 60_000;
|
|
8
|
+
const LIFETIME_MS = 12 * 60 * 60_000;
|
|
9
|
+
const secret = () => randomBytes(32).toString("base64url");
|
|
10
|
+
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
11
|
+
const tokenPattern = /^[A-Za-z0-9_-]{43}$/u;
|
|
12
|
+
/** No caller-controlled destinations, control routes, session cookies or header forwarding. */
|
|
13
|
+
export function isConsoleBridgeOperation(method, path) {
|
|
14
|
+
if (method === "GET") {
|
|
15
|
+
return ([
|
|
16
|
+
"/bootstrap",
|
|
17
|
+
"/projects",
|
|
18
|
+
"/heartbeat",
|
|
19
|
+
"/package-update",
|
|
20
|
+
"/package-update?refresh=true",
|
|
21
|
+
].includes(path) || /^\/projects\/prj_[A-Za-z0-9]{1,64}\/feishu-setup$/u.test(path));
|
|
22
|
+
}
|
|
23
|
+
return (method === "POST" &&
|
|
24
|
+
([
|
|
25
|
+
"/folders/pick",
|
|
26
|
+
"/providers/models",
|
|
27
|
+
"/projects/launch",
|
|
28
|
+
"/package-update",
|
|
29
|
+
"/account/managed-usage",
|
|
30
|
+
].includes(path) ||
|
|
31
|
+
/^\/projects\/prj_[A-Za-z0-9]{1,64}\/(?:open|invite|stop)$/u.test(path) ||
|
|
32
|
+
/^\/managed-projects\/(?:prepare|authorize|read|reconcile|validate|launch)$/u.test(path)));
|
|
33
|
+
}
|
|
34
|
+
function validOrigin(origin) {
|
|
35
|
+
if (typeof origin !== "string")
|
|
36
|
+
return false;
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(origin);
|
|
39
|
+
return (url.origin === origin &&
|
|
40
|
+
!url.username &&
|
|
41
|
+
!url.password &&
|
|
42
|
+
(url.protocol === "https:" || (url.protocol === "http:" && url.hostname === "127.0.0.1")));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function objectWithKeys(value, required, optional = []) {
|
|
49
|
+
return (typeof value === "object" &&
|
|
50
|
+
value !== null &&
|
|
51
|
+
!Array.isArray(value) &&
|
|
52
|
+
required.every((key) => Object.hasOwn(value, key)) &&
|
|
53
|
+
Object.keys(value).every((key) => required.includes(key) || optional.includes(key)));
|
|
54
|
+
}
|
|
55
|
+
export function registerConsoleBrowserBridge(app, options) {
|
|
56
|
+
const registry = new Map();
|
|
57
|
+
app.addHook("onResponse", (request, reply, done) => {
|
|
58
|
+
if (options.tuttiHome && request.url.startsWith(`${BASE}/`)) {
|
|
59
|
+
const suffix = request.url.slice(BASE.length);
|
|
60
|
+
const stage = [
|
|
61
|
+
"/session",
|
|
62
|
+
"/account",
|
|
63
|
+
"/account/complete",
|
|
64
|
+
"/request",
|
|
65
|
+
"/disconnect",
|
|
66
|
+
].includes(suffix)
|
|
67
|
+
? suffix.slice(1).replaceAll("/", "_")
|
|
68
|
+
: "rejected";
|
|
69
|
+
try {
|
|
70
|
+
appendHostLogLine(getHostLogFilePath(options.tuttiHome), serializeRuntimeLogLine({
|
|
71
|
+
service: "host",
|
|
72
|
+
component: "console-bridge",
|
|
73
|
+
level: reply.statusCode >= 400 ? "warn" : "info",
|
|
74
|
+
message: "Console bridge request",
|
|
75
|
+
now: options.now ?? (() => new Date()),
|
|
76
|
+
fields: { request_id: request.id, stage, status: reply.statusCode },
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* Diagnostic failure never changes access or reveals request data. */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
done();
|
|
84
|
+
});
|
|
85
|
+
const now = () => (options.now?.() ?? new Date()).getTime();
|
|
86
|
+
const remove = (key, entry) => {
|
|
87
|
+
options.sessions.revokeSession(entry.session);
|
|
88
|
+
if (entry.continuation)
|
|
89
|
+
options.sessions.revokeAccessToken(entry.continuation);
|
|
90
|
+
registry.delete(key);
|
|
91
|
+
};
|
|
92
|
+
const purge = () => {
|
|
93
|
+
for (const [key, entry] of registry) {
|
|
94
|
+
if (entry.expires <= now() || entry.touched + IDLE_MS <= now())
|
|
95
|
+
remove(key, entry);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const fail = (request, reply, status, code) => reply.code(status).send({
|
|
99
|
+
error: {
|
|
100
|
+
code,
|
|
101
|
+
message: "Reconnect this computer from Projects.",
|
|
102
|
+
retryable: false,
|
|
103
|
+
request_id: request.id,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
// A preflight is only a transport negotiation. Every actual request separately authenticates
|
|
107
|
+
// its native-configured origin and entry capability; a successful OPTIONS grants no access.
|
|
108
|
+
app.addHook("onRequest", (request, reply, done) => {
|
|
109
|
+
if (!request.url.startsWith(`${BASE}/`))
|
|
110
|
+
return done();
|
|
111
|
+
reply.header("cache-control", "no-store").header("vary", "Origin");
|
|
112
|
+
if (!validOrigin(request.headers.origin)) {
|
|
113
|
+
void fail(request, reply, 403, "bridge_origin_rejected");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (request.url.includes("?")) {
|
|
117
|
+
void fail(request, reply, 400, "bridge_input_invalid");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
reply
|
|
121
|
+
.header("access-control-allow-origin", request.headers.origin)
|
|
122
|
+
.header("access-control-expose-headers", "x-tutti-request-id");
|
|
123
|
+
if (request.method === "OPTIONS") {
|
|
124
|
+
const requested = String(request.headers["access-control-request-headers"] ?? "")
|
|
125
|
+
.toLowerCase()
|
|
126
|
+
.split(",")
|
|
127
|
+
.map((v) => v.trim())
|
|
128
|
+
.filter(Boolean);
|
|
129
|
+
if (request.headers["access-control-request-method"] !== "POST" ||
|
|
130
|
+
requested.some((v) => !["authorization", "content-type"].includes(v))) {
|
|
131
|
+
void fail(request, reply, 403, "bridge_preflight_rejected");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
reply
|
|
135
|
+
.header("access-control-allow-methods", "POST")
|
|
136
|
+
.header("access-control-allow-headers", "authorization, content-type")
|
|
137
|
+
.header("access-control-allow-private-network", "true");
|
|
138
|
+
void reply.code(204).send();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
done();
|
|
142
|
+
});
|
|
143
|
+
app.options(`${BASE}/*`, (_request, reply) => reply.code(204).send());
|
|
144
|
+
app.post(`${BASE}/session`, (request, reply) => {
|
|
145
|
+
purge();
|
|
146
|
+
const body = request.body;
|
|
147
|
+
if (!objectWithKeys(body, ["access_token"]) ||
|
|
148
|
+
typeof body.access_token !== "string" ||
|
|
149
|
+
!tokenPattern.test(body.access_token))
|
|
150
|
+
return fail(request, reply, 400, "bridge_input_invalid");
|
|
151
|
+
const context = options.sessions.readEntryContext(body.access_token);
|
|
152
|
+
const origin = request.headers.origin;
|
|
153
|
+
const configured = context ? resolveRelayUrl(context.environment).replace(/\/$/u, "") : null;
|
|
154
|
+
if (!context || !validOrigin(origin) || configured !== origin)
|
|
155
|
+
return fail(request, reply, 403, "bridge_entry_rejected");
|
|
156
|
+
if (registry.size >= 16)
|
|
157
|
+
return fail(request, reply, 429, "bridge_capacity_reached");
|
|
158
|
+
const session = options.sessions.exchangeAccessToken(body.access_token, undefined, undefined, true);
|
|
159
|
+
if (!session?.sessionToken)
|
|
160
|
+
return fail(request, reply, 401, "bridge_entry_expired");
|
|
161
|
+
const token = secret();
|
|
162
|
+
registry.set(hash(token), {
|
|
163
|
+
origin,
|
|
164
|
+
session: session.sessionToken,
|
|
165
|
+
context: session.contextToken,
|
|
166
|
+
csrf: session.csrfToken,
|
|
167
|
+
touched: now(),
|
|
168
|
+
expires: now() + LIFETIME_MS,
|
|
169
|
+
});
|
|
170
|
+
return { bridge_token: token };
|
|
171
|
+
});
|
|
172
|
+
function authenticate(request) {
|
|
173
|
+
purge();
|
|
174
|
+
const bearer = /^Bearer ([A-Za-z0-9_-]{43})$/u.exec(String(request.headers.authorization ?? ""))?.[1];
|
|
175
|
+
const key = bearer ? hash(bearer) : "";
|
|
176
|
+
const entry = registry.get(key);
|
|
177
|
+
if (!entry ||
|
|
178
|
+
entry.origin !== request.headers.origin ||
|
|
179
|
+
!options.sessions.readSession(entry.session, entry.context))
|
|
180
|
+
return null;
|
|
181
|
+
entry.touched = now();
|
|
182
|
+
return [key, entry];
|
|
183
|
+
}
|
|
184
|
+
app.post(`${BASE}/disconnect`, (request, reply) => {
|
|
185
|
+
const auth = authenticate(request);
|
|
186
|
+
if (!auth)
|
|
187
|
+
return fail(request, reply, 401, "bridge_session_required");
|
|
188
|
+
if (request.body !== undefined)
|
|
189
|
+
return fail(request, reply, 400, "bridge_input_invalid");
|
|
190
|
+
remove(...auth);
|
|
191
|
+
return { status: "disconnected" };
|
|
192
|
+
});
|
|
193
|
+
app.post(`${BASE}/account`, (request, reply) => {
|
|
194
|
+
const auth = authenticate(request);
|
|
195
|
+
if (!auth)
|
|
196
|
+
return fail(request, reply, 401, "bridge_session_required");
|
|
197
|
+
if (request.body !== undefined)
|
|
198
|
+
return fail(request, reply, 400, "bridge_input_invalid");
|
|
199
|
+
const entry = auth[1];
|
|
200
|
+
const session = options.sessions.readSession(entry.session, entry.context);
|
|
201
|
+
if (session.accountReady)
|
|
202
|
+
return fail(request, reply, 409, "bridge_account_already_ready");
|
|
203
|
+
if (entry.continuation)
|
|
204
|
+
options.sessions.revokeAccessToken(entry.continuation);
|
|
205
|
+
const issued = options.sessions.issueAccountContinuation(session.context);
|
|
206
|
+
entry.continuation = issued.token;
|
|
207
|
+
return { access_token: issued.token, expires_at: issued.expires_at };
|
|
208
|
+
});
|
|
209
|
+
app.post(`${BASE}/account/complete`, (request, reply) => {
|
|
210
|
+
const auth = authenticate(request);
|
|
211
|
+
if (!auth)
|
|
212
|
+
return fail(request, reply, 401, "bridge_session_required");
|
|
213
|
+
const entry = auth[1];
|
|
214
|
+
const body = request.body;
|
|
215
|
+
if (!objectWithKeys(body, ["access_token", "account_display_name"]) ||
|
|
216
|
+
body.access_token !== entry.continuation ||
|
|
217
|
+
typeof body.access_token !== "string" ||
|
|
218
|
+
typeof body.account_display_name !== "string" ||
|
|
219
|
+
!body.account_display_name.trim() ||
|
|
220
|
+
body.account_display_name.length > 80)
|
|
221
|
+
return fail(request, reply, 400, "bridge_account_mismatch");
|
|
222
|
+
const session = options.sessions.exchangeAccessToken(body.access_token, undefined, body.account_display_name, true);
|
|
223
|
+
if (!session?.sessionToken)
|
|
224
|
+
return fail(request, reply, 401, "bridge_account_expired");
|
|
225
|
+
options.sessions.revokeSession(entry.session);
|
|
226
|
+
entry.session = session.sessionToken;
|
|
227
|
+
entry.context = session.contextToken;
|
|
228
|
+
entry.csrf = session.csrfToken;
|
|
229
|
+
delete entry.continuation;
|
|
230
|
+
return { status: "ready" };
|
|
231
|
+
});
|
|
232
|
+
app.post(`${BASE}/request`, async (request, reply) => {
|
|
233
|
+
const auth = authenticate(request);
|
|
234
|
+
if (!auth)
|
|
235
|
+
return fail(request, reply, 401, "bridge_session_required");
|
|
236
|
+
const [key, entry] = auth;
|
|
237
|
+
const body = request.body;
|
|
238
|
+
if (!objectWithKeys(body, ["method", "path"], ["body"]) ||
|
|
239
|
+
typeof body.method !== "string" ||
|
|
240
|
+
typeof body.path !== "string" ||
|
|
241
|
+
!isConsoleBridgeOperation(body.method, body.path) ||
|
|
242
|
+
(body.body !== undefined && typeof body.body !== "string") ||
|
|
243
|
+
(body.method === "GET" && body.body !== undefined))
|
|
244
|
+
return fail(request, reply, 400, "bridge_operation_rejected");
|
|
245
|
+
if (!options.sessions.readSession(entry.session, entry.context)?.accountReady)
|
|
246
|
+
return fail(request, reply, 401, "bridge_account_required");
|
|
247
|
+
const response = await app.inject({
|
|
248
|
+
method: body.method,
|
|
249
|
+
url: `/local-console/v1${body.path}`,
|
|
250
|
+
headers: {
|
|
251
|
+
host: request.headers.host,
|
|
252
|
+
origin: `http://${request.headers.host}`,
|
|
253
|
+
cookie: `${LOCAL_CONSOLE_SESSION_COOKIE}=${entry.session}`,
|
|
254
|
+
"x-local-console-context": entry.context,
|
|
255
|
+
"x-local-console-csrf": entry.csrf,
|
|
256
|
+
...(body.body === undefined ? {} : { "content-type": "application/json" }),
|
|
257
|
+
},
|
|
258
|
+
...(body.body === undefined ? {} : { payload: body.body }),
|
|
259
|
+
});
|
|
260
|
+
purge();
|
|
261
|
+
if (registry.get(key) !== entry)
|
|
262
|
+
return fail(request, reply, 401, "bridge_session_required");
|
|
263
|
+
if (response.rawPayload.length > 2 * 1024 * 1024)
|
|
264
|
+
return fail(request, reply, 502, "bridge_response_too_large");
|
|
265
|
+
let data;
|
|
266
|
+
try {
|
|
267
|
+
data = response.json();
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return fail(request, reply, 502, "bridge_response_invalid");
|
|
271
|
+
}
|
|
272
|
+
// The reused controller only needs a readiness marker; never expose native cookie/CSRF.
|
|
273
|
+
if (body.path === "/bootstrap" &&
|
|
274
|
+
typeof data === "object" &&
|
|
275
|
+
data !== null &&
|
|
276
|
+
"csrf_token" in data)
|
|
277
|
+
data = { ...data, csrf_token: "bridge" };
|
|
278
|
+
return reply.code(response.statusCode).send(data);
|
|
279
|
+
});
|
|
280
|
+
app.addHook("onClose", (_instance, done) => {
|
|
281
|
+
for (const [key, entry] of registry)
|
|
282
|
+
remove(key, entry);
|
|
283
|
+
done();
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=browser-bridge.js.map
|
|
@@ -18,6 +18,7 @@ export type LocalConsoleProject = {
|
|
|
18
18
|
provider_status: ProviderConfigProjection["status"];
|
|
19
19
|
provider_model?: string;
|
|
20
20
|
relay_project_ref?: RelayProjectRef;
|
|
21
|
+
relay_origin?: string;
|
|
21
22
|
open_url?: string;
|
|
22
23
|
relay_connection_status?: string;
|
|
23
24
|
created_at?: string;
|
|
@@ -91,6 +91,7 @@ function validateProjectMetadata(input) {
|
|
|
91
91
|
}
|
|
92
92
|
function projectFromRuntimeRow(row, providerStatus, providerModel, metadata) {
|
|
93
93
|
const currentVersion = readCliVersion();
|
|
94
|
+
const relayOrigin = safeRelayOrigin(metadata.relayUrl);
|
|
94
95
|
const openUrl = metadata.surfaceProfile.kind === "standalone"
|
|
95
96
|
? projectOpenUrl(metadata.relayUrl, row.relay_project_ref)
|
|
96
97
|
: undefined;
|
|
@@ -103,6 +104,7 @@ function projectFromRuntimeRow(row, providerStatus, providerModel, metadata) {
|
|
|
103
104
|
provider_status: providerStatus,
|
|
104
105
|
...(providerModel === undefined ? {} : { provider_model: providerModel }),
|
|
105
106
|
...(row.relay_project_ref === undefined ? {} : { relay_project_ref: row.relay_project_ref }),
|
|
107
|
+
...(relayOrigin === undefined ? {} : { relay_origin: relayOrigin }),
|
|
106
108
|
...(openUrl === undefined ? {} : { open_url: openUrl }),
|
|
107
109
|
...(row.relay_connection_status === undefined
|
|
108
110
|
? {}
|
|
@@ -115,6 +117,17 @@ function projectFromRuntimeRow(row, providerStatus, providerModel, metadata) {
|
|
|
115
117
|
: { update_required: true }),
|
|
116
118
|
};
|
|
117
119
|
}
|
|
120
|
+
function safeRelayOrigin(value) {
|
|
121
|
+
try {
|
|
122
|
+
const url = new URL(value ?? "");
|
|
123
|
+
return !url.username && !url.password && (url.protocol === "https:" || url.protocol === "http:")
|
|
124
|
+
? url.origin
|
|
125
|
+
: undefined;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
118
131
|
function projectOpenUrl(relayUrl, relayProjectRef) {
|
|
119
132
|
if (relayUrl === undefined || relayProjectRef === undefined) {
|
|
120
133
|
return undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { registerConsoleBrowserBridge } from "./browser-bridge.js";
|
|
2
3
|
import { LocalConsoleManagedProjectService } from "./managed-project-service.js";
|
|
3
4
|
import { registerLocalConsoleManagedProjectRoutes } from "./managed-project-routes.js";
|
|
4
5
|
import { existsSync, statSync } from "node:fs";
|
|
@@ -193,6 +194,11 @@ export function createLocalConsoleServer(options) {
|
|
|
193
194
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
194
195
|
}),
|
|
195
196
|
});
|
|
197
|
+
registerConsoleBrowserBridge(app, {
|
|
198
|
+
sessions,
|
|
199
|
+
tuttiHome,
|
|
200
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
201
|
+
});
|
|
196
202
|
app.get(`${LOCAL_CONSOLE_API_BASE}/health`, () => ({
|
|
197
203
|
status: "ready",
|
|
198
204
|
protocol_version: 2,
|
|
@@ -27,7 +27,11 @@ export declare class LocalConsoleSessionRegistry {
|
|
|
27
27
|
});
|
|
28
28
|
issueAccessToken(context: LocalConsoleInvocationContext): IssuedLocalConsoleAccessToken;
|
|
29
29
|
issueAccountContinuation(context: LocalConsoleInvocationContext): IssuedLocalConsoleAccessToken;
|
|
30
|
-
exchangeAccessToken(token: string, existingSessionToken?: string, accountDisplayName?: string): ExchangedLocalConsoleSession | null;
|
|
30
|
+
exchangeAccessToken(token: string, existingSessionToken?: string, accountDisplayName?: string, isolated?: boolean): ExchangedLocalConsoleSession | null;
|
|
31
|
+
/** Inspect only a fresh native entry, never an account-ready continuation. */
|
|
32
|
+
readEntryContext(token: string): LocalConsoleInvocationContext | null;
|
|
33
|
+
revokeAccessToken(token: string): void;
|
|
34
|
+
revokeSession(token: string): void;
|
|
31
35
|
readSession(token: string | undefined, contextToken?: string): ReadLocalConsoleSession | null;
|
|
32
36
|
}
|
|
33
37
|
export declare function parseCookieHeader(header: string | undefined): Record<string, string>;
|
|
@@ -55,7 +55,7 @@ export class LocalConsoleSessionRegistry {
|
|
|
55
55
|
});
|
|
56
56
|
return { token, expires_at: new Date(expiresAt).toISOString() };
|
|
57
57
|
}
|
|
58
|
-
exchangeAccessToken(token, existingSessionToken, accountDisplayName) {
|
|
58
|
+
exchangeAccessToken(token, existingSessionToken, accountDisplayName, isolated = false) {
|
|
59
59
|
this.#purgeExpired();
|
|
60
60
|
const accessTokenHash = hashSecret(token);
|
|
61
61
|
const accessToken = this.#accessTokens.get(accessTokenHash);
|
|
@@ -68,7 +68,7 @@ export class LocalConsoleSessionRegistry {
|
|
|
68
68
|
let sessionToken;
|
|
69
69
|
let sessionHash = "";
|
|
70
70
|
let session;
|
|
71
|
-
if (existingSessionToken !== undefined) {
|
|
71
|
+
if (!isolated && existingSessionToken !== undefined) {
|
|
72
72
|
sessionHash = hashSecret(existingSessionToken);
|
|
73
73
|
const existingSession = this.#sessions.get(sessionHash);
|
|
74
74
|
if (existingSession !== undefined &&
|
|
@@ -77,7 +77,7 @@ export class LocalConsoleSessionRegistry {
|
|
|
77
77
|
session = existingSession;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
if (session === undefined && this.#browserSessionToken !== undefined) {
|
|
80
|
+
if (!isolated && session === undefined && this.#browserSessionToken !== undefined) {
|
|
81
81
|
const sharedSessionHash = hashSecret(this.#browserSessionToken);
|
|
82
82
|
const sharedSession = this.#sessions.get(sharedSessionHash);
|
|
83
83
|
if (sharedSession !== undefined &&
|
|
@@ -90,7 +90,8 @@ export class LocalConsoleSessionRegistry {
|
|
|
90
90
|
}
|
|
91
91
|
if (session === undefined) {
|
|
92
92
|
sessionToken = createSecret();
|
|
93
|
-
|
|
93
|
+
if (!isolated)
|
|
94
|
+
this.#browserSessionToken = sessionToken;
|
|
94
95
|
sessionHash = hashSecret(sessionToken);
|
|
95
96
|
session = {
|
|
96
97
|
hash: sessionHash,
|
|
@@ -129,6 +130,21 @@ export class LocalConsoleSessionRegistry {
|
|
|
129
130
|
expires_at: new Date(session.expiresAt).toISOString(),
|
|
130
131
|
};
|
|
131
132
|
}
|
|
133
|
+
/** Inspect only a fresh native entry, never an account-ready continuation. */
|
|
134
|
+
readEntryContext(token) {
|
|
135
|
+
this.#purgeExpired();
|
|
136
|
+
const access = this.#accessTokens.get(hashSecret(token));
|
|
137
|
+
return access && !access.accountReady && secretEquals(token, access.hash)
|
|
138
|
+
? access.context
|
|
139
|
+
: null;
|
|
140
|
+
}
|
|
141
|
+
revokeAccessToken(token) {
|
|
142
|
+
this.#accessTokens.delete(hashSecret(token));
|
|
143
|
+
}
|
|
144
|
+
revokeSession(token) {
|
|
145
|
+
this.#sessions.delete(hashSecret(token));
|
|
146
|
+
this.#purgeExpired();
|
|
147
|
+
}
|
|
132
148
|
readSession(token, contextToken) {
|
|
133
149
|
this.#purgeExpired();
|
|
134
150
|
if (token === undefined || token.trim() === "") {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Static } from "@sinclair/typebox";
|
|
2
|
+
export declare const AccountProjectsSchema: import("@sinclair/typebox").TObject<{
|
|
3
|
+
projects: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
4
|
+
relay_project_ref: import("@sinclair/typebox").TString;
|
|
5
|
+
display_name: import("@sinclair/typebox").TString;
|
|
6
|
+
surface: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"standalone">, import("@sinclair/typebox").TLiteral<"feishu">]>;
|
|
7
|
+
role: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"owner">, import("@sinclair/typebox").TLiteral<"member">]>;
|
|
8
|
+
host_status: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"online">, import("@sinclair/typebox").TLiteral<"offline">, import("@sinclair/typebox").TLiteral<"unknown">]>;
|
|
9
|
+
last_opened_at: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
10
|
+
}>>;
|
|
11
|
+
truncated: import("@sinclair/typebox").TBoolean;
|
|
12
|
+
}>;
|
|
13
|
+
export type AccountProjects = Static<typeof AccountProjectsSchema>;
|
|
14
|
+
//# sourceMappingURL=account-projects.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { IsoDateTimeStringSchema, RelayProjectRefSchema } from "../domain/index.js";
|
|
3
|
+
// Account navigation metadata only. Feishu rows never confer workspace authority.
|
|
4
|
+
export const AccountProjectsSchema = Type.Object({
|
|
5
|
+
projects: Type.Array(Type.Object({
|
|
6
|
+
relay_project_ref: RelayProjectRefSchema,
|
|
7
|
+
display_name: Type.String(),
|
|
8
|
+
surface: Type.Union([Type.Literal("standalone"), Type.Literal("feishu")]),
|
|
9
|
+
role: Type.Union([Type.Literal("owner"), Type.Literal("member")]),
|
|
10
|
+
host_status: Type.Union([
|
|
11
|
+
Type.Literal("online"),
|
|
12
|
+
Type.Literal("offline"),
|
|
13
|
+
Type.Literal("unknown"),
|
|
14
|
+
]),
|
|
15
|
+
last_opened_at: Type.Optional(IsoDateTimeStringSchema),
|
|
16
|
+
}, { additionalProperties: false }), { maxItems: 100 }),
|
|
17
|
+
truncated: Type.Boolean(),
|
|
18
|
+
}, { additionalProperties: false });
|
|
19
|
+
//# sourceMappingURL=account-projects.js.map
|
|
@@ -26,4 +26,5 @@ export { ContextSyncBodySchema, ContextSyncDispositionSchema, ContextSyncPayload
|
|
|
26
26
|
export * from "./feishu-documents.js";
|
|
27
27
|
export * from "./external-references.js";
|
|
28
28
|
export * from "./feishu-connections.js";
|
|
29
|
+
export * from "./account-projects.js";
|
|
29
30
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -22,4 +22,5 @@ export { ContextSyncBodySchema, ContextSyncDispositionSchema, ContextSyncPayload
|
|
|
22
22
|
export * from "./feishu-documents.js";
|
|
23
23
|
export * from "./external-references.js";
|
|
24
24
|
export * from "./feishu-connections.js";
|
|
25
|
+
export * from "./account-projects.js";
|
|
25
26
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{C as e,S as t,_ as n,a as r,b as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"./index-BcWfL_Uk.js";var w=e(`mouse-pointer-2`,[[`path`,{d:`M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z`,key:`edeuup`}]]),T=e(`send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),E=x();function D(e){return{"--reveal":e}}function O(e,t,n){let r=(e,t)=>Number.parseInt(e.slice(t,t+2),16),i=i=>Math.round(r(e,i)+(r(t,i)-r(e,i))*n).toString(16).padStart(2,`0`);return`#${i(1)}${i(3)}${i(5)}`}function k(e){let t=r((e-p.runEnd)/(p.executionComplete-p.runEnd));return t+t*t-t*t*t}function A({progress:e,children:t,className:n=``}){return(0,E.jsx)(`div`,{className:`scene-reveal ${n}`,style:D(e),children:t})}function j({progress:e,author:t,avatar:n,tone:r,timestamp:i,online:a=!1,assets:o,children:s}){return(0,E.jsxs)(`article`,{className:`scene-message`,style:D(e),children:[(0,E.jsx)(`span`,{className:`scene-avatar is-${r} ${n===`tutti`?`is-tutti`:``} ${a?`is-online`:``}`,"aria-hidden":`true`,children:n===`tutti`?(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:o.tuttiAvatarSrc,alt:``}):(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:o.humanAvatars[n],alt:``})}),(0,E.jsxs)(`div`,{className:`scene-message-copy`,children:[(0,E.jsxs)(`header`,{children:[(0,E.jsx)(`strong`,{children:t}),(0,E.jsx)(`span`,{children:i})]}),(0,E.jsx)(`p`,{children:s})]})]})}function M({time:e,assets:r}){let i=e>=p.artifactLive,a=e>=p.artifactClick,o=e>=p.runEnd&&e<p.executionComplete,u=d(e,p.runEnd,19.2),f=d(e,p.artifactLive,30.92);return(0,E.jsxs)(`aside`,{className:`scene-sidebar`,children:[(0,E.jsx)(`button`,{className:`scene-brand`,type:`button`,"aria-label":`Tutti`,tabIndex:-1,children:(0,E.jsx)(`img`,{src:r.logoSrc,alt:``})}),(0,E.jsxs)(`div`,{className:`scene-nav-stack`,children:[(0,E.jsxs)(`nav`,{className:`scene-nav`,"aria-label":`Workspace pages`,children:[(0,E.jsx)(`button`,{className:a?``:`is-active`,type:`button`,"aria-label":`Chat`,tabIndex:-1,children:(0,E.jsx)(c,{"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`Worklist`,tabIndex:-1,children:(0,E.jsx)(b,{"aria-hidden":`true`})}),(0,E.jsxs)(`button`,{className:a?`is-active`:``,type:`button`,"aria-label":`Artifacts`,tabIndex:-1,children:[(0,E.jsx)(l,{"aria-hidden":`true`}),i?(0,E.jsx)(`span`,{className:`scene-live-pill`,style:D(f),children:`Live`}):null]}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`References`,tabIndex:-1,children:(0,E.jsx)(C,{"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`Skills`,tabIndex:-1,children:(0,E.jsx)(s,{"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`Timeline`,tabIndex:-1,children:(0,E.jsx)(t,{"aria-hidden":`true`})})]}),o?(0,E.jsx)(`button`,{className:`scene-status-entry is-running`,style:D(u),type:`button`,"aria-label":`Task running`,tabIndex:-1,children:(0,E.jsx)(n,{"aria-hidden":`true`})}):null]}),(0,E.jsx)(`button`,{className:`scene-settings`,type:`button`,"aria-label":`Settings`,tabIndex:-1,children:(0,E.jsx)(h,{"aria-hidden":`true`})})]})}function N({time:e,assets:t}){let n=d(e,30.35,30.92);return(0,E.jsxs)(`section`,{className:`scene-panel scene-chat-panel`,children:[(0,E.jsxs)(`header`,{className:`scene-panel-header`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,children:(0,E.jsx)(c,{"aria-hidden":`true`})}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`h2`,{children:`Morrow Studio`}),(0,E.jsx)(`p`,{children:`Ceramics storefront`})]}),(0,E.jsxs)(`span`,{className:`scene-members`,"aria-hidden":`true`,children:[(0,E.jsx)(`i`,{className:`is-green`,children:(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:t.humanAvatars.fey,alt:``})}),(0,E.jsx)(`i`,{className:`is-blue`,children:(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:t.humanAvatars.avery,alt:``})}),(0,E.jsx)(`i`,{className:`is-yellow`,children:(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:t.humanAvatars.jun,alt:``})})]})]}),(0,E.jsxs)(`div`,{className:`scene-chat-stream`,children:[(0,E.jsx)(j,{progress:d(e,1.35,2.05),author:`Fey`,avatar:`fey`,tone:`green`,timestamp:`10:12`,online:!0,assets:t,children:`🏺 Let's build an online shop for our ceramics studio.`}),(0,E.jsx)(j,{progress:d(e,2.65,3.35),author:`Avery`,avatar:`avery`,tone:`blue`,timestamp:`10:13`,online:!0,assets:t,children:`✨ Keep it warm, minimal, and editorial.`}),(0,E.jsx)(j,{progress:d(e,3.95,4.65),author:`Jun`,avatar:`jun`,tone:`yellow`,timestamp:`10:14`,online:!0,assets:t,children:`🎨 Let people preview every piece in different glazes.`}),(0,E.jsx)(j,{progress:d(e,5.25,5.95),author:`Tutti`,avatar:`tutti`,tone:`green`,timestamp:`10:15`,assets:t,children:`Got it — I'll put it together. ✨`}),(0,E.jsxs)(`article`,{className:`scene-task-result`,style:D(n),children:[(0,E.jsx)(`span`,{className:`scene-task-result-rail`,"aria-hidden":`true`}),(0,E.jsx)(`span`,{className:`scene-task-result-icon`,"aria-hidden":`true`,children:(0,E.jsx)(S,{})}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Task completed`}),(0,E.jsx)(`strong`,{children:`Build ceramics storefront`}),(0,E.jsx)(`small`,{children:`Storefront`}),(0,E.jsx)(`p`,{children:`Warm editorial shopping and glaze previews are ready in Artifacts.`})]}),(0,E.jsx)(i,{className:`scene-task-result-chevron`,"aria-hidden":`true`})]})]}),(0,E.jsxs)(`div`,{className:`scene-composer`,"aria-hidden":`true`,children:[(0,E.jsx)(`span`,{children:`Write a message`}),(0,E.jsx)(T,{})]})]})}function P({progress:e,icon:t,children:n}){return(0,E.jsxs)(`div`,{className:`scene-scratchpad-row`,style:D(e),children:[(0,E.jsx)(`span`,{"aria-hidden":`true`,children:t}),(0,E.jsx)(`p`,{children:n})]})}function F({time:e}){return(0,E.jsxs)(`section`,{className:`scene-panel scene-scratchpad-panel`,style:{"--scratchpad-collapse":d(e,p.runEnd,19.18)},children:[(0,E.jsxs)(`header`,{className:`scene-panel-header`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,children:(0,E.jsx)(v,{"aria-hidden":`true`})}),(0,E.jsx)(`h2`,{children:`Scratchpad`})]}),(0,E.jsxs)(`div`,{className:`scene-scratchpad-body`,children:[(0,E.jsxs)(A,{progress:d(e,8.95,10),className:`scene-scratchpad-intro`,children:[(0,E.jsx)(`h3`,{children:`Ceramics storefront`}),(0,E.jsx)(`p`,{children:`A warm, editorial storefront for a small-batch studio, centered on tactile product discovery.`})]}),(0,E.jsx)(A,{progress:d(e,9.85,10.65),className:`scene-scratchpad-label`,children:`Confirmed`}),(0,E.jsx)(P,{progress:d(e,10.4,11.25),icon:(0,E.jsx)(S,{}),children:`Product-first editorial layout with generous space`}),(0,E.jsx)(P,{progress:d(e,11.2,12.05),icon:(0,E.jsx)(S,{}),children:`Warm neutrals with quiet serif headlines`}),(0,E.jsx)(P,{progress:d(e,12,12.85),icon:(0,E.jsx)(S,{}),children:`Keep the collection small, curated, and story-led`}),(0,E.jsx)(A,{progress:d(e,12.75,13.55),className:`scene-scratchpad-label`,children:`Requested feature`}),(0,E.jsx)(P,{progress:d(e,13.3,14.2),icon:(0,E.jsx)(o,{}),children:`Preview every piece in clay, sage, and ink glazes`})]}),(0,E.jsxs)(`footer`,{className:`scene-scratchpad-footer`,style:D(d(e,15.7,16.8)),children:[(0,E.jsxs)(`span`,{className:`scene-writing-mark`,children:[(0,E.jsx)(`strong`,{children:`Updated`}),(0,E.jsx)(`span`,{children:`just now`})]}),(0,E.jsxs)(`button`,{className:`scene-run-button`,type:`button`,tabIndex:-1,children:[(0,E.jsx)(f,{"aria-hidden":`true`}),(0,E.jsx)(`span`,{children:`Run`})]})]})]})}function I(e,t){let n=Math.max(0,Math.floor((e-t)*50/5)*5);return`${Math.floor(n/60)}m${(n%60).toString().padStart(2,`0`)}s`}function L({time:e,scoreSrc:t}){let r=d(e,18.92,19.2),i=_(e),a=i.id===`complete`,o={prepare:p.runEnd,implement:p.executionPrepareEnd,validate:p.executionImplementEnd,update:p.executionValidateEnd,complete:p.executionComplete}[i.id],s=d(e,o,o+.32),c=k(e);return(0,E.jsx)(`div`,{className:`scene-execution`,style:{"--reveal":r,"--stage-reveal":s},children:(0,E.jsxs)(`article`,{className:`homepage-motion-panel homepage-motion-execution-panel homepage-motion-execution-card is-score-${a?`complete`:`running`}`,children:[(0,E.jsx)(`header`,{className:`homepage-motion-panel-header`,children:(0,E.jsxs)(`div`,{className:`homepage-motion-panel-title`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,"aria-hidden":`true`,children:(0,E.jsx)(y,{})}),(0,E.jsx)(`h3`,{children:`Tutti is working on`})]})}),(0,E.jsxs)(`div`,{className:`homepage-motion-execution-body`,children:[(0,E.jsxs)(`div`,{className:`homepage-motion-execution-step homepage-motion-execution-stage is-${a?`complete`:`running`} has-marker`,children:[(0,E.jsx)(`span`,{className:`homepage-motion-execution-marker ${a?`is-done`:`is-running`}`,"aria-hidden":`true`,children:a?(0,E.jsx)(S,{}):(0,E.jsx)(n,{})}),(0,E.jsx)(`span`,{className:`homepage-motion-execution-step-content homepage-motion-execution-stage-copy`,children:(0,E.jsxs)(`span`,{className:`homepage-motion-execution-step-title`,children:[(0,E.jsx)(`span`,{className:`homepage-motion-execution-step-label`,children:i.label}),a?null:(0,E.jsx)(`span`,{className:`homepage-motion-execution-step-elapsed`,children:` (${I(e,o)})`})]})},i.id)]}),(0,E.jsx)(`div`,{className:`homepage-motion-running-score ${a?`is-complete`:``}`,role:`img`,"aria-label":`Ode to Joy score phrase`,children:(0,E.jsx)(`div`,{className:`homepage-motion-score-passage`,style:{"--score-translate-x":`${-395.3*c}px`},"aria-hidden":`true`,children:(0,E.jsx)(`img`,{src:t,alt:``,draggable:!1})})})]})]})})}function R({color:e,variant:t=`vase`,className:n=``}){let r={"--ceramic-color":e};return t===`cup`?(0,E.jsxs)(`svg`,{className:`ceramic-object is-cup ${n}`,viewBox:`0 0 260 260`,style:r,"aria-hidden":`true`,children:[(0,E.jsx)(`path`,{className:`ceramic-main`,d:`M54 64h132l-9 126c-2 26-20 42-46 42h-22c-26 0-44-16-46-42Z`}),(0,E.jsx)(`path`,{className:`ceramic-outline`,d:`M186 92h18c34 0 34 72 1 76h-27`}),(0,E.jsx)(`ellipse`,{className:`ceramic-rim`,cx:`120`,cy:`64`,rx:`66`,ry:`13`}),(0,E.jsx)(`path`,{className:`ceramic-highlight`,d:`M78 90c5 62 4 91 20 114`})]}):t===`bowl`?(0,E.jsxs)(`svg`,{className:`ceramic-object is-bowl ${n}`,viewBox:`0 0 300 220`,style:r,"aria-hidden":`true`,children:[(0,E.jsx)(`ellipse`,{className:`ceramic-rim`,cx:`150`,cy:`55`,rx:`116`,ry:`24`}),(0,E.jsx)(`path`,{className:`ceramic-main`,d:`M34 55c8 82 45 132 116 132S258 137 266 55c-42 25-190 25-232 0Z`}),(0,E.jsx)(`path`,{className:`ceramic-highlight`,d:`M72 82c18 45 40 70 70 82`}),(0,E.jsx)(`path`,{className:`ceramic-base`,d:`M112 185h76`})]}):(0,E.jsxs)(`svg`,{className:`ceramic-object is-vase ${n}`,viewBox:`0 0 320 420`,style:r,"aria-hidden":`true`,children:[(0,E.jsx)(`path`,{className:`ceramic-main`,d:`M116 56c7 38-17 61-36 96-31 56-28 157 5 199 33 42 117 42 150 0 33-42 36-143 5-199-19-35-43-58-36-96Z`}),(0,E.jsx)(`ellipse`,{className:`ceramic-rim`,cx:`160`,cy:`56`,rx:`44`,ry:`12`}),(0,E.jsx)(`ellipse`,{className:`ceramic-base`,cx:`160`,cy:`365`,rx:`66`,ry:`13`}),(0,E.jsx)(`path`,{className:`ceramic-highlight`,d:`M108 153c-25 64-23 145 1 185`})]})}function z({time:e}){let t=d(e,p.artifactClick,33.25),n=d(e,34.25,34.52),r=d(e,34.88,35.15),i=d(e,35.55,35.82),a=g(e),o=O(`#d9cbb4`,`#c56f4f`,n);e>=34.88&&(o=O(`#c56f4f`,`#6f9275`,r)),e>=35.55&&(o=O(`#6f9275`,`#243a46`,i));let s=e>=35.55?`ink`:e>=34.88?`sage`:e>=34.25?`clay`:null,c={"--artifact-scroll":a};return(0,E.jsxs)(`section`,{className:`scene-panel scene-artifact-panel`,style:D(t),children:[(0,E.jsxs)(`header`,{className:`scene-panel-header`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,children:(0,E.jsx)(l,{"aria-hidden":`true`})}),(0,E.jsx)(`h2`,{children:`Artifacts`}),(0,E.jsx)(`span`,{className:`scene-ready-tag`,children:`Ready`})]}),(0,E.jsx)(`div`,{className:`scene-artifact-canvas`,children:(0,E.jsxs)(`div`,{className:`artifact-page-frame`,style:c,children:[(0,E.jsxs)(`div`,{className:`artifact-page-nav`,children:[(0,E.jsx)(`strong`,{children:`Morrow`}),(0,E.jsx)(`span`,{children:`Objects · Journal · Studio`})]}),(0,E.jsxs)(`section`,{className:`artifact-hero`,children:[(0,E.jsxs)(`div`,{className:`artifact-hero-copy`,children:[(0,E.jsx)(`span`,{className:`artifact-eyebrow`,children:`Hand-finished in small batches`}),(0,E.jsx)(`h3`,{children:`Objects for slower days.`}),(0,E.jsx)(`p`,{children:`Quiet forms, warm glazes, and useful pieces made to live with.`}),(0,E.jsx)(`button`,{type:`button`,tabIndex:-1,children:`Explore the collection`})]}),(0,E.jsxs)(`div`,{className:`artifact-hero-object`,children:[(0,E.jsx)(R,{color:o}),(0,E.jsxs)(`div`,{className:`artifact-glaze-picker`,"aria-label":`Glaze preview`,children:[(0,E.jsx)(`span`,{children:`Glaze`}),(0,E.jsx)(`i`,{className:s===`clay`?`is-active is-clay`:`is-clay`}),(0,E.jsx)(`i`,{className:s===`sage`?`is-active is-sage`:`is-sage`}),(0,E.jsx)(`i`,{className:s===`ink`?`is-active is-ink`:`is-ink`})]})]})]}),(0,E.jsxs)(`section`,{className:`artifact-collection`,children:[(0,E.jsxs)(`header`,{children:[(0,E.jsx)(`span`,{children:`Selected pieces`}),(0,E.jsx)(`p`,{children:`Everyday forms shaped for the rituals around them.`})]}),(0,E.jsxs)(`div`,{className:`artifact-product-grid`,children:[(0,E.jsxs)(`article`,{children:[(0,E.jsx)(R,{color:`#b9785f`,variant:`cup`}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`strong`,{children:`Low cup`}),(0,E.jsx)(`span`,{children:`Rust glaze`})]})]}),(0,E.jsxs)(`article`,{children:[(0,E.jsx)(R,{color:`#819283`,variant:`vase`}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`strong`,{children:`Field vase`}),(0,E.jsx)(`span`,{children:`Sage glaze`})]})]}),(0,E.jsxs)(`article`,{children:[(0,E.jsx)(R,{color:`#d5c7af`,variant:`bowl`}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`strong`,{children:`Gather bowl`}),(0,E.jsx)(`span`,{children:`Flax glaze`})]})]})]})]}),(0,E.jsxs)(`section`,{className:`artifact-studio`,children:[(0,E.jsx)(`span`,{children:`Made by hand · Meant for every day`}),(0,E.jsx)(`h3`,{children:`Useful things can still feel special.`}),(0,E.jsx)(`p`,{children:`We make a small number of considered objects, slowly and close to home.`}),(0,E.jsx)(`button`,{type:`button`,tabIndex:-1,children:`Visit the studio`})]}),(0,E.jsxs)(`footer`,{className:`artifact-page-footer`,children:[(0,E.jsx)(`strong`,{children:`Morrow Ceramics`}),(0,E.jsx)(`span`,{className:`artifact-built-with`,children:`Built with Tutti.`}),(0,E.jsx)(`span`,{children:`Small batch · Est. 2026`})]})]})})]})}function B(e,t,n){return e+(t-e)*n}function V(e,t,n,r,i){let a=d(e,t,n,m);return{x:B(r.x,i.x,a),y:B(r.y,i.y,a)}}function H(e,t,n){return e<t||e>n?0:Math.sin(Math.PI*((e-t)/(n-t)))}function U(e){if(e>=p.runCursorStart&&e<18.92){let t=V(e,p.runCursorStart,p.runCursorArrive,{x:86,y:76},{x:94.25,y:94.2});return{visible:!0,x:t.x,y:t.y,clickPulse:H(e,p.runClickStart,p.runEnd)}}if(e>=31&&e<33.45){let t=V(e,31,32.22,{x:50,y:58},{x:4,y:22.85});return{visible:!0,x:t.x,y:t.y,clickPulse:H(e,32.28,32.6)}}return{visible:!1,x:50,y:50,clickPulse:0}}function W({time:e,assets:t}){let n=u(e),r=U(e),i=a(e),o=d(e,.08,1.05,m);return(0,E.jsx)(`div`,{className:`motion-scene`,role:`img`,"aria-label":`Tutti workflow animation from team discussion to a generated ceramics storefront`,style:{opacity:i,visibility:i<=.001?`hidden`:`visible`},children:(0,E.jsxs)(`div`,{className:`motion-stage`,style:{opacity:o,transform:`translate(50%, 50%) scale(${n.scale}) translate(${-n.centerX*100}%, ${-n.centerY*100}%)`},children:[(0,E.jsx)(M,{time:e,assets:t}),(0,E.jsxs)(`div`,{className:`scene-workspace-layer`,children:[(0,E.jsx)(N,{time:e,assets:t}),(0,E.jsx)(F,{time:e}),(0,E.jsx)(L,{time:e,scoreSrc:t.scoreSrc})]}),(0,E.jsx)(z,{time:e}),(0,E.jsxs)(`span`,{className:`scene-cursor ${r.visible?`is-visible`:``}`,style:{left:`${r.x}%`,top:`${r.y}%`,"--click-pulse":r.clickPulse},"aria-hidden":`true`,children:[(0,E.jsx)(w,{}),(0,E.jsx)(`i`,{})]})]})})}export{W as HomepageMotionScene};
|
|
1
|
+
import{C as e,S as t,_ as n,a as r,b as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"./index-BJpTcX_E.js";var w=e(`mouse-pointer-2`,[[`path`,{d:`M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z`,key:`edeuup`}]]),T=e(`send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),E=x();function D(e){return{"--reveal":e}}function O(e,t,n){let r=(e,t)=>Number.parseInt(e.slice(t,t+2),16),i=i=>Math.round(r(e,i)+(r(t,i)-r(e,i))*n).toString(16).padStart(2,`0`);return`#${i(1)}${i(3)}${i(5)}`}function k(e){let t=r((e-p.runEnd)/(p.executionComplete-p.runEnd));return t+t*t-t*t*t}function A({progress:e,children:t,className:n=``}){return(0,E.jsx)(`div`,{className:`scene-reveal ${n}`,style:D(e),children:t})}function j({progress:e,author:t,avatar:n,tone:r,timestamp:i,online:a=!1,assets:o,children:s}){return(0,E.jsxs)(`article`,{className:`scene-message`,style:D(e),children:[(0,E.jsx)(`span`,{className:`scene-avatar is-${r} ${n===`tutti`?`is-tutti`:``} ${a?`is-online`:``}`,"aria-hidden":`true`,children:n===`tutti`?(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:o.tuttiAvatarSrc,alt:``}):(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:o.humanAvatars[n],alt:``})}),(0,E.jsxs)(`div`,{className:`scene-message-copy`,children:[(0,E.jsxs)(`header`,{children:[(0,E.jsx)(`strong`,{children:t}),(0,E.jsx)(`span`,{children:i})]}),(0,E.jsx)(`p`,{children:s})]})]})}function M({time:e,assets:r}){let i=e>=p.artifactLive,a=e>=p.artifactClick,o=e>=p.runEnd&&e<p.executionComplete,u=d(e,p.runEnd,19.2),f=d(e,p.artifactLive,30.92);return(0,E.jsxs)(`aside`,{className:`scene-sidebar`,children:[(0,E.jsx)(`button`,{className:`scene-brand`,type:`button`,"aria-label":`Tutti`,tabIndex:-1,children:(0,E.jsx)(`img`,{src:r.logoSrc,alt:``})}),(0,E.jsxs)(`div`,{className:`scene-nav-stack`,children:[(0,E.jsxs)(`nav`,{className:`scene-nav`,"aria-label":`Workspace pages`,children:[(0,E.jsx)(`button`,{className:a?``:`is-active`,type:`button`,"aria-label":`Chat`,tabIndex:-1,children:(0,E.jsx)(c,{"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`Worklist`,tabIndex:-1,children:(0,E.jsx)(b,{"aria-hidden":`true`})}),(0,E.jsxs)(`button`,{className:a?`is-active`:``,type:`button`,"aria-label":`Artifacts`,tabIndex:-1,children:[(0,E.jsx)(l,{"aria-hidden":`true`}),i?(0,E.jsx)(`span`,{className:`scene-live-pill`,style:D(f),children:`Live`}):null]}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`References`,tabIndex:-1,children:(0,E.jsx)(C,{"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`Skills`,tabIndex:-1,children:(0,E.jsx)(s,{"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`Timeline`,tabIndex:-1,children:(0,E.jsx)(t,{"aria-hidden":`true`})})]}),o?(0,E.jsx)(`button`,{className:`scene-status-entry is-running`,style:D(u),type:`button`,"aria-label":`Task running`,tabIndex:-1,children:(0,E.jsx)(n,{"aria-hidden":`true`})}):null]}),(0,E.jsx)(`button`,{className:`scene-settings`,type:`button`,"aria-label":`Settings`,tabIndex:-1,children:(0,E.jsx)(h,{"aria-hidden":`true`})})]})}function N({time:e,assets:t}){let n=d(e,30.35,30.92);return(0,E.jsxs)(`section`,{className:`scene-panel scene-chat-panel`,children:[(0,E.jsxs)(`header`,{className:`scene-panel-header`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,children:(0,E.jsx)(c,{"aria-hidden":`true`})}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`h2`,{children:`Morrow Studio`}),(0,E.jsx)(`p`,{children:`Ceramics storefront`})]}),(0,E.jsxs)(`span`,{className:`scene-members`,"aria-hidden":`true`,children:[(0,E.jsx)(`i`,{className:`is-green`,children:(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:t.humanAvatars.fey,alt:``})}),(0,E.jsx)(`i`,{className:`is-blue`,children:(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:t.humanAvatars.avery,alt:``})}),(0,E.jsx)(`i`,{className:`is-yellow`,children:(0,E.jsx)(`img`,{className:`scene-avatar-image`,src:t.humanAvatars.jun,alt:``})})]})]}),(0,E.jsxs)(`div`,{className:`scene-chat-stream`,children:[(0,E.jsx)(j,{progress:d(e,1.35,2.05),author:`Fey`,avatar:`fey`,tone:`green`,timestamp:`10:12`,online:!0,assets:t,children:`🏺 Let's build an online shop for our ceramics studio.`}),(0,E.jsx)(j,{progress:d(e,2.65,3.35),author:`Avery`,avatar:`avery`,tone:`blue`,timestamp:`10:13`,online:!0,assets:t,children:`✨ Keep it warm, minimal, and editorial.`}),(0,E.jsx)(j,{progress:d(e,3.95,4.65),author:`Jun`,avatar:`jun`,tone:`yellow`,timestamp:`10:14`,online:!0,assets:t,children:`🎨 Let people preview every piece in different glazes.`}),(0,E.jsx)(j,{progress:d(e,5.25,5.95),author:`Tutti`,avatar:`tutti`,tone:`green`,timestamp:`10:15`,assets:t,children:`Got it — I'll put it together. ✨`}),(0,E.jsxs)(`article`,{className:`scene-task-result`,style:D(n),children:[(0,E.jsx)(`span`,{className:`scene-task-result-rail`,"aria-hidden":`true`}),(0,E.jsx)(`span`,{className:`scene-task-result-icon`,"aria-hidden":`true`,children:(0,E.jsx)(S,{})}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`span`,{children:`Task completed`}),(0,E.jsx)(`strong`,{children:`Build ceramics storefront`}),(0,E.jsx)(`small`,{children:`Storefront`}),(0,E.jsx)(`p`,{children:`Warm editorial shopping and glaze previews are ready in Artifacts.`})]}),(0,E.jsx)(i,{className:`scene-task-result-chevron`,"aria-hidden":`true`})]})]}),(0,E.jsxs)(`div`,{className:`scene-composer`,"aria-hidden":`true`,children:[(0,E.jsx)(`span`,{children:`Write a message`}),(0,E.jsx)(T,{})]})]})}function P({progress:e,icon:t,children:n}){return(0,E.jsxs)(`div`,{className:`scene-scratchpad-row`,style:D(e),children:[(0,E.jsx)(`span`,{"aria-hidden":`true`,children:t}),(0,E.jsx)(`p`,{children:n})]})}function F({time:e}){return(0,E.jsxs)(`section`,{className:`scene-panel scene-scratchpad-panel`,style:{"--scratchpad-collapse":d(e,p.runEnd,19.18)},children:[(0,E.jsxs)(`header`,{className:`scene-panel-header`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,children:(0,E.jsx)(v,{"aria-hidden":`true`})}),(0,E.jsx)(`h2`,{children:`Scratchpad`})]}),(0,E.jsxs)(`div`,{className:`scene-scratchpad-body`,children:[(0,E.jsxs)(A,{progress:d(e,8.95,10),className:`scene-scratchpad-intro`,children:[(0,E.jsx)(`h3`,{children:`Ceramics storefront`}),(0,E.jsx)(`p`,{children:`A warm, editorial storefront for a small-batch studio, centered on tactile product discovery.`})]}),(0,E.jsx)(A,{progress:d(e,9.85,10.65),className:`scene-scratchpad-label`,children:`Confirmed`}),(0,E.jsx)(P,{progress:d(e,10.4,11.25),icon:(0,E.jsx)(S,{}),children:`Product-first editorial layout with generous space`}),(0,E.jsx)(P,{progress:d(e,11.2,12.05),icon:(0,E.jsx)(S,{}),children:`Warm neutrals with quiet serif headlines`}),(0,E.jsx)(P,{progress:d(e,12,12.85),icon:(0,E.jsx)(S,{}),children:`Keep the collection small, curated, and story-led`}),(0,E.jsx)(A,{progress:d(e,12.75,13.55),className:`scene-scratchpad-label`,children:`Requested feature`}),(0,E.jsx)(P,{progress:d(e,13.3,14.2),icon:(0,E.jsx)(o,{}),children:`Preview every piece in clay, sage, and ink glazes`})]}),(0,E.jsxs)(`footer`,{className:`scene-scratchpad-footer`,style:D(d(e,15.7,16.8)),children:[(0,E.jsxs)(`span`,{className:`scene-writing-mark`,children:[(0,E.jsx)(`strong`,{children:`Updated`}),(0,E.jsx)(`span`,{children:`just now`})]}),(0,E.jsxs)(`button`,{className:`scene-run-button`,type:`button`,tabIndex:-1,children:[(0,E.jsx)(f,{"aria-hidden":`true`}),(0,E.jsx)(`span`,{children:`Run`})]})]})]})}function I(e,t){let n=Math.max(0,Math.floor((e-t)*50/5)*5);return`${Math.floor(n/60)}m${(n%60).toString().padStart(2,`0`)}s`}function L({time:e,scoreSrc:t}){let r=d(e,18.92,19.2),i=_(e),a=i.id===`complete`,o={prepare:p.runEnd,implement:p.executionPrepareEnd,validate:p.executionImplementEnd,update:p.executionValidateEnd,complete:p.executionComplete}[i.id],s=d(e,o,o+.32),c=k(e);return(0,E.jsx)(`div`,{className:`scene-execution`,style:{"--reveal":r,"--stage-reveal":s},children:(0,E.jsxs)(`article`,{className:`homepage-motion-panel homepage-motion-execution-panel homepage-motion-execution-card is-score-${a?`complete`:`running`}`,children:[(0,E.jsx)(`header`,{className:`homepage-motion-panel-header`,children:(0,E.jsxs)(`div`,{className:`homepage-motion-panel-title`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,"aria-hidden":`true`,children:(0,E.jsx)(y,{})}),(0,E.jsx)(`h3`,{children:`Tutti is working on`})]})}),(0,E.jsxs)(`div`,{className:`homepage-motion-execution-body`,children:[(0,E.jsxs)(`div`,{className:`homepage-motion-execution-step homepage-motion-execution-stage is-${a?`complete`:`running`} has-marker`,children:[(0,E.jsx)(`span`,{className:`homepage-motion-execution-marker ${a?`is-done`:`is-running`}`,"aria-hidden":`true`,children:a?(0,E.jsx)(S,{}):(0,E.jsx)(n,{})}),(0,E.jsx)(`span`,{className:`homepage-motion-execution-step-content homepage-motion-execution-stage-copy`,children:(0,E.jsxs)(`span`,{className:`homepage-motion-execution-step-title`,children:[(0,E.jsx)(`span`,{className:`homepage-motion-execution-step-label`,children:i.label}),a?null:(0,E.jsx)(`span`,{className:`homepage-motion-execution-step-elapsed`,children:` (${I(e,o)})`})]})},i.id)]}),(0,E.jsx)(`div`,{className:`homepage-motion-running-score ${a?`is-complete`:``}`,role:`img`,"aria-label":`Ode to Joy score phrase`,children:(0,E.jsx)(`div`,{className:`homepage-motion-score-passage`,style:{"--score-translate-x":`${-395.3*c}px`},"aria-hidden":`true`,children:(0,E.jsx)(`img`,{src:t,alt:``,draggable:!1})})})]})]})})}function R({color:e,variant:t=`vase`,className:n=``}){let r={"--ceramic-color":e};return t===`cup`?(0,E.jsxs)(`svg`,{className:`ceramic-object is-cup ${n}`,viewBox:`0 0 260 260`,style:r,"aria-hidden":`true`,children:[(0,E.jsx)(`path`,{className:`ceramic-main`,d:`M54 64h132l-9 126c-2 26-20 42-46 42h-22c-26 0-44-16-46-42Z`}),(0,E.jsx)(`path`,{className:`ceramic-outline`,d:`M186 92h18c34 0 34 72 1 76h-27`}),(0,E.jsx)(`ellipse`,{className:`ceramic-rim`,cx:`120`,cy:`64`,rx:`66`,ry:`13`}),(0,E.jsx)(`path`,{className:`ceramic-highlight`,d:`M78 90c5 62 4 91 20 114`})]}):t===`bowl`?(0,E.jsxs)(`svg`,{className:`ceramic-object is-bowl ${n}`,viewBox:`0 0 300 220`,style:r,"aria-hidden":`true`,children:[(0,E.jsx)(`ellipse`,{className:`ceramic-rim`,cx:`150`,cy:`55`,rx:`116`,ry:`24`}),(0,E.jsx)(`path`,{className:`ceramic-main`,d:`M34 55c8 82 45 132 116 132S258 137 266 55c-42 25-190 25-232 0Z`}),(0,E.jsx)(`path`,{className:`ceramic-highlight`,d:`M72 82c18 45 40 70 70 82`}),(0,E.jsx)(`path`,{className:`ceramic-base`,d:`M112 185h76`})]}):(0,E.jsxs)(`svg`,{className:`ceramic-object is-vase ${n}`,viewBox:`0 0 320 420`,style:r,"aria-hidden":`true`,children:[(0,E.jsx)(`path`,{className:`ceramic-main`,d:`M116 56c7 38-17 61-36 96-31 56-28 157 5 199 33 42 117 42 150 0 33-42 36-143 5-199-19-35-43-58-36-96Z`}),(0,E.jsx)(`ellipse`,{className:`ceramic-rim`,cx:`160`,cy:`56`,rx:`44`,ry:`12`}),(0,E.jsx)(`ellipse`,{className:`ceramic-base`,cx:`160`,cy:`365`,rx:`66`,ry:`13`}),(0,E.jsx)(`path`,{className:`ceramic-highlight`,d:`M108 153c-25 64-23 145 1 185`})]})}function z({time:e}){let t=d(e,p.artifactClick,33.25),n=d(e,34.25,34.52),r=d(e,34.88,35.15),i=d(e,35.55,35.82),a=g(e),o=O(`#d9cbb4`,`#c56f4f`,n);e>=34.88&&(o=O(`#c56f4f`,`#6f9275`,r)),e>=35.55&&(o=O(`#6f9275`,`#243a46`,i));let s=e>=35.55?`ink`:e>=34.88?`sage`:e>=34.25?`clay`:null,c={"--artifact-scroll":a};return(0,E.jsxs)(`section`,{className:`scene-panel scene-artifact-panel`,style:D(t),children:[(0,E.jsxs)(`header`,{className:`scene-panel-header`,children:[(0,E.jsx)(`span`,{className:`scene-icon-tile`,children:(0,E.jsx)(l,{"aria-hidden":`true`})}),(0,E.jsx)(`h2`,{children:`Artifacts`}),(0,E.jsx)(`span`,{className:`scene-ready-tag`,children:`Ready`})]}),(0,E.jsx)(`div`,{className:`scene-artifact-canvas`,children:(0,E.jsxs)(`div`,{className:`artifact-page-frame`,style:c,children:[(0,E.jsxs)(`div`,{className:`artifact-page-nav`,children:[(0,E.jsx)(`strong`,{children:`Morrow`}),(0,E.jsx)(`span`,{children:`Objects · Journal · Studio`})]}),(0,E.jsxs)(`section`,{className:`artifact-hero`,children:[(0,E.jsxs)(`div`,{className:`artifact-hero-copy`,children:[(0,E.jsx)(`span`,{className:`artifact-eyebrow`,children:`Hand-finished in small batches`}),(0,E.jsx)(`h3`,{children:`Objects for slower days.`}),(0,E.jsx)(`p`,{children:`Quiet forms, warm glazes, and useful pieces made to live with.`}),(0,E.jsx)(`button`,{type:`button`,tabIndex:-1,children:`Explore the collection`})]}),(0,E.jsxs)(`div`,{className:`artifact-hero-object`,children:[(0,E.jsx)(R,{color:o}),(0,E.jsxs)(`div`,{className:`artifact-glaze-picker`,"aria-label":`Glaze preview`,children:[(0,E.jsx)(`span`,{children:`Glaze`}),(0,E.jsx)(`i`,{className:s===`clay`?`is-active is-clay`:`is-clay`}),(0,E.jsx)(`i`,{className:s===`sage`?`is-active is-sage`:`is-sage`}),(0,E.jsx)(`i`,{className:s===`ink`?`is-active is-ink`:`is-ink`})]})]})]}),(0,E.jsxs)(`section`,{className:`artifact-collection`,children:[(0,E.jsxs)(`header`,{children:[(0,E.jsx)(`span`,{children:`Selected pieces`}),(0,E.jsx)(`p`,{children:`Everyday forms shaped for the rituals around them.`})]}),(0,E.jsxs)(`div`,{className:`artifact-product-grid`,children:[(0,E.jsxs)(`article`,{children:[(0,E.jsx)(R,{color:`#b9785f`,variant:`cup`}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`strong`,{children:`Low cup`}),(0,E.jsx)(`span`,{children:`Rust glaze`})]})]}),(0,E.jsxs)(`article`,{children:[(0,E.jsx)(R,{color:`#819283`,variant:`vase`}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`strong`,{children:`Field vase`}),(0,E.jsx)(`span`,{children:`Sage glaze`})]})]}),(0,E.jsxs)(`article`,{children:[(0,E.jsx)(R,{color:`#d5c7af`,variant:`bowl`}),(0,E.jsxs)(`div`,{children:[(0,E.jsx)(`strong`,{children:`Gather bowl`}),(0,E.jsx)(`span`,{children:`Flax glaze`})]})]})]})]}),(0,E.jsxs)(`section`,{className:`artifact-studio`,children:[(0,E.jsx)(`span`,{children:`Made by hand · Meant for every day`}),(0,E.jsx)(`h3`,{children:`Useful things can still feel special.`}),(0,E.jsx)(`p`,{children:`We make a small number of considered objects, slowly and close to home.`}),(0,E.jsx)(`button`,{type:`button`,tabIndex:-1,children:`Visit the studio`})]}),(0,E.jsxs)(`footer`,{className:`artifact-page-footer`,children:[(0,E.jsx)(`strong`,{children:`Morrow Ceramics`}),(0,E.jsx)(`span`,{className:`artifact-built-with`,children:`Built with Tutti.`}),(0,E.jsx)(`span`,{children:`Small batch · Est. 2026`})]})]})})]})}function B(e,t,n){return e+(t-e)*n}function V(e,t,n,r,i){let a=d(e,t,n,m);return{x:B(r.x,i.x,a),y:B(r.y,i.y,a)}}function H(e,t,n){return e<t||e>n?0:Math.sin(Math.PI*((e-t)/(n-t)))}function U(e){if(e>=p.runCursorStart&&e<18.92){let t=V(e,p.runCursorStart,p.runCursorArrive,{x:86,y:76},{x:94.25,y:94.2});return{visible:!0,x:t.x,y:t.y,clickPulse:H(e,p.runClickStart,p.runEnd)}}if(e>=31&&e<33.45){let t=V(e,31,32.22,{x:50,y:58},{x:4,y:22.85});return{visible:!0,x:t.x,y:t.y,clickPulse:H(e,32.28,32.6)}}return{visible:!1,x:50,y:50,clickPulse:0}}function W({time:e,assets:t}){let n=u(e),r=U(e),i=a(e),o=d(e,.08,1.05,m);return(0,E.jsx)(`div`,{className:`motion-scene`,role:`img`,"aria-label":`Tutti workflow animation from team discussion to a generated ceramics storefront`,style:{opacity:i,visibility:i<=.001?`hidden`:`visible`},children:(0,E.jsxs)(`div`,{className:`motion-stage`,style:{opacity:o,transform:`translate(50%, 50%) scale(${n.scale}) translate(${-n.centerX*100}%, ${-n.centerY*100}%)`},children:[(0,E.jsx)(M,{time:e,assets:t}),(0,E.jsxs)(`div`,{className:`scene-workspace-layer`,children:[(0,E.jsx)(N,{time:e,assets:t}),(0,E.jsx)(F,{time:e}),(0,E.jsx)(L,{time:e,scoreSrc:t.scoreSrc})]}),(0,E.jsx)(z,{time:e}),(0,E.jsxs)(`span`,{className:`scene-cursor ${r.visible?`is-visible`:``}`,style:{left:`${r.x}%`,top:`${r.y}%`,"--click-pulse":r.clickPulse},"aria-hidden":`true`,children:[(0,E.jsx)(w,{}),(0,E.jsx)(`i`,{})]})]})})}export{W as HomepageMotionScene};
|