@xfey/tutti 0.1.46 → 0.1.48

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/README.md CHANGED
@@ -49,5 +49,5 @@
49
49
  - Reference 和 Skills 上传的最终写入由 Host Project API 完成;Host 只通过 Relay host-control resolve 获取短期 R2 URL 下载 staged object,不接收浏览器 base64 文件正文,也不持久化 presigned URL。
50
50
  - Host Project API command 的 browser session context 只信任 Relay tunnel metadata 中的 `relay_session_context`;route handler 不读取浏览器身份 header,也不接受 payload 内身份字段。
51
51
  - Fastify request log、debug log、SSE payload、activity event 和 run result 都必须经过 redaction,不得泄露 provider secret、host registration secret、host connection token、join token、cookie、host 绝对路径或 run workspace path。
52
- - Repo Viewer path policy 已以可测试 helper 落地并接入 Phase 6 Viewer / Reference Files APIPhase 7 checks executor 已以 safe npm script discovery、结构化 `spawn`、timeout、stdout / stderr redaction 与 no-shell 执行为首批命令安全边界。
52
+ - Repo path metadata policy 与更严格的 Viewer content path policy 已拆分为可测试 helperPhase 6 Viewer / Reference Files API 继续拒绝 secret-like 内容读取,Run / merge changed paths 可以完整报告结构性安全的 repo-relative 文件名;Phase 7 checks executor 已以 safe npm script discovery、结构化 `spawn`、timeout、stdout / stderr redaction 与 no-shell 执行为首批命令安全边界。
53
53
  - 当前源码 package root 仍是 `apps/server`;对外 npm 名由 staging artifact 使用 `@xfey/tutti`,internal workspace dependency bundling、对应外部 runtime dependency bundling 和 packed CLI install smoke 已接通。
@@ -365,11 +365,7 @@ export function readMainChatMessageWindow(db, options = {}) {
365
365
  .all(target.created_at, target.created_at, target.id, afterLimit + 1);
366
366
  hasOlder = olderRows.length > beforeLimit;
367
367
  hasNewer = newerRows.length > afterLimit;
368
- rows = [
369
- ...olderRows.slice(0, beforeLimit).reverse(),
370
- target,
371
- ...newerRows.slice(0, afterLimit),
372
- ];
368
+ rows = [...olderRows.slice(0, beforeLimit).reverse(), target, ...newerRows.slice(0, afterLimit)];
373
369
  }
374
370
  }
375
371
  else if (before !== null) {
@@ -9,14 +9,16 @@ export type LaunchConfirmationHandler = (request: LaunchConfirmationRequest) =>
9
9
  export type GitBootstrapOptions = {
10
10
  workspaceRoot: string;
11
11
  projectId: ProjectId;
12
+ projectSetup: "create" | "resume";
12
13
  assumeYes?: boolean;
13
14
  confirm?: LaunchConfirmationHandler;
14
15
  prepareBootstrapFiles?: () => void;
15
16
  };
16
17
  export type GitBootstrapResult = {
17
18
  branch: typeof TUTTI_MAINLINE_BRANCH;
19
+ mode: "bootstrap_baseline" | "adopt_existing" | "resume_existing";
18
20
  initialized_git: boolean;
19
- bootstrap_commit_created: boolean;
21
+ baseline_commit_created: boolean;
20
22
  };
21
23
  export declare function assertSafeProjectRoot(workspaceRoot: string): void;
22
24
  export declare function listSensitiveBootstrapCandidates(workspaceRoot: string): string[];
@@ -180,11 +180,12 @@ export function listSensitiveBootstrapCandidates(workspaceRoot) {
180
180
  .filter((file) => !isGitIgnored(workspaceRoot, file))
181
181
  .sort();
182
182
  }
183
- function commitBootstrapIfNeeded(workspaceRoot, projectId) {
183
+ function commitBaselineIfNeeded(workspaceRoot, projectId, kind) {
184
184
  if (!hasUncommittedChanges(workspaceRoot)) {
185
185
  return false;
186
186
  }
187
187
  runGit(["add", "--all"], workspaceRoot);
188
+ const title = kind === "adoption" ? "Tutti: adopt existing workspace" : "Tutti: initialize project";
188
189
  runGit([
189
190
  "-c",
190
191
  "user.name=Tutti",
@@ -192,7 +193,7 @@ function commitBootstrapIfNeeded(workspaceRoot, projectId) {
192
193
  "user.email=tutti@example.invalid",
193
194
  "commit",
194
195
  "-m",
195
- `Tutti: initialize project\n\nProject: ${projectId}\nBranch: ${TUTTI_MAINLINE_BRANCH}`,
196
+ `${title}\n\nProject: ${projectId}\nBranch: ${TUTTI_MAINLINE_BRANCH}`,
196
197
  ], workspaceRoot);
197
198
  return true;
198
199
  }
@@ -208,7 +209,7 @@ async function ensureGitRepository(options) {
208
209
  }, options);
209
210
  }
210
211
  runGit(["init", "-b", TUTTI_MAINLINE_BRANCH], workspaceRoot);
211
- return true;
212
+ return { initializedGit: true, hadHead: false };
212
213
  }
213
214
  if (!rootHasGitDirectory && gitTopLevel !== workspaceRoot) {
214
215
  await requireConfirmation({
@@ -216,31 +217,44 @@ async function ensureGitRepository(options) {
216
217
  message: "This directory appears to be inside another Git repository. Tutti will initialize a nested Git repository here.",
217
218
  }, options);
218
219
  runGit(["init", "-b", TUTTI_MAINLINE_BRANCH], workspaceRoot);
219
- return true;
220
+ return { initializedGit: true, hadHead: false };
220
221
  }
221
- if (hasUncommittedChanges(workspaceRoot)) {
222
+ const repositoryHadHead = hasHead(workspaceRoot);
223
+ if (options.projectSetup === "create" && hasUncommittedChanges(workspaceRoot)) {
222
224
  await requireConfirmation({
223
225
  kind: "uncommitted_changes",
224
226
  message: "Current repository has uncommitted changes. Tutti can commit the current working tree as the initial Tutti baseline.",
225
227
  }, options);
226
228
  }
227
229
  switchToTuttiMainline(workspaceRoot);
228
- return false;
230
+ return { initializedGit: false, hadHead: repositoryHadHead };
229
231
  }
230
232
  export async function ensureGitBootstrap(options) {
231
233
  const workspaceRoot = resolve(options.workspaceRoot);
232
234
  assertSafeProjectRoot(workspaceRoot);
233
- const initializedGit = await ensureGitRepository({ ...options, workspaceRoot });
234
- ensureMinimalGitignore(workspaceRoot);
235
- const sensitiveCandidates = listSensitiveBootstrapCandidates(workspaceRoot);
236
- if (sensitiveCandidates.length > 0) {
237
- throw new LaunchError("sensitive_file_detected", `Sensitive-like file would enter bootstrap commit: ${sensitiveCandidates[0]}`, "Move or ignore sensitive files before launching. `--yes` cannot bypass this check.");
235
+ const repository = await ensureGitRepository({ ...options, workspaceRoot });
236
+ const createsBootstrapBaseline = repository.initializedGit || !repository.hadHead;
237
+ const adoptsExistingRepository = repository.hadHead && options.projectSetup === "create";
238
+ const mode = createsBootstrapBaseline
239
+ ? "bootstrap_baseline"
240
+ : adoptsExistingRepository
241
+ ? "adopt_existing"
242
+ : "resume_existing";
243
+ if (createsBootstrapBaseline) {
244
+ ensureMinimalGitignore(workspaceRoot);
245
+ const sensitiveCandidates = listSensitiveBootstrapCandidates(workspaceRoot);
246
+ if (sensitiveCandidates.length > 0) {
247
+ throw new LaunchError("sensitive_file_detected", `Sensitive-like file would enter bootstrap commit: ${sensitiveCandidates[0]}`, "Move or ignore sensitive files before launching. `--yes` cannot bypass this check.");
248
+ }
238
249
  }
239
250
  options.prepareBootstrapFiles?.();
240
251
  return {
241
252
  branch: TUTTI_MAINLINE_BRANCH,
242
- initialized_git: initializedGit,
243
- bootstrap_commit_created: commitBootstrapIfNeeded(workspaceRoot, options.projectId),
253
+ mode,
254
+ initialized_git: repository.initializedGit,
255
+ baseline_commit_created: createsBootstrapBaseline || adoptsExistingRepository
256
+ ? commitBaselineIfNeeded(workspaceRoot, options.projectId, adoptsExistingRepository ? "adoption" : "bootstrap")
257
+ : false,
244
258
  };
245
259
  }
246
260
  //# sourceMappingURL=git-bootstrap.js.map
@@ -55,7 +55,10 @@ export async function prepareLaunchProject(options) {
55
55
  const gitOptions = {
56
56
  workspaceRoot,
57
57
  projectId,
58
- prepareBootstrapFiles: () => writeProjectIdentity(workspaceRoot, projectId),
58
+ projectSetup: projectIdentityCreated ? "create" : "resume",
59
+ ...(projectIdentityCreated
60
+ ? { prepareBootstrapFiles: () => writeProjectIdentity(workspaceRoot, projectId) }
61
+ : {}),
59
62
  };
60
63
  if (options.yes !== undefined) {
61
64
  gitOptions.assumeYes = options.yes;
@@ -1,5 +1,5 @@
1
1
  import { type ProjectId, type RelayProjectRef } from "@tutti/shared/ids";
2
- import { type ConfigureProjectOpenAiProviderResult } from "../../providers/openai/index.js";
2
+ import { type ConfigureProjectOpenAiProviderResult, type OpenAiProviderCredentialValidator } from "../../providers/openai/index.js";
3
3
  import { type FetchLike } from "../cli/host-runtime-endpoint.js";
4
4
  import { type RuntimeProjectRow } from "../cli/runtime-commands.js";
5
5
  import { type LocalConsoleInvocationContext } from "./invocation-context.js";
@@ -54,6 +54,7 @@ export declare class LocalConsoleProjectService {
54
54
  tuttiHome: string;
55
55
  serviceEnvironment?: NodeJS.ProcessEnv;
56
56
  fetchImpl?: FetchLike;
57
+ validateProviderCredential?: OpenAiProviderCredentialValidator;
57
58
  });
58
59
  listProjects(context: LocalConsoleInvocationContext): Promise<LocalConsoleProject[]>;
59
60
  discoverModels(input: {
@@ -1,10 +1,10 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { relayProjectRouteSegment } from "@tutti/shared/ids";
4
- import { configureProjectOpenAiProvider, discoverOpenAiModels, readOpenAiProviderConfigProjection, } from "../../providers/openai/index.js";
4
+ import { configureProjectOpenAiProvider, discoverOpenAiModels, isOpenAiApiKey, readOpenAiProviderConfigProjection, validateOpenAiCredential, } from "../../providers/openai/index.js";
5
5
  import { formatCliErrorReason, LaunchError } from "../cli/errors.js";
6
6
  import { createRuntimeEndpointProbe, waitForHostShutdown, } from "../cli/host-runtime-endpoint.js";
7
- import { prepareLaunchProject, resolveRelayUrl } from "../cli/launch.js";
7
+ import { prepareLaunchProject, resolveLaunchLocalContext, resolveRelayUrl } from "../cli/launch.js";
8
8
  import { requestHostLocalIdleShutdown, rotateHostLocalInvite, } from "../cli/local-control-client.js";
9
9
  import { readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "../cli/machine-local.js";
10
10
  import { spawnDetachedHost, waitForManagedHostReady } from "../cli/managed-host.js";
@@ -152,12 +152,15 @@ export class LocalConsoleProjectService {
152
152
  #tuttiHome;
153
153
  #serviceEnvironment;
154
154
  #fetchImpl;
155
+ #validateProviderCredential;
155
156
  #launches = new Map();
156
157
  #mutationTail = Promise.resolve();
157
158
  constructor(options) {
158
159
  this.#tuttiHome = resolve(options.tuttiHome);
159
160
  this.#serviceEnvironment = options.serviceEnvironment ?? process.env;
160
161
  this.#fetchImpl = options.fetchImpl ?? fetch;
162
+ this.#validateProviderCredential =
163
+ options.validateProviderCredential ?? validateOpenAiCredential;
161
164
  }
162
165
  #operationOptions(context) {
163
166
  return {
@@ -243,6 +246,47 @@ export class LocalConsoleProjectService {
243
246
  apiKey: provider.api_key,
244
247
  });
245
248
  }
249
+ async #validateLaunchProvider(input) {
250
+ let validation;
251
+ try {
252
+ validation = isOpenAiApiKey(input.api_key)
253
+ ? await this.#validateProviderCredential({
254
+ apiKey: input.api_key,
255
+ apiBaseUrl: input.base_url,
256
+ defaultModel: input.model,
257
+ })
258
+ : {
259
+ kind: "failed",
260
+ reason: "provider_auth_invalid",
261
+ retryable: false,
262
+ };
263
+ }
264
+ catch (error) {
265
+ throw this.#normalizeError(error);
266
+ }
267
+ if (validation.kind === "failed") {
268
+ const failure = {
269
+ kind: "validation_failed",
270
+ reason: validation.reason,
271
+ retryable: validation.retryable,
272
+ ...(validation.diagnostic === undefined ? {} : { diagnostic: validation.diagnostic }),
273
+ };
274
+ throw new LocalConsoleProjectError(failure.reason, localConsoleProviderValidationMessage(failure));
275
+ }
276
+ return { input, validation };
277
+ }
278
+ #requireStoredLaunchProvider(workspacePath, operation) {
279
+ const context = resolveLaunchLocalContext({ workspacePath, ...operation });
280
+ const provider = context.existing_identity.kind === "present"
281
+ ? readOpenAiProviderConfigProjection({
282
+ tuttiHome: context.tutti_home,
283
+ projectId: context.existing_identity.project_id,
284
+ })
285
+ : { status: "not_configured" };
286
+ if (provider.status !== "configured") {
287
+ throw new LocalConsoleProjectError("provider_configuration_required", "This project does not have a valid saved Provider. Configure it in the launch form.");
288
+ }
289
+ }
246
290
  async launchProject(input, context) {
247
291
  const workspacePath = validateWorkspacePath(input.workspacePath);
248
292
  const active = this.#launches.get(workspacePath);
@@ -263,6 +307,15 @@ export class LocalConsoleProjectService {
263
307
  }
264
308
  async #launchProject(input, context) {
265
309
  const operation = this.#operationOptions(context);
310
+ if (input.provider === undefined) {
311
+ try {
312
+ this.#requireStoredLaunchProvider(input.workspacePath, operation);
313
+ }
314
+ catch (error) {
315
+ throw this.#normalizeError(error);
316
+ }
317
+ }
318
+ const provider = input.provider === undefined ? undefined : await this.#validateLaunchProvider(input.provider);
266
319
  let preparation;
267
320
  try {
268
321
  preparation = await prepareLaunchProject({
@@ -282,13 +335,14 @@ export class LocalConsoleProjectService {
282
335
  catch (error) {
283
336
  throw this.#normalizeError(error);
284
337
  }
285
- if (input.provider !== undefined) {
338
+ if (provider !== undefined) {
286
339
  const configured = await configureProjectOpenAiProvider({
287
340
  tuttiHome: preparation.tutti_home,
288
341
  projectId: preparation.project_id,
289
- apiBaseUrl: input.provider.base_url,
290
- apiKey: input.provider.api_key,
291
- defaultModel: input.provider.model,
342
+ apiBaseUrl: provider.input.base_url,
343
+ apiKey: provider.input.api_key,
344
+ defaultModel: provider.input.model,
345
+ validateCredential: () => Promise.resolve(provider.validation),
292
346
  });
293
347
  if (configured.kind === "validation_failed") {
294
348
  throw new LocalConsoleProjectError(configured.reason, localConsoleProviderValidationMessage(configured));
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
3
  import { join } from "node:path";
4
- import { classifyViewerPath } from "@tutti/shared/utils";
4
+ import { classifyRepositoryPathMetadata } from "@tutti/shared/utils";
5
5
  import { WorkspaceOpsError } from "./errors.js";
6
6
  import { runGit, runGitRaw, runGitText, stagedChangesExist, summarizeGitFailure, tryGitText, } from "./git.js";
7
7
  import { requireMainlineWriteReady } from "./mainline.js";
@@ -54,8 +54,12 @@ function parsePorcelainChangedPaths(output) {
54
54
  for (const entry of entries) {
55
55
  const pathText = entry.slice(3);
56
56
  const path = pathText.includes(" -> ") ? pathText.split(" -> ").at(-1) : pathText;
57
- if (path !== undefined && classifyViewerPath(path).kind === "allowed") {
58
- paths.add(path);
57
+ if (path === undefined) {
58
+ continue;
59
+ }
60
+ const classification = classifyRepositoryPathMetadata(path);
61
+ if (classification.kind === "allowed" && classification.path_kind === "project_relative") {
62
+ paths.add(classification.normalized_path);
59
63
  }
60
64
  }
61
65
  return [...paths].sort((left, right) => left.localeCompare(right, "en"));
@@ -1,13 +1,22 @@
1
- export type ViewerPathRejectReason = "absolute_path" | "parent_traversal" | "private_tutti_path" | "git_path" | "secret_like_path" | "control_character";
2
- export type ViewerPathClassification = {
1
+ export type RepositoryPathMetadataRejectReason = "absolute_path" | "parent_traversal" | "private_tutti_path" | "git_path" | "control_character";
2
+ export type RepositoryPathMetadataClassification = {
3
3
  kind: "allowed";
4
4
  normalized_path: string;
5
5
  path_kind: "root" | "project_relative";
6
6
  } | {
7
+ kind: "rejected";
8
+ reason_code: RepositoryPathMetadataRejectReason;
9
+ normalized_path?: string;
10
+ };
11
+ export type ViewerPathRejectReason = RepositoryPathMetadataRejectReason | "secret_like_path";
12
+ export type ViewerPathClassification = Extract<RepositoryPathMetadataClassification, {
13
+ kind: "allowed";
14
+ }> | {
7
15
  kind: "rejected";
8
16
  reason_code: ViewerPathRejectReason;
9
17
  normalized_path?: string;
10
18
  };
19
+ export declare function classifyRepositoryPathMetadata(path: string): RepositoryPathMetadataClassification;
11
20
  export declare function classifyViewerPath(path: string): ViewerPathClassification;
12
21
  export declare function isViewerPathAllowed(path: string): boolean;
13
22
  //# sourceMappingURL=index.d.ts.map
@@ -36,19 +36,18 @@ function hasControlCharacter(value) {
36
36
  }
37
37
  return false;
38
38
  }
39
- export function classifyViewerPath(path) {
39
+ export function classifyRepositoryPathMetadata(path) {
40
40
  if (hasControlCharacter(path)) {
41
41
  return { kind: "rejected", reason_code: "control_character" };
42
42
  }
43
- const trimmed = path.trim();
44
- if (trimmed === "" || trimmed === ".") {
43
+ if (path === "" || path === ".") {
45
44
  return { kind: "allowed", normalized_path: "", path_kind: "root" };
46
45
  }
47
- if (isAbsolutePath(trimmed)) {
46
+ if (isAbsolutePath(path)) {
48
47
  return { kind: "rejected", reason_code: "absolute_path" };
49
48
  }
50
49
  const segments = [];
51
- for (const segment of trimmed.replaceAll("\\", "/").split("/")) {
50
+ for (const segment of path.replaceAll("\\", "/").split("/")) {
52
51
  if (segment === "" || segment === ".") {
53
52
  continue;
54
53
  }
@@ -62,9 +61,6 @@ export function classifyViewerPath(path) {
62
61
  if (lower === ".tutti") {
63
62
  return { kind: "rejected", reason_code: "private_tutti_path" };
64
63
  }
65
- if (isSecretLikeSegment(segment)) {
66
- return { kind: "rejected", reason_code: "secret_like_path" };
67
- }
68
64
  segments.push(segment);
69
65
  }
70
66
  const normalizedPath = segments.join("/");
@@ -74,6 +70,22 @@ export function classifyViewerPath(path) {
74
70
  path_kind: normalizedPath === "" ? "root" : "project_relative",
75
71
  };
76
72
  }
73
+ export function classifyViewerPath(path) {
74
+ if (hasControlCharacter(path)) {
75
+ return { kind: "rejected", reason_code: "control_character" };
76
+ }
77
+ const classification = classifyRepositoryPathMetadata(path.trim());
78
+ if (classification.kind === "rejected") {
79
+ return classification;
80
+ }
81
+ if (classification.normalized_path.split("/").some(isSecretLikeSegment)) {
82
+ return {
83
+ kind: "rejected",
84
+ reason_code: "secret_like_path",
85
+ };
86
+ }
87
+ return classification;
88
+ }
77
89
  export function isViewerPathAllowed(path) {
78
90
  return classifyViewerPath(path).kind === "allowed";
79
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none" shape-rendering="auto" aria-hidden="true"><!-- Generated by DiceBear (https://dicebear.com) --><metadata xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/"><rdf:RDF><rdf:Description><dc:title>Dylan! The Avatar Generator</dc:title><dc:creator>Natalia Spivak</dc:creator><dc:source xsi:type="dcterms:URI">https://www.figma.com/community/file/1356575240759683500</dc:source><dcterms:license xsi:type="dcterms:URI">https://creativecommons.org/licenses/by/4.0/</dcterms:license><dc:rights>Remix of “Dylan! The Avatar Generator” (https://www.figma.com/community/file/1356575240759683500) by “Natalia Spivak”, licensed under “CC BY 4.0” (https://creativecommons.org/licenses/by/4.0/)</dc:rights></rdf:Description></rdf:RDF></metadata><defs><g id="mood-hopeful-181c8200"><path d="M7.88 15.65c-1.18 1.26-2.17 2.74-2.36 4.49a4.4 4.4 0 0 0 1.78 4.03c1.26.9 2.88.8 4.07-.13a5.5 5.5 0 0 0 1.87-3.88c.1-.97-1.46-1.38-1.86-.5-.75 1.63.16 3.26 1.34 4.42a3.9 3.9 0 0 0 4.13.98 4.6 4.6 0 0 0 2.93-3.17 6.6 6.6 0 0 0-.82-4.87c-.64-1.1-2.37-.1-1.73 1.01.92 1.6 1.18 4-.71 5-.68.37-1.48.38-2.1-.1-.5-.38-1.64-1.54-1.31-2.25l-1.86-.5c-.14 1.34-1.44 3.63-2.97 2.23-1.74-1.59-.22-4 1.03-5.33.9-.94-.5-2.36-1.4-1.4z" fill="black"/></g><g id="hair-longCurls-181c8200"><path d="M21.73 17.15c-.23.95-.21 2.72 0 3.67s1.46 1.17 2.44 1.15c1 0 1.75-.93 2.22-1.83.58-1.1.97-2.31 1.16-3.54-.09 1.82.42 4.03 2.15 4.59 1.2.38 2.53-.24 3.34-1.2a10 10 0 0 0 1.6-3.4 8 8 0 0 0 .16 3.34c.35 1.07 1.27 2.05 2.4 2.1.92.04 1.76-.52 2.4-1.2a7 7 0 0 0 1.8-3.92c-.54 2 1.15 4.27 3.2 4.34s3.9-2.1 3.49-4.13c.3 1.57 1.44 3.04 2.99 3.43s3.42-.59 3.7-2.17a16 16 0 0 0-.8 8.7c.17.9.5 1.9 1.32 2.31s2.1-.36 1.76-1.2a16.8 16.8 0 0 0-3.86 10.6c0 1.3.2 2.71 1.12 3.6s2.78.9 3.29-.3c-1.68 1.32-2.76 6.96-2.81 9.1-.04 1.95 0 4 .94 5.7s2.98 2.98 4.83 2.37c2.1-.69 3.73-6.6 3.35-8.79-.32.2-.21.75.1.95.33.2.75.14 1.12.03a5.1 5.1 0 0 0 3.5-3.88 5.1 5.1 0 0 0-1.87-4.88c.06.62 1.02.5 1.51.1a7.2 7.2 0 0 0 2.32-7.77c-.33-.95-1.13-1.97-2.12-1.79 1.32-.17 1.98-1.72 2-3.05.06-3.82-2.17-8.5-6-8.3.96-2.2.86-5.09-.71-6.92-2.54-2.96-4.1-2.55-6.48-2.2a6.5 6.5 0 0 0-2.01-6.52 6.5 6.5 0 0 0-6.73-1c-1.06-2.2-3.53-3.43-5.95-3.8a6 6 0 0 0-3.17.2c-1 .4-1.83 1.32-1.88 2.4a8.56 8.56 0 0 0-11.2-.17c-.55.5-.97 1.51-.3 1.84-.01 0-6.66-1.62-9.5 2.62-2.82 4.24-1.12 5.8-1.12 5.8s-6.92.27-6.78 3.94 1.27 4.24 1.27 4.24-6.01-1.72-7.3 1.8c-1.03 2.81 1.8 4.41 1.8 4.41S-.96 30.81.2 36.16c.76 3.48 3.4 4.1 3.4 4.1s-2.4 6.63-.14 9.03 5.22.42 5.22.42-1.41 7.2 1.84 8.33 5.36-2.12 5.36-2.12-2.12 6.07 2.54 5.5c4.66-.55 7.46-9.6 3.39-13.68 0 0 2.12-3.95 1.4-7.34C22.55 37 20 34.61 20 34.61s1.98-1.13 1.7-3.95c-.3-2.82-3.26-4.38-3.26-4.38s3.4-3.1 3.53-9.18" fill="#ff543d"/></g><clipPath id="clip-181c8200"><rect width="80" height="80" rx="0" ry="0"/></clipPath></defs><g clip-path="url(#clip-181c8200)"><rect width="80" height="80" fill="#e6eff8"/><path d="M19.07 30.47s1.57-20.23 21.59-20.23S62.3 30.55 62.3 30.55s9.43-.8 9.43 7.6c0 8.42-9.28 7.13-9.28 7.13S60.9 67.15 42.03 67.15c-21.11 0-23.4-20.8-23.4-20.8s-9 .72-9.93-6.25c-1.08-8.2 10.37-9.64 10.37-9.64" fill="#ffd6c0"/><path d="m64.3 39.49.46-.41.1-.09c.12-.1-.13.1-.02.02l.24-.17q.5-.35 1.06-.62l.26-.12.05-.02.05-.02.58-.21q.6-.18 1.2-.28c.52-.08.85-.76.7-1.23-.18-.56-.67-.8-1.23-.7a9.3 9.3 0 0 0-4.87 2.43c-.38.36-.4 1.06 0 1.4.4.36 1 .4 1.4 0zm-51.8-1.16.14.01c-.27-.02-.11-.01-.04 0l.3.05.52.14.28.09.12.05c.02 0 .22.09.06.02-.14-.1 0-.04.03-.03l.15.06.26.13.47.3.27.22q.47.38.83.83c.33.4 1.07.37 1.41 0 .4-.43.36-.98 0-1.4a7.3 7.3 0 0 0-4.84-2.53c-.52-.06-1.02.5-1 1 .03.59.44.94 1 1m18.3-1.9v4.54c0 .52.46 1.02 1 1s1-.44 1-1V36.4c0-.52-.46-1.02-1-1s-1 .44-1 1M49.2 36l-.15 4.81a1 1 0 0 0 1 1c.56-.02.98-.44 1-1l.15-4.8a1 1 0 0 0-1-1 1 1 0 0 0-1 1" fill="black"/><use transform="translate(27.82 26.75)" href="#mood-hopeful-181c8200"/><use transform="translate(3.78 2.95)" href="#hair-longCurls-181c8200"/></g></svg>
@@ -0,0 +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-Br4CGoxh.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=u((e-v.runEnd)/(v.executionComplete-v.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>=v.artifactLive,u=e>=v.artifactClick,d=e>=v.runEnd&&e<v.executionComplete,f=a(e,v.runEnd,19.2),p=a(e,v.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:u?``:`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:u?`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(p),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)(o,{"aria-hidden":`true`})}),(0,E.jsx)(`button`,{type:`button`,"aria-label":`Timeline`,tabIndex:-1,children:(0,E.jsx)(t,{"aria-hidden":`true`})})]}),d?(0,E.jsx)(`button`,{className:`scene-status-entry is-running`,style:D(f),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)(s,{"aria-hidden":`true`})})]})}function N({time:e,assets:t}){let n=a(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:a(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:a(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:a(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:a(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":a(e,v.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)(h,{"aria-hidden":`true`})}),(0,E.jsx)(`h2`,{children:`Scratchpad`})]}),(0,E.jsxs)(`div`,{className:`scene-scratchpad-body`,children:[(0,E.jsxs)(A,{progress:a(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:a(e,9.85,10.65),className:`scene-scratchpad-label`,children:`Confirmed`}),(0,E.jsx)(P,{progress:a(e,10.4,11.25),icon:(0,E.jsx)(S,{}),children:`Product-first editorial layout with generous space`}),(0,E.jsx)(P,{progress:a(e,11.2,12.05),icon:(0,E.jsx)(S,{}),children:`Warm neutrals with quiet serif headlines`}),(0,E.jsx)(P,{progress:a(e,12,12.85),icon:(0,E.jsx)(S,{}),children:`Keep the collection small, curated, and story-led`}),(0,E.jsx)(A,{progress:a(e,12.75,13.55),className:`scene-scratchpad-label`,children:`Requested feature`}),(0,E.jsx)(P,{progress:a(e,13.3,14.2),icon:(0,E.jsx)(y,{}),children:`Preview every piece in clay, sage, and ink glazes`})]}),(0,E.jsxs)(`footer`,{className:`scene-scratchpad-footer`,style:D(a(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=a(e,18.92,19.2),i=m(e),o=i.id===`complete`,s={prepare:v.runEnd,implement:v.executionPrepareEnd,validate:v.executionImplementEnd,update:v.executionValidateEnd,complete:v.executionComplete}[i.id],c=a(e,s,s+.32),l=k(e);return(0,E.jsx)(`div`,{className:`scene-execution`,style:{"--reveal":r,"--stage-reveal":c},children:(0,E.jsxs)(`article`,{className:`homepage-motion-panel homepage-motion-execution-panel homepage-motion-execution-card is-score-${o?`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)(d,{})}),(0,E.jsx)(`h3`,{children:`Execution progress`})]})}),(0,E.jsxs)(`div`,{className:`homepage-motion-execution-body`,children:[(0,E.jsxs)(`div`,{className:`homepage-motion-execution-step homepage-motion-execution-stage is-${o?`complete`:`running`} has-marker`,children:[(0,E.jsx)(`span`,{className:`homepage-motion-execution-marker ${o?`is-done`:`is-running`}`,"aria-hidden":`true`,children:o?(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}),o?null:(0,E.jsx)(`span`,{className:`homepage-motion-execution-step-elapsed`,children:` (${I(e,s)})`})]})},i.id)]}),(0,E.jsx)(`div`,{className:`homepage-motion-running-score ${o?`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*l}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=a(e,v.artifactClick,33.25),n=a(e,34.25,34.52),r=a(e,34.88,35.15),i=a(e,35.55,35.82),o=p(e),s=O(`#d9cbb4`,`#c56f4f`,n);e>=34.88&&(s=O(`#c56f4f`,`#6f9275`,r)),e>=35.55&&(s=O(`#6f9275`,`#243a46`,i));let c=e>=35.55?`ink`:e>=34.88?`sage`:e>=34.25?`clay`:null,u={"--artifact-scroll":o};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:u,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:s}),(0,E.jsxs)(`div`,{className:`artifact-glaze-picker`,"aria-label":`Glaze preview`,children:[(0,E.jsx)(`span`,{children:`Glaze`}),(0,E.jsx)(`i`,{className:c===`clay`?`is-active is-clay`:`is-clay`}),(0,E.jsx)(`i`,{className:c===`sage`?`is-active is-sage`:`is-sage`}),(0,E.jsx)(`i`,{className:c===`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,i,o){let s=a(e,t,n,r);return{x:B(i.x,o.x,s),y:B(i.y,o.y,s)}}function H(e,t,n){return e<t||e>n?0:Math.sin(Math.PI*((e-t)/(n-t)))}function U(e){if(e>=v.runCursorStart&&e<18.92){let t=V(e,v.runCursorStart,v.runCursorArrive,{x:86,y:76},{x:94.25,y:94.2});return{visible:!0,x:t.x,y:t.y,clickPulse:H(e,v.runClickStart,v.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)}}if(e>=33.45&&e<35.95){let t={x:87.27,y:66.35},n={x:88.95,y:66.35},r={x:90.64,y:66.35},i;return i=e<34.15?V(e,33.45,34.15,{x:4,y:22.85},t):e<34.78?V(e,34.5,34.78,t,n):e<35.45?V(e,35.15,35.45,n,r):r,{visible:!0,x:i.x,y:i.y,clickPulse:Math.max(H(e,34.15,34.4),H(e,34.78,35.03),H(e,35.45,35.7))}}return{visible:!1,x:50,y:50,clickPulse:0}}function W({time:e,assets:t}){let n=g(e),i=U(e),o=_(e),s=a(e,.08,1.05,r);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:o,visibility:o<=.001?`hidden`:`visible`},children:(0,E.jsxs)(`div`,{className:`motion-stage`,style:{opacity:s,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 ${i.visible?`is-visible`:``}`,style:{left:`${i.x}%`,top:`${i.y}%`,"--click-pulse":i.clickPulse},"aria-hidden":`true`,children:[(0,E.jsx)(w,{}),(0,E.jsx)(`i`,{})]})]})})}export{W as HomepageMotionScene};