@mapled/cli 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,285 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash, randomBytes } from "node:crypto";
3
+ import { access, readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { ApiError } from "./api.js";
6
+ import { stringFlag } from "./args.js";
7
+ import { openSession } from "./commands.js";
8
+ import { CliError } from "./errors.js";
9
+ const HOSTS = ["vercel", "netlify", "cloudflare"];
10
+ const HOST_NAMES = { vercel: "Vercel", netlify: "Netlify", cloudflare: "Cloudflare" };
11
+ /** The host's CLI as installed (on PATH) or through npx. */
12
+ const HOST_CLI = {
13
+ vercel: { bin: "vercel", pkg: "vercel" },
14
+ netlify: { bin: "netlify", pkg: "netlify-cli" },
15
+ cloudflare: { bin: "wrangler", pkg: "wrangler" },
16
+ };
17
+ export const ENV_NAME = "MAPLED_SERVER_KEY";
18
+ const RUN_TIMEOUT_MS = 180_000;
19
+ /** The default runner: no shell (but on Windows, where the CLIs are
20
+ `.cmd` shims), the parent's environment untouched, stdin closed after
21
+ `input`, output kept — never streamed to the terminal. */
22
+ export const runProcess = (cmd, args, opts) => new Promise((resolve) => {
23
+ let stdout = "";
24
+ let stderr = "";
25
+ let settled = false;
26
+ const done = (r) => {
27
+ if (settled)
28
+ return;
29
+ settled = true;
30
+ clearTimeout(timer);
31
+ resolve(r);
32
+ };
33
+ const child = spawn(cmd, args, { cwd: opts.cwd, stdio: ["pipe", "pipe", "pipe"], shell: process.platform === "win32", windowsHide: true });
34
+ const timer = setTimeout(() => {
35
+ child.kill();
36
+ done({ code: 124, stdout, stderr: `${stderr}\n(timed out)` });
37
+ }, RUN_TIMEOUT_MS);
38
+ child.stdout.on("data", (d) => (stdout += d.toString("utf8")));
39
+ child.stderr.on("data", (d) => (stderr += d.toString("utf8")));
40
+ child.on("error", (err) => done({ code: 127, stdout, stderr: err.message, missing: err.code === "ENOENT" }));
41
+ child.on("close", (code) => done({ code: code ?? 1, stdout, stderr }));
42
+ child.stdin.on("error", () => { });
43
+ child.stdin.end(opts.input ?? "");
44
+ });
45
+ /** Every occurrence of the key in a text, as its prefix and an ellipsis. */
46
+ export function redact(text, key, prefix) {
47
+ return key ? text.split(key).join(`${prefix}…`) : text;
48
+ }
49
+ async function hostCli(ctx, run, host, cwd) {
50
+ const { bin, pkg } = HOST_CLI[host];
51
+ const probe = await run(bin, ["--version"], { cwd });
52
+ if (!probe.missing)
53
+ return { run: (args, input) => run(bin, args, { cwd, input }), shown: bin };
54
+ const viaNpx = await run("npx", ["-y", pkg, "--version"], { cwd });
55
+ if (viaNpx.missing || viaNpx.code !== 0) {
56
+ throw new CliError(`${HOST_NAMES[host]}'s CLI isn't installed and npx couldn't run it. Install it with \`npm install -g ${pkg}\`, then run this again.`);
57
+ }
58
+ ctx.out(`Using ${pkg} through npx.`);
59
+ return { run: (args, input) => run("npx", ["-y", pkg, ...args], { cwd, input }), shown: `npx ${pkg}` };
60
+ }
61
+ const exists = async (file) => access(file).then(() => true, () => false);
62
+ /** The host's CLI is signed in, and this directory is the site's there. */
63
+ async function preflight(host, cli, cwd) {
64
+ if (host === "vercel") {
65
+ if ((await cli.run(["whoami"])).code !== 0) {
66
+ throw new CliError(`Vercel's CLI isn't signed in. Run \`${cli.shown} login\`, then run this again.`);
67
+ }
68
+ if (!(await exists(path.join(cwd, ".vercel", "project.json")))) {
69
+ throw new CliError(`This directory isn't linked to a Vercel project. Run \`${cli.shown} link\` here, then run this again.`);
70
+ }
71
+ return;
72
+ }
73
+ if (host === "netlify") {
74
+ const status = await cli.run(["status"]);
75
+ if (status.code !== 0 || /not logged in|log in/i.test(`${status.stdout}\n${status.stderr}`)) {
76
+ throw new CliError(`Netlify's CLI isn't signed in. Run \`${cli.shown} login\`, then run this again.`);
77
+ }
78
+ if (!(await exists(path.join(cwd, ".netlify", "state.json")))) {
79
+ throw new CliError(`This directory isn't linked to a Netlify site. Run \`${cli.shown} link\` here, then run this again.`);
80
+ }
81
+ return;
82
+ }
83
+ const who = await cli.run(["whoami"]);
84
+ if (who.code !== 0 || /not authenticated/i.test(`${who.stdout}\n${who.stderr}`)) {
85
+ throw new CliError(`Wrangler isn't signed in to Cloudflare. Run \`${cli.shown} login\`, then run this again.`);
86
+ }
87
+ const configs = ["wrangler.toml", "wrangler.json", "wrangler.jsonc"];
88
+ if (!(await Promise.all(configs.map((f) => exists(path.join(cwd, f))))).some(Boolean)) {
89
+ throw new CliError("No wrangler.toml here. Run this from the Worker's directory, where Wrangler finds its configuration.");
90
+ }
91
+ }
92
+ function stepsFor(host, key, workerEnv) {
93
+ if (host === "vercel") {
94
+ const steps = [];
95
+ // one overwrite per environment, never a remove then an add: a
96
+ // deployment that starts between the two would build without the key
97
+ for (const env of ["production", "preview"]) {
98
+ steps.push({ args: ["env", "add", ENV_NAME, env, "--force"], input: `${key}\n`, what: `set the ${env} value` });
99
+ }
100
+ return { steps, where: " (production, preview)" };
101
+ }
102
+ if (host === "netlify") {
103
+ // the Netlify CLI takes a value as an argument only
104
+ return { steps: [{ args: ["env:set", ENV_NAME, key], what: "set the value" }], where: "" };
105
+ }
106
+ // a named Wrangler environment is a Worker of its own: the secret goes to the one deployed
107
+ const args = ["secret", "put", ENV_NAME, ...(workerEnv ? ["--env", workerEnv] : [])];
108
+ return { steps: [{ args, input: `${key}\n`, what: "put the secret" }], where: workerEnv ? ` (Worker secret, ${workerEnv})` : " (Worker secret)" };
109
+ }
110
+ /** A TOML key path (`env.production`, `"env"."live"`, `env . a`) as its
111
+ parts; null when the text is no key path. */
112
+ function keyPath(text) {
113
+ const parts = [];
114
+ const re = /\s*(?:"((?:[^"\\]|\\.)*)"|'([^']*)'|([A-Za-z0-9_-]+))\s*(\.|$)/y;
115
+ let at = 0;
116
+ while (at < text.length) {
117
+ re.lastIndex = at;
118
+ const m = re.exec(text);
119
+ if (!m)
120
+ return null;
121
+ parts.push(m[1] ?? m[2] ?? m[3]);
122
+ at = re.lastIndex;
123
+ if (m[4] !== ".")
124
+ break;
125
+ }
126
+ return at === text.length && parts.length ? parts : null;
127
+ }
128
+ /** The environments wrangler.toml names, in each form TOML allows:
129
+ `[env.<name>]` headers, keys of an `[env]` table (`<name> = {…}`) and
130
+ dotted keys (`env.<name>.vars = …`). `opaque` — it has an `env` this
131
+ reading can't name (`env = {…}` inline). */
132
+ export function tomlEnvs(toml) {
133
+ const names = new Set();
134
+ let opaque = false;
135
+ let table = [];
136
+ for (const raw of toml.split(/\r?\n/)) {
137
+ // the line up to a comment that isn't inside a string
138
+ const line = (/^(?:[^"'#]|"(?:[^"\\]|\\.)*"|'[^']*')*/.exec(raw)?.[0] ?? "").trim();
139
+ const header = /^\[\[?([^\[\]]*)\]\]?$/.exec(line);
140
+ if (header) {
141
+ table = keyPath(header[1]) ?? [];
142
+ if (table[0] === "env" && table[1] !== undefined)
143
+ names.add(table[1]);
144
+ continue;
145
+ }
146
+ const eq = /^((?:[^="']|"(?:[^"\\]|\\.)*"|'[^']*')+)=/.exec(line);
147
+ const key = eq ? keyPath(eq[1].trim()) : null;
148
+ if (!key)
149
+ continue;
150
+ const full = [...table, ...key];
151
+ if (full[0] !== "env")
152
+ continue;
153
+ if (full[1] !== undefined)
154
+ names.add(full[1]);
155
+ else
156
+ opaque = true;
157
+ }
158
+ return { names: [...names], opaque };
159
+ }
160
+ /** The environments a Worker's configuration names (wrangler.toml —
161
+ `tomlEnvs`; `"env": {…}` in wrangler.json(c)); none when it names none
162
+ or can't be read. `opaque` — it has environments whose names this
163
+ reading doesn't get. */
164
+ async function wranglerEnvs(cwd) {
165
+ const names = new Set();
166
+ let opaque = false;
167
+ const toml = await readFile(path.join(cwd, "wrangler.toml"), "utf8").catch(() => null);
168
+ if (toml !== null) {
169
+ const found = tomlEnvs(toml);
170
+ for (const name of found.names)
171
+ names.add(name);
172
+ opaque = found.opaque;
173
+ }
174
+ for (const file of ["wrangler.json", "wrangler.jsonc"]) {
175
+ const text = await readFile(path.join(cwd, file), "utf8").catch(() => null);
176
+ if (text === null)
177
+ continue;
178
+ try {
179
+ // comments and trailing commas of jsonc, outside strings
180
+ const plain = text.replace(/("(?:[^"\\]|\\.)*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_, str) => str ?? "")
181
+ .replace(/,(\s*[}\]])/g, "$1");
182
+ const env = JSON.parse(plain).env;
183
+ if (env && typeof env === "object" && !Array.isArray(env))
184
+ for (const name of Object.keys(env))
185
+ names.add(name);
186
+ }
187
+ catch {
188
+ // unreadable: Wrangler says what is wrong when it runs
189
+ }
190
+ }
191
+ return { names: [...names], opaque };
192
+ }
193
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
194
+ /** Until the request is decided: applied, or a CliError saying why not. */
195
+ async function decision(ctx, session, asked) {
196
+ const deadline = asked.expiresAt ? new Date(asked.expiresAt).getTime() : Date.now() + 60 * 60_000;
197
+ let current = asked;
198
+ for (;;) {
199
+ if (current.status === "applied")
200
+ return;
201
+ if (current.status === "denied")
202
+ throw new CliError("An owner denied the request in Mapled. Nothing was set.");
203
+ if (current.status === "expired")
204
+ throw new CliError("The request expired before an owner confirmed it. Run this again.");
205
+ if (current.status === "failed")
206
+ throw new CliError(current.error ?? "The request failed in Mapled. Run this again.");
207
+ if (Date.now() >= deadline)
208
+ throw new CliError("No owner confirmed the request in time. Run this again.");
209
+ await sleep(Math.min(ctx.pollMs ?? 2000, Math.max(0, deadline - Date.now())));
210
+ current = (await session.get(`/v1/agent/confirmations/${current.id}`)).confirmation;
211
+ }
212
+ }
213
+ export async function envPush(ctx, flags, run = ctx.run ?? runProcess) {
214
+ const host = stringFlag(flags, "host");
215
+ if (!host || !HOSTS.includes(host)) {
216
+ throw new CliError(`Name the site's host: \`mapled env push --host ${HOSTS.join("|")}\`.`, 2);
217
+ }
218
+ const which = host;
219
+ const name = HOST_NAMES[which];
220
+ const { found, session } = await openSession(ctx, flags);
221
+ const cwd = ctx.cwd;
222
+ const workerEnv = stringFlag(flags, "env") ?? null;
223
+ if (workerEnv !== null && (which !== "cloudflare" || !/^[A-Za-z0-9_-]+$/.test(workerEnv))) {
224
+ throw new CliError(which === "cloudflare" ? "--env takes the name of an environment in the Worker's configuration." : "--env names a Wrangler environment: use it with --host cloudflare.", 2);
225
+ }
226
+ const cli = await hostCli(ctx, run, which, cwd);
227
+ await preflight(which, cli, cwd);
228
+ if (which === "cloudflare") {
229
+ const { names: envs, opaque } = await wranglerEnvs(cwd);
230
+ if (workerEnv === null && (envs.length > 0 || opaque)) {
231
+ const listed = envs.length ? ` (${envs.join(", ")})` : "";
232
+ throw new CliError(`This Worker's configuration has environments${listed}. Name the one the site is deployed to: \`mapled env push --host cloudflare --env ${envs[0] ?? "<name>"}\`.`, 2);
233
+ }
234
+ if (workerEnv !== null && !opaque && !envs.includes(workerEnv)) {
235
+ throw new CliError(`The Worker's configuration has no environment ${workerEnv}${envs.length ? ` (it has ${envs.join(", ")})` : ""}.`, 2);
236
+ }
237
+ }
238
+ // the verifier stays in this process: Mapled gets its hash, the key goes only to the one who holds it
239
+ const verifier = randomBytes(32).toString("hex");
240
+ const challenge = createHash("sha256").update(verifier).digest("hex");
241
+ let asked;
242
+ try {
243
+ asked = await session.send("POST", "/v1/agent/server-keys", { host: which, challenge });
244
+ }
245
+ catch (err) {
246
+ if (err instanceof ApiError)
247
+ throw new CliError(err.message);
248
+ throw err;
249
+ }
250
+ ctx.out(`Give the person this link to confirm: ${asked.reviewUrl}`);
251
+ ctx.out(`Waiting for an owner of ${session.conn.projectName} to confirm in Mapled…`);
252
+ await decision(ctx, session, asked.confirmation);
253
+ let claimed;
254
+ try {
255
+ claimed = await session.send("POST", "/v1/agent/server-keys/claim", {
256
+ confirmationId: asked.confirmation.id,
257
+ verifier,
258
+ });
259
+ }
260
+ catch (err) {
261
+ if (err instanceof ApiError)
262
+ throw new CliError(err.message);
263
+ throw err;
264
+ }
265
+ const { key, prefix } = claimed;
266
+ const shown = `${prefix}…`;
267
+ const { steps, where } = stepsFor(which, key, workerEnv);
268
+ for (const step of steps) {
269
+ const result = await cli.run(step.args, step.input);
270
+ if (result.code === 0 || step.optional)
271
+ continue;
272
+ const output = redact(`${result.stdout}\n${result.stderr}`, key, prefix).trim();
273
+ if (output)
274
+ for (const line of output.split("\n"))
275
+ ctx.out(` ${line}`);
276
+ throw new CliError(`${name}'s CLI couldn't ${step.what} of ${ENV_NAME} (exit ${result.code}). Mapled made the key ${shown}; run this again to make another one once the host's CLI works.`);
277
+ }
278
+ if (flags.json) {
279
+ ctx.out(JSON.stringify({ host: which, variable: ENV_NAME, prefix, confirmationId: asked.confirmation.id, project: found.config.project }, null, 2));
280
+ }
281
+ else {
282
+ ctx.out(`✓ ${ENV_NAME} set on ${name}${where} — ${shown}. Redeploy the site for it to apply.`);
283
+ }
284
+ return 0;
285
+ }
package/dist/index.js CHANGED
@@ -3,8 +3,9 @@ import { readFileSync } from "node:fs";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { parseArgs } from "./args.js";
5
5
  import { openBrowser } from "./browser.js";
6
- import { bindingsPull, bindingsPush, doctor, formsCreate, formsList, generate, integrationReport, link, login, logout, manifestValidate, mdPull, preview, scan, connect, tool, tools, schemaDiff, schemaPull, } from "./commands.js";
6
+ import { bindingsPull, bindingsPush, doctor, formsCreate, formsList, generate, integrationReport, link, login, logout, manifestValidate, mdPull, mediaUpload, preview, scan, connect, tool, tools, schemaDiff, schemaPull, } from "./commands.js";
7
7
  import { credentialsPath } from "./credentials.js";
8
+ import { envPush } from "./envPush.js";
8
9
  import { CliError } from "./errors.js";
9
10
  /** `mapled` — sign in, link a repository to its project, generate types
10
11
  for the content, keep the schema pin, the site manifest and MAPLED.md
@@ -28,11 +29,13 @@ Usage
28
29
  mapled bindings push [--dry-run] Push mapled/manifest.json so Mapled grades every binding
29
30
  mapled bindings pull [--force] Write the pushed manifest into mapled/manifest.json
30
31
  mapled md pull [--force] Write MAPLED.md, the guide for the next agent, from the project
32
+ mapled media upload [<path | url>…] [--run <runId>] [--json] Upload images and files into Media; --run uploads every one a setup plan names
31
33
  mapled forms create <name> [--key <key>] Create a form the site posts submissions to (--no-spam-protection to skip the honeypot)
32
34
  mapled forms list [--json] List the project's forms and where the site posts to
33
35
  mapled doctor [--json] [--load-config] Check the integration end to end; --load-config runs a next.config doctor can't read, as next build does
34
36
  mapled integration report [--json] [--verify] Show the last setup run's verification checklist and report
35
37
  mapled preview [<collection> [<slug>]] [--locale <code>] [--open] Print a one-time Preview link that shows the drafts on the site
38
+ mapled env push --host <vercel|netlify|cloudflare> [--env <name>] [--json] Put the site's server key on its host once an owner confirms it in Mapled (--env: the Wrangler environment); the key is never printed
36
39
 
37
40
  Options
38
41
  --version, -v Print the version
@@ -127,6 +130,8 @@ async function main(argv) {
127
130
  case "md pull":
128
131
  await mdPull(ctx, flags);
129
132
  return 0;
133
+ case "media upload":
134
+ return mediaUpload(ctx, flags, words.slice(2));
130
135
  case "forms create":
131
136
  await formsCreate(ctx, flags, words.slice(2));
132
137
  return 0;
@@ -140,6 +145,8 @@ async function main(argv) {
140
145
  case "preview":
141
146
  await preview(ctx, flags, words.slice(1));
142
147
  return 0;
148
+ case "env push":
149
+ return envPush(ctx, flags);
143
150
  case "auth":
144
151
  case "project":
145
152
  case "types":
@@ -148,7 +155,9 @@ async function main(argv) {
148
155
  case "bindings":
149
156
  case "md":
150
157
  case "forms":
158
+ case "media":
151
159
  case "integration":
160
+ case "env":
152
161
  console.log(HELP);
153
162
  return 2;
154
163
  default:
package/dist/logo.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** The Mapled logo — apps/web/public/brand/logo.svg (Figma, 122×32), with
2
+ its coordinates rounded to two decimals so the sign-in page carries it
3
+ inline: the page is served from 127.0.0.1 and loads nothing. */
4
+ export declare const LOGO_SVG = "<svg width=\"122\" height=\"32\" role=\"img\" aria-label=\"Mapled\" viewBox=\"0 0 122 32.03\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M41.62 7.49H45.87L51.57 21.08H51.8L57.49 7.49H61.75V26.25H58.41V13.36H58.23L52.93 26.2H50.44L45.13 13.33H44.96V26.25H41.62V7.49Z\" fill=\"#FEFEFE\"/><path d=\"M67.41 26.54C66.5 26.54 65.68 26.38 64.95 26.06C64.22 25.74 63.65 25.26 63.23 24.63C62.81 24 62.6 23.23 62.6 22.3C62.6 21.51 62.75 20.85 63.05 20.33C63.35 19.81 63.76 19.4 64.27 19.09C64.79 18.78 65.38 18.54 66.03 18.38C66.68 18.22 67.36 18.1 68.06 18.02C68.9 17.94 69.59 17.86 70.11 17.8C70.64 17.72 71.02 17.61 71.26 17.47C71.5 17.31 71.62 17.08 71.62 16.76V16.71C71.62 16.02 71.41 15.48 70.99 15.1C70.57 14.72 69.97 14.53 69.18 14.53C68.35 14.53 67.69 14.71 67.21 15.07C66.73 15.42 66.4 15.84 66.23 16.32L63.07 15.88C63.32 15.03 63.73 14.31 64.3 13.74C64.88 13.16 65.58 12.72 66.41 12.44C67.24 12.14 68.16 12 69.17 12C69.86 12 70.55 12.08 71.24 12.23C71.92 12.39 72.55 12.66 73.12 13.02C73.69 13.38 74.14 13.87 74.49 14.5C74.84 15.12 75.01 15.9 75.01 16.83V26.25H71.75V24.32H71.64C71.43 24.71 71.14 25.08 70.77 25.42C70.4 25.75 69.93 26.03 69.37 26.23C68.82 26.43 68.16 26.54 67.41 26.54ZM68.29 24.1C68.98 24.1 69.57 23.97 70.07 23.7C70.57 23.44 70.95 23.08 71.22 22.64C71.49 22.2 71.63 21.72 71.63 21.2V19.55C71.52 19.63 71.34 19.71 71.09 19.78C70.84 19.86 70.56 19.92 70.24 19.98C69.93 20.03 69.62 20.08 69.32 20.12C69.01 20.17 68.74 20.2 68.52 20.23C68.01 20.3 67.56 20.41 67.16 20.56C66.76 20.71 66.45 20.93 66.21 21.2C65.98 21.47 65.87 21.82 65.87 22.25C65.87 22.86 66.1 23.32 66.55 23.63C67.01 23.94 67.59 24.1 68.29 24.1Z\" fill=\"#FEFEFE\"/><path d=\"M76.31 31.53V12.18H79.65V14.51H79.85C80.02 14.16 80.27 13.8 80.59 13.42C80.9 13.03 81.34 12.69 81.88 12.42C82.42 12.14 83.12 12 83.96 12C85.07 12 86.07 12.27 86.97 12.83C87.87 13.38 88.58 14.2 89.1 15.28C89.63 16.35 89.9 17.67 89.9 19.23C89.9 20.78 89.64 22.09 89.12 23.17C88.6 24.25 87.9 25.08 87 25.65C86.11 26.21 85.1 26.5 83.97 26.5C83.14 26.5 82.46 26.36 81.92 26.1C81.37 25.83 80.94 25.5 80.6 25.12C80.28 24.74 80.03 24.38 79.85 24.03H79.71V31.53H76.31ZM79.64 19.22C79.64 20.13 79.77 20.92 80.03 21.61C80.3 22.29 80.69 22.83 81.19 23.21C81.69 23.59 82.3 23.78 83.02 23.78C83.77 23.78 84.4 23.58 84.91 23.19C85.41 22.79 85.79 22.25 86.05 21.57C86.31 20.88 86.44 20.1 86.44 19.22C86.44 18.34 86.31 17.57 86.06 16.89C85.8 16.21 85.42 15.68 84.92 15.29C84.41 14.91 83.78 14.72 83.02 14.72C82.3 14.72 81.68 14.9 81.18 15.28C80.67 15.65 80.29 16.17 80.02 16.84C79.77 17.51 79.64 18.31 79.64 19.22Z\" fill=\"#FEFEFE\"/><path d=\"M94.09 7.49V26.25H90.69V7.49H94.09Z\" fill=\"#FEFEFE\"/><path d=\"M101.84 26.53C100.39 26.53 99.15 26.23 98.1 25.65C97.05 25.05 96.25 24.22 95.69 23.14C95.13 22.05 94.84 20.77 94.84 19.3C94.84 17.85 95.13 16.58 95.69 15.49C96.26 14.39 97.05 13.53 98.07 12.92C99.09 12.3 100.28 12 101.66 12C102.54 12 103.38 12.14 104.17 12.42C104.96 12.69 105.66 13.12 106.27 13.7C106.88 14.28 107.36 15.02 107.71 15.92C108.06 16.81 108.24 17.87 108.24 19.11V20.12H96.44V17.89H104.98C104.98 17.25 104.84 16.69 104.56 16.19C104.29 15.69 103.9 15.3 103.41 15.01C102.92 14.72 102.35 14.58 101.7 14.58C101.01 14.58 100.4 14.74 99.88 15.07C99.35 15.4 98.94 15.83 98.65 16.36C98.36 16.88 98.22 17.46 98.21 18.09V20.04C98.21 20.86 98.36 21.56 98.67 22.15C98.97 22.73 99.4 23.17 99.95 23.48C100.5 23.79 101.15 23.94 101.88 23.94C102.38 23.94 102.82 23.88 103.22 23.74C103.62 23.6 103.97 23.4 104.26 23.13C104.56 22.86 104.78 22.53 104.93 22.13L108.1 22.48C107.9 23.3 107.51 24.01 106.95 24.62C106.4 25.23 105.68 25.7 104.82 26.03C103.95 26.36 102.95 26.53 101.84 26.53Z\" fill=\"#FEFEFE\"/><path d=\"M114.34 26.5C113.2 26.5 112.19 26.21 111.3 25.65C110.41 25.08 109.7 24.25 109.18 23.17C108.66 22.09 108.4 20.78 108.4 19.23C108.4 17.67 108.67 16.35 109.19 15.28C109.72 14.2 110.44 13.38 111.34 12.83C112.24 12.27 113.24 12 114.34 12C115.19 12 115.88 12.14 116.43 12.42C116.97 12.69 117.4 13.03 117.72 13.42C118.04 13.8 118.28 14.16 118.46 14.51H118.6V7.49H122V26.25H118.66V24.03H118.46C118.28 24.38 118.03 24.74 117.7 25.12C117.37 25.5 116.93 25.83 116.39 26.1C115.84 26.36 115.16 26.5 114.34 26.5ZM115.28 23.78C116 23.78 116.61 23.59 117.12 23.21C117.62 22.83 118.01 22.29 118.27 21.61C118.53 20.92 118.66 20.13 118.66 19.22C118.66 18.31 118.53 17.51 118.27 16.84C118.01 16.17 117.63 15.65 117.13 15.28C116.63 14.9 116.01 14.72 115.28 14.72C114.53 14.72 113.9 14.91 113.39 15.29C112.88 15.68 112.5 16.21 112.25 16.89C111.99 17.57 111.86 18.34 111.86 19.22C111.86 20.1 111.99 20.88 112.25 21.57C112.51 22.25 112.89 22.79 113.4 23.19C113.91 23.58 114.54 23.78 115.28 23.78Z\" fill=\"#FEFEFE\"/><path d=\"M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z\" fill=\"url(#g0)\"/><path d=\"M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z\" fill=\"url(#g1)\"/><path d=\"M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z\" fill=\"url(#g2)\"/><path d=\"M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z\" fill=\"url(#g3)\"/><path d=\"M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.79 5.28 10.91 5.03L10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.24 4.52 21.39 4.43 21.51 4.31L21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15L27.3 5.17C27.44 5.38 27.73 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39L29.43 14.49C29.34 15.53 29.09 16.53 30.25 17C30.4 17.06 30.61 17.18 30.78 17.27L30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2L21.81 27.49C20.71 28.99 19.55 30.52 18.49 32.03C18.18 31.44 17.25 29.54 16.84 29.15L16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.5 4.59 20.37L4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.13 18.4 10.23 18.29L10.24 18.27C10.34 18.15 10.38 17.99 10.36 17.83L10.36 17.8C10.29 17.42 9.66 17.07 9.3 16.83L9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.36 10.63 5.55 10.22 5.5 9.93L5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63ZM5.18 7.89C5.37 8.52 5.58 9.19 5.72 9.85C5.77 10.06 5.72 10.29 5.63 10.48C5.54 10.67 5.39 10.86 5.19 10.95C4.67 11.17 4.12 11.36 3.58 11.53C3.19 11.66 2.8 11.78 2.42 11.91L9.36 16.6C9.51 16.7 9.81 16.87 10.04 17.05C10.16 17.14 10.28 17.24 10.38 17.36C10.48 17.47 10.56 17.61 10.59 17.76H10.59C10.63 17.99 10.57 18.23 10.43 18.41L10.43 18.41L10.43 18.41C10.36 18.5 10.27 18.58 10.17 18.64C10.07 18.69 9.95 18.75 9.81 18.75C9.18 18.76 8.44 18.69 7.82 18.65L7.82 18.65L7.82 18.65L3.82 18.34C4.16 18.96 4.52 19.62 4.8 20.27C4.84 20.38 4.85 20.5 4.85 20.6C4.85 20.7 4.83 20.81 4.8 20.9L4.8 20.9L4.8 20.9C4.73 21.08 4.62 21.23 4.48 21.36C4.35 21.46 4.06 21.64 3.78 21.81C3.49 21.99 3.21 22.15 3.06 22.24L0.46 23.83C1.45 24.37 2.43 24.97 3.39 25.52C3.69 25.7 4.04 25.88 4.41 26.08C4.76 26.28 5.12 26.49 5.4 26.69C5.55 26.81 5.67 26.98 5.72 27.17C5.75 27.26 5.75 27.36 5.75 27.45C5.74 27.55 5.72 27.66 5.7 27.77C5.65 28 5.58 28.25 5.5 28.5C5.41 28.76 5.32 29.01 5.24 29.23C5.22 29.31 5.19 29.38 5.17 29.45L12.52 28.89H12.53L14.79 28.72C15.19 28.69 15.66 28.63 16.1 28.64L16.15 28.64L16.15 28.64C16.44 28.66 16.74 28.76 16.98 28.96C17.11 29.07 17.26 29.29 17.41 29.53C17.56 29.77 17.73 30.07 17.89 30.38C18.12 30.8 18.35 31.25 18.52 31.58C19.6 30.08 20.75 28.55 21.84 27.06C22.47 26.19 23.54 24.66 24.26 23.9L24.27 23.89L24.27 23.89C24.78 23.42 25.23 23.11 25.75 22.74L25.98 22.57L25.98 22.57L27.84 21.25L27.85 21.25C29.21 20.28 30.71 19.18 32.11 18.23L30.74 17.51L30.74 17.51C30.54 17.41 30.32 17.28 30.16 17.21C29.83 17.08 29.59 16.9 29.42 16.68C29.25 16.45 29.17 16.2 29.13 15.95C29.1 15.69 29.11 15.43 29.13 15.16C29.14 15.03 29.16 14.9 29.17 14.77C29.18 14.64 29.2 14.5 29.21 14.37L29.38 12.17L29.94 5.11C29.76 5.17 29.57 5.23 29.38 5.29C29.16 5.35 28.96 5.43 28.73 5.51C28.5 5.58 28.26 5.65 28.01 5.68L27.98 5.69C27.65 5.72 27.27 5.57 27.08 5.27C26.73 4.72 26.4 4.13 26.08 3.59C25.45 2.55 24.83 1.52 24.22 0.48L22.61 3.04H22.61C22.51 3.19 22.35 3.46 22.17 3.75C22 4.02 21.82 4.3 21.71 4.43L21.71 4.43C21.56 4.61 21.36 4.73 21.13 4.78L21.13 4.78C20.92 4.82 20.72 4.78 20.55 4.73C20.38 4.67 20.21 4.58 20.09 4.52C19.61 4.28 19.12 4.04 18.64 3.79L18.91 7.34C18.94 7.7 19 8.13 19.01 8.51V8.51C19.01 8.64 19.03 8.8 19.05 8.99C19.07 9.17 19.08 9.37 19.07 9.56C19.05 9.75 19 9.95 18.88 10.12C18.74 10.29 18.55 10.4 18.3 10.44L18.27 10.44C18.07 10.47 17.89 10.4 17.74 10.3C17.6 10.21 17.46 10.07 17.35 9.93C17.13 9.67 16.93 9.33 16.82 9.17L16.82 9.17L15.67 7.54C14.51 5.88 13.23 4.12 12.08 2.43C11.78 3.31 11.49 4.27 11.13 5.11C11.04 5.31 10.84 5.46 10.66 5.55C10.47 5.64 10.23 5.71 10.02 5.66C9.47 5.55 8.91 5.37 8.38 5.22L8.38 5.22L8.38 5.22L4.78 4.18L4.78 4.18C4.69 4.16 4.42 4.08 4.16 4C4.1 3.98 4.05 3.97 3.99 3.95L5.18 7.89Z\" fill=\"url(#g4)\"/><defs><linearGradient id=\"g0\" x1=\"3.66\" y1=\"3.62\" x2=\"25.15\" y2=\"25.49\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#EB5C1A\"/><stop offset=\"1\" stop-color=\"#CD3E2E\"/></linearGradient><linearGradient id=\"g1\" x1=\"27.28\" y1=\"1.91\" x2=\"2.36\" y2=\"27.27\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0.36\" stop-color=\"#EB5C1A\" stop-opacity=\"0\"/><stop offset=\"0.5\" stop-color=\"#EB5C1A\"/><stop offset=\"0.62\" stop-color=\"#EB5C1A\" stop-opacity=\"0\"/></linearGradient><linearGradient id=\"g2\" x1=\"36.46\" y1=\"13.12\" x2=\"11.14\" y2=\"12.29\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0.34\" stop-color=\"#EB5C1A\" stop-opacity=\"0\"/><stop offset=\"0.5\" stop-color=\"#EB5C1A\"/><stop offset=\"0.65\" stop-color=\"#EB5C1A\" stop-opacity=\"0\"/></linearGradient><linearGradient id=\"g3\" x1=\"18.9\" y1=\"11.28\" x2=\"18.34\" y2=\"36.85\" gradientUnits=\"userSpaceOnUse\"><stop offset=\"0.34\" stop-color=\"#EB5C1A\" stop-opacity=\"0\"/><stop offset=\"0.5\" stop-color=\"#EB5C1A\"/><stop offset=\"0.65\" stop-color=\"#EB5C1A\" stop-opacity=\"0\"/></linearGradient><linearGradient id=\"g4\" x1=\"3.66\" y1=\"3.62\" x2=\"24.21\" y2=\"24.27\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#CD3E2E\"/><stop offset=\"0.33\" stop-color=\"#EB5C1A\"/><stop offset=\"0.66\" stop-color=\"#CD3E2E\"/><stop offset=\"1\" stop-color=\"#EB5C1A\"/></linearGradient></defs></svg>";
package/dist/logo.js ADDED
@@ -0,0 +1,4 @@
1
+ /** The Mapled logo — apps/web/public/brand/logo.svg (Figma, 122×32), with
2
+ its coordinates rounded to two decimals so the sign-in page carries it
3
+ inline: the page is served from 127.0.0.1 and loads nothing. */
4
+ export const LOGO_SVG = `<svg width="122" height="32" role="img" aria-label="Mapled" viewBox="0 0 122 32.03" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M41.62 7.49H45.87L51.57 21.08H51.8L57.49 7.49H61.75V26.25H58.41V13.36H58.23L52.93 26.2H50.44L45.13 13.33H44.96V26.25H41.62V7.49Z" fill="#FEFEFE"/><path d="M67.41 26.54C66.5 26.54 65.68 26.38 64.95 26.06C64.22 25.74 63.65 25.26 63.23 24.63C62.81 24 62.6 23.23 62.6 22.3C62.6 21.51 62.75 20.85 63.05 20.33C63.35 19.81 63.76 19.4 64.27 19.09C64.79 18.78 65.38 18.54 66.03 18.38C66.68 18.22 67.36 18.1 68.06 18.02C68.9 17.94 69.59 17.86 70.11 17.8C70.64 17.72 71.02 17.61 71.26 17.47C71.5 17.31 71.62 17.08 71.62 16.76V16.71C71.62 16.02 71.41 15.48 70.99 15.1C70.57 14.72 69.97 14.53 69.18 14.53C68.35 14.53 67.69 14.71 67.21 15.07C66.73 15.42 66.4 15.84 66.23 16.32L63.07 15.88C63.32 15.03 63.73 14.31 64.3 13.74C64.88 13.16 65.58 12.72 66.41 12.44C67.24 12.14 68.16 12 69.17 12C69.86 12 70.55 12.08 71.24 12.23C71.92 12.39 72.55 12.66 73.12 13.02C73.69 13.38 74.14 13.87 74.49 14.5C74.84 15.12 75.01 15.9 75.01 16.83V26.25H71.75V24.32H71.64C71.43 24.71 71.14 25.08 70.77 25.42C70.4 25.75 69.93 26.03 69.37 26.23C68.82 26.43 68.16 26.54 67.41 26.54ZM68.29 24.1C68.98 24.1 69.57 23.97 70.07 23.7C70.57 23.44 70.95 23.08 71.22 22.64C71.49 22.2 71.63 21.72 71.63 21.2V19.55C71.52 19.63 71.34 19.71 71.09 19.78C70.84 19.86 70.56 19.92 70.24 19.98C69.93 20.03 69.62 20.08 69.32 20.12C69.01 20.17 68.74 20.2 68.52 20.23C68.01 20.3 67.56 20.41 67.16 20.56C66.76 20.71 66.45 20.93 66.21 21.2C65.98 21.47 65.87 21.82 65.87 22.25C65.87 22.86 66.1 23.32 66.55 23.63C67.01 23.94 67.59 24.1 68.29 24.1Z" fill="#FEFEFE"/><path d="M76.31 31.53V12.18H79.65V14.51H79.85C80.02 14.16 80.27 13.8 80.59 13.42C80.9 13.03 81.34 12.69 81.88 12.42C82.42 12.14 83.12 12 83.96 12C85.07 12 86.07 12.27 86.97 12.83C87.87 13.38 88.58 14.2 89.1 15.28C89.63 16.35 89.9 17.67 89.9 19.23C89.9 20.78 89.64 22.09 89.12 23.17C88.6 24.25 87.9 25.08 87 25.65C86.11 26.21 85.1 26.5 83.97 26.5C83.14 26.5 82.46 26.36 81.92 26.1C81.37 25.83 80.94 25.5 80.6 25.12C80.28 24.74 80.03 24.38 79.85 24.03H79.71V31.53H76.31ZM79.64 19.22C79.64 20.13 79.77 20.92 80.03 21.61C80.3 22.29 80.69 22.83 81.19 23.21C81.69 23.59 82.3 23.78 83.02 23.78C83.77 23.78 84.4 23.58 84.91 23.19C85.41 22.79 85.79 22.25 86.05 21.57C86.31 20.88 86.44 20.1 86.44 19.22C86.44 18.34 86.31 17.57 86.06 16.89C85.8 16.21 85.42 15.68 84.92 15.29C84.41 14.91 83.78 14.72 83.02 14.72C82.3 14.72 81.68 14.9 81.18 15.28C80.67 15.65 80.29 16.17 80.02 16.84C79.77 17.51 79.64 18.31 79.64 19.22Z" fill="#FEFEFE"/><path d="M94.09 7.49V26.25H90.69V7.49H94.09Z" fill="#FEFEFE"/><path d="M101.84 26.53C100.39 26.53 99.15 26.23 98.1 25.65C97.05 25.05 96.25 24.22 95.69 23.14C95.13 22.05 94.84 20.77 94.84 19.3C94.84 17.85 95.13 16.58 95.69 15.49C96.26 14.39 97.05 13.53 98.07 12.92C99.09 12.3 100.28 12 101.66 12C102.54 12 103.38 12.14 104.17 12.42C104.96 12.69 105.66 13.12 106.27 13.7C106.88 14.28 107.36 15.02 107.71 15.92C108.06 16.81 108.24 17.87 108.24 19.11V20.12H96.44V17.89H104.98C104.98 17.25 104.84 16.69 104.56 16.19C104.29 15.69 103.9 15.3 103.41 15.01C102.92 14.72 102.35 14.58 101.7 14.58C101.01 14.58 100.4 14.74 99.88 15.07C99.35 15.4 98.94 15.83 98.65 16.36C98.36 16.88 98.22 17.46 98.21 18.09V20.04C98.21 20.86 98.36 21.56 98.67 22.15C98.97 22.73 99.4 23.17 99.95 23.48C100.5 23.79 101.15 23.94 101.88 23.94C102.38 23.94 102.82 23.88 103.22 23.74C103.62 23.6 103.97 23.4 104.26 23.13C104.56 22.86 104.78 22.53 104.93 22.13L108.1 22.48C107.9 23.3 107.51 24.01 106.95 24.62C106.4 25.23 105.68 25.7 104.82 26.03C103.95 26.36 102.95 26.53 101.84 26.53Z" fill="#FEFEFE"/><path d="M114.34 26.5C113.2 26.5 112.19 26.21 111.3 25.65C110.41 25.08 109.7 24.25 109.18 23.17C108.66 22.09 108.4 20.78 108.4 19.23C108.4 17.67 108.67 16.35 109.19 15.28C109.72 14.2 110.44 13.38 111.34 12.83C112.24 12.27 113.24 12 114.34 12C115.19 12 115.88 12.14 116.43 12.42C116.97 12.69 117.4 13.03 117.72 13.42C118.04 13.8 118.28 14.16 118.46 14.51H118.6V7.49H122V26.25H118.66V24.03H118.46C118.28 24.38 118.03 24.74 117.7 25.12C117.37 25.5 116.93 25.83 116.39 26.1C115.84 26.36 115.16 26.5 114.34 26.5ZM115.28 23.78C116 23.78 116.61 23.59 117.12 23.21C117.62 22.83 118.01 22.29 118.27 21.61C118.53 20.92 118.66 20.13 118.66 19.22C118.66 18.31 118.53 17.51 118.27 16.84C118.01 16.17 117.63 15.65 117.13 15.28C116.63 14.9 116.01 14.72 115.28 14.72C114.53 14.72 113.9 14.91 113.39 15.29C112.88 15.68 112.5 16.21 112.25 16.89C111.99 17.57 111.86 18.34 111.86 19.22C111.86 20.1 111.99 20.88 112.25 21.57C112.51 22.25 112.89 22.79 113.4 23.19C113.91 23.58 114.54 23.78 115.28 23.78Z" fill="#FEFEFE"/><path d="M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z" fill="url(#g0)"/><path d="M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z" fill="url(#g1)"/><path d="M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z" fill="url(#g2)"/><path d="M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.8 5.28 10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.25 4.51 21.41 4.42 21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15C27.42 5.37 27.72 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39C29.36 15.46 29.05 16.52 30.25 17C30.42 17.07 30.67 17.22 30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2C20.86 28.79 19.62 30.42 18.49 32.03C18.17 31.43 17.22 29.48 16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.49 4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.15 18.39 10.24 18.27C10.35 18.14 10.39 17.97 10.36 17.8C10.28 17.39 9.58 17.02 9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.37 10.62 5.56 10.2 5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63Z" fill="url(#g3)"/><path d="M3.65 3.63C3.75 3.63 4.68 3.91 4.85 3.96L8.45 5C8.98 5.15 9.53 5.33 10.07 5.44C10.34 5.49 10.79 5.28 10.91 5.03L10.91 5.02C11.33 4.04 11.66 2.92 12.01 1.92C13.22 3.73 14.61 5.62 15.86 7.41L17.01 9.04C17.26 9.4 17.74 10.29 18.24 10.22C19.1 10.1 18.79 9.1 18.77 8.51C18.76 8.14 18.71 7.73 18.68 7.36L18.37 3.38C18.97 3.71 19.59 4.01 20.19 4.32C20.45 4.45 20.79 4.62 21.08 4.55C21.24 4.52 21.39 4.43 21.51 4.31L21.53 4.28C21.72 4.06 22.22 3.22 22.41 2.92L24.23 0.03C24.9 1.18 25.59 2.33 26.28 3.47C26.61 4.03 26.93 4.6 27.28 5.15L27.3 5.17C27.44 5.38 27.73 5.49 27.98 5.46C28.44 5.39 28.87 5.2 29.31 5.07C29.61 4.98 29.91 4.88 30.21 4.79L29.61 12.19L29.44 14.39L29.43 14.49C29.34 15.53 29.09 16.53 30.25 17C30.4 17.06 30.61 17.18 30.78 17.27L30.85 17.31L32.57 18.2C31.07 19.2 29.46 20.39 27.98 21.44L26.12 22.76C25.49 23.21 25 23.53 24.43 24.05C23.72 24.8 22.67 26.31 22.03 27.2L21.81 27.49C20.71 28.99 19.55 30.52 18.49 32.03C18.18 31.44 17.25 29.54 16.84 29.15L16.82 29.13C16.63 28.97 16.38 28.89 16.13 28.87C15.7 28.85 15.24 28.91 14.81 28.95L4.84 29.7C4.99 29.18 5.63 27.67 5.5 27.23C5.46 27.09 5.37 26.96 5.26 26.88C4.73 26.49 3.87 26.07 3.27 25.72C2.18 25.1 1.11 24.42 0 23.84L2.94 22.05C3.24 21.86 4.1 21.37 4.33 21.18C4.44 21.09 4.53 20.96 4.58 20.83C4.62 20.7 4.64 20.5 4.59 20.37L4.58 20.36C4.25 19.59 3.81 18.82 3.42 18.08L7.84 18.42C8.46 18.46 9.19 18.53 9.81 18.52C9.95 18.52 10.13 18.4 10.23 18.29L10.24 18.27C10.34 18.15 10.38 17.99 10.36 17.83L10.36 17.8C10.29 17.42 9.66 17.07 9.3 16.83L9.23 16.79L1.91 11.84C2.94 11.46 4.09 11.18 5.09 10.74C5.36 10.63 5.55 10.22 5.5 9.93L5.49 9.9C5.36 9.25 5.14 8.6 4.95 7.95L3.65 3.63ZM5.18 7.89C5.37 8.52 5.58 9.19 5.72 9.85C5.77 10.06 5.72 10.29 5.63 10.48C5.54 10.67 5.39 10.86 5.19 10.95C4.67 11.17 4.12 11.36 3.58 11.53C3.19 11.66 2.8 11.78 2.42 11.91L9.36 16.6C9.51 16.7 9.81 16.87 10.04 17.05C10.16 17.14 10.28 17.24 10.38 17.36C10.48 17.47 10.56 17.61 10.59 17.76H10.59C10.63 17.99 10.57 18.23 10.43 18.41L10.43 18.41L10.43 18.41C10.36 18.5 10.27 18.58 10.17 18.64C10.07 18.69 9.95 18.75 9.81 18.75C9.18 18.76 8.44 18.69 7.82 18.65L7.82 18.65L7.82 18.65L3.82 18.34C4.16 18.96 4.52 19.62 4.8 20.27C4.84 20.38 4.85 20.5 4.85 20.6C4.85 20.7 4.83 20.81 4.8 20.9L4.8 20.9L4.8 20.9C4.73 21.08 4.62 21.23 4.48 21.36C4.35 21.46 4.06 21.64 3.78 21.81C3.49 21.99 3.21 22.15 3.06 22.24L0.46 23.83C1.45 24.37 2.43 24.97 3.39 25.52C3.69 25.7 4.04 25.88 4.41 26.08C4.76 26.28 5.12 26.49 5.4 26.69C5.55 26.81 5.67 26.98 5.72 27.17C5.75 27.26 5.75 27.36 5.75 27.45C5.74 27.55 5.72 27.66 5.7 27.77C5.65 28 5.58 28.25 5.5 28.5C5.41 28.76 5.32 29.01 5.24 29.23C5.22 29.31 5.19 29.38 5.17 29.45L12.52 28.89H12.53L14.79 28.72C15.19 28.69 15.66 28.63 16.1 28.64L16.15 28.64L16.15 28.64C16.44 28.66 16.74 28.76 16.98 28.96C17.11 29.07 17.26 29.29 17.41 29.53C17.56 29.77 17.73 30.07 17.89 30.38C18.12 30.8 18.35 31.25 18.52 31.58C19.6 30.08 20.75 28.55 21.84 27.06C22.47 26.19 23.54 24.66 24.26 23.9L24.27 23.89L24.27 23.89C24.78 23.42 25.23 23.11 25.75 22.74L25.98 22.57L25.98 22.57L27.84 21.25L27.85 21.25C29.21 20.28 30.71 19.18 32.11 18.23L30.74 17.51L30.74 17.51C30.54 17.41 30.32 17.28 30.16 17.21C29.83 17.08 29.59 16.9 29.42 16.68C29.25 16.45 29.17 16.2 29.13 15.95C29.1 15.69 29.11 15.43 29.13 15.16C29.14 15.03 29.16 14.9 29.17 14.77C29.18 14.64 29.2 14.5 29.21 14.37L29.38 12.17L29.94 5.11C29.76 5.17 29.57 5.23 29.38 5.29C29.16 5.35 28.96 5.43 28.73 5.51C28.5 5.58 28.26 5.65 28.01 5.68L27.98 5.69C27.65 5.72 27.27 5.57 27.08 5.27C26.73 4.72 26.4 4.13 26.08 3.59C25.45 2.55 24.83 1.52 24.22 0.48L22.61 3.04H22.61C22.51 3.19 22.35 3.46 22.17 3.75C22 4.02 21.82 4.3 21.71 4.43L21.71 4.43C21.56 4.61 21.36 4.73 21.13 4.78L21.13 4.78C20.92 4.82 20.72 4.78 20.55 4.73C20.38 4.67 20.21 4.58 20.09 4.52C19.61 4.28 19.12 4.04 18.64 3.79L18.91 7.34C18.94 7.7 19 8.13 19.01 8.51V8.51C19.01 8.64 19.03 8.8 19.05 8.99C19.07 9.17 19.08 9.37 19.07 9.56C19.05 9.75 19 9.95 18.88 10.12C18.74 10.29 18.55 10.4 18.3 10.44L18.27 10.44C18.07 10.47 17.89 10.4 17.74 10.3C17.6 10.21 17.46 10.07 17.35 9.93C17.13 9.67 16.93 9.33 16.82 9.17L16.82 9.17L15.67 7.54C14.51 5.88 13.23 4.12 12.08 2.43C11.78 3.31 11.49 4.27 11.13 5.11C11.04 5.31 10.84 5.46 10.66 5.55C10.47 5.64 10.23 5.71 10.02 5.66C9.47 5.55 8.91 5.37 8.38 5.22L8.38 5.22L8.38 5.22L4.78 4.18L4.78 4.18C4.69 4.16 4.42 4.08 4.16 4C4.1 3.98 4.05 3.97 3.99 3.95L5.18 7.89Z" fill="url(#g4)"/><defs><linearGradient id="g0" x1="3.66" y1="3.62" x2="25.15" y2="25.49" gradientUnits="userSpaceOnUse"><stop stop-color="#EB5C1A"/><stop offset="1" stop-color="#CD3E2E"/></linearGradient><linearGradient id="g1" x1="27.28" y1="1.91" x2="2.36" y2="27.27" gradientUnits="userSpaceOnUse"><stop offset="0.36" stop-color="#EB5C1A" stop-opacity="0"/><stop offset="0.5" stop-color="#EB5C1A"/><stop offset="0.62" stop-color="#EB5C1A" stop-opacity="0"/></linearGradient><linearGradient id="g2" x1="36.46" y1="13.12" x2="11.14" y2="12.29" gradientUnits="userSpaceOnUse"><stop offset="0.34" stop-color="#EB5C1A" stop-opacity="0"/><stop offset="0.5" stop-color="#EB5C1A"/><stop offset="0.65" stop-color="#EB5C1A" stop-opacity="0"/></linearGradient><linearGradient id="g3" x1="18.9" y1="11.28" x2="18.34" y2="36.85" gradientUnits="userSpaceOnUse"><stop offset="0.34" stop-color="#EB5C1A" stop-opacity="0"/><stop offset="0.5" stop-color="#EB5C1A"/><stop offset="0.65" stop-color="#EB5C1A" stop-opacity="0"/></linearGradient><linearGradient id="g4" x1="3.66" y1="3.62" x2="24.21" y2="24.27" gradientUnits="userSpaceOnUse"><stop stop-color="#CD3E2E"/><stop offset="0.33" stop-color="#EB5C1A"/><stop offset="0.66" stop-color="#CD3E2E"/><stop offset="1" stop-color="#EB5C1A"/></linearGradient></defs></svg>`;
@@ -78,17 +78,43 @@ export declare function parseManifest(raw: string, file: string): {
78
78
  manifest: Manifest;
79
79
  problems: Problem[];
80
80
  };
81
- export type SchemaIndex = Map<string, {
81
+ type SubFieldEntry = {
82
82
  displayName: string;
83
- kind: string;
84
- fields: Map<string, {
83
+ type: string;
84
+ /** a component type's group sub-field (§45.4): the sub-fields of its items */
85
+ sub?: Map<string, SubFieldEntry>;
86
+ };
87
+ type FieldEntry = SubFieldEntry & {
88
+ /** a group's sub-fields */
89
+ sub?: Map<string, SubFieldEntry>;
90
+ /** the block types a components field takes (§45.4) */
91
+ allowed?: string[];
92
+ };
93
+ export type SchemaIndex = {
94
+ collections: Map<string, {
85
95
  displayName: string;
86
- type: string;
96
+ kind: string;
97
+ fields: Map<string, FieldEntry>;
98
+ /** previous key → current key (§33.5): a binding on it still works, through the alias */
99
+ aliases: Map<string, string>;
87
100
  }>;
88
- /** previous key → current key (§33.5): a binding on it still works, through the alias */
89
- aliases: Map<string, string>;
90
- }>;
101
+ /** the component types a components field names (§45.4) */
102
+ types: Map<string, {
103
+ displayName: string;
104
+ fields: Map<string, SubFieldEntry>;
105
+ }>;
106
+ };
91
107
  export declare function indexSchema(schema: Schema): SchemaIndex;
108
+ /** A binding's field as a path (§20) — the API's `fieldPath`
109
+ (lib/bindings.ts): `team.name`, a group's sub-field;
110
+ `sections[hero].heading`, a sub-field of the blocks of one type;
111
+ `sections[pricing].tiers.price`, a sub-field of the items of a
112
+ block's group sub-field; null for a field's plain key. */
113
+ export declare function fieldPath(field: string): {
114
+ head: string;
115
+ type: string | null;
116
+ rest: string[];
117
+ } | null;
92
118
  /** What a well-formed manifest still gets wrong: bindings the schema
93
119
  can't back (Mapled would grade them outdated or mismatched), files the
94
120
  repository doesn't have, pages the list doesn't know. Warnings only —
@@ -126,3 +152,4 @@ export type ManifestComparison = {
126
152
  localesDiffer: boolean;
127
153
  };
128
154
  export declare function compareManifests(local: Manifest, remote: Manifest): ManifestComparison;
155
+ export {};
package/dist/manifest.js CHANGED
@@ -30,7 +30,7 @@ export const LIMITS = { bindings: 500, pages: 200, notes: 20 };
30
30
  export const COMPATIBLE = {
31
31
  text: ["short_text", "long_text", "slug", "number", "date", "datetime", "enum", "url", "email", "color"],
32
32
  rich_text: ["rich_text", "long_text"],
33
- image: ["image"],
33
+ image: ["image", "gallery"],
34
34
  image_alt: ["short_text", "long_text"],
35
35
  link: ["short_text", "slug", "url", "email", "file"],
36
36
  number: ["number"],
@@ -45,6 +45,7 @@ export function targetFor(fieldType) {
45
45
  case "rich_text":
46
46
  return "rich_text";
47
47
  case "image":
48
+ case "gallery":
48
49
  return "image";
49
50
  case "url":
50
51
  case "email":
@@ -59,6 +60,7 @@ export function targetFor(fieldType) {
59
60
  return "boolean";
60
61
  case "relation":
61
62
  case "group":
63
+ case "components":
62
64
  case "json":
63
65
  case "location":
64
66
  return "other";
@@ -72,6 +74,7 @@ export const FIELD_TYPE_LABEL = {
72
74
  rich_text: "rich text",
73
75
  slug: "slug",
74
76
  image: "image",
77
+ gallery: "gallery",
75
78
  file: "file",
76
79
  number: "number",
77
80
  boolean: "boolean",
@@ -300,22 +303,87 @@ export function parseManifest(raw, file) {
300
303
  }
301
304
  return { manifest, problems };
302
305
  }
306
+ const subFields = (fields) => new Map(fields.map((s) => [s.key, { displayName: s.displayName, type: s.type, ...(s.group ? { sub: subFields(s.group.fields) } : {}) }]));
303
307
  export function indexSchema(schema) {
304
- const index = new Map();
308
+ const collections = new Map();
309
+ // a computed field (§14.9) binds as a field of its result type
310
+ const COMPUTED_AS = { number: "number", text: "short_text", boolean: "boolean", date: "date", datetime: "datetime" };
305
311
  for (const c of schema.collections) {
306
312
  const fields = new Map();
307
313
  const aliases = new Map();
308
- // a computed field (§14.9) binds as a field of its result type
309
- const COMPUTED_AS = { number: "number", text: "short_text", boolean: "boolean", date: "date", datetime: "datetime" };
310
314
  for (const f of c.fields) {
311
315
  const type = f.type === "computed" && f.computed ? (COMPUTED_AS[f.computed.resultType] ?? f.type) : f.type;
312
- fields.set(f.key, { displayName: f.displayName, type });
316
+ const entry = { displayName: f.displayName, type };
317
+ if (f.group)
318
+ entry.sub = subFields(f.group.fields);
319
+ if (f.components)
320
+ entry.allowed = [...f.components.allowed];
321
+ fields.set(f.key, entry);
313
322
  for (const a of f.aliases ?? [])
314
323
  aliases.set(a, f.key);
315
324
  }
316
- index.set(c.key, { displayName: c.displayName, kind: c.kind, fields, aliases });
325
+ collections.set(c.key, { displayName: c.displayName, kind: c.kind, fields, aliases });
317
326
  }
318
- return index;
327
+ const types = new Map((schema.componentTypes ?? []).map((t) => [t.key, { displayName: t.displayName, fields: subFields(t.fields) }]));
328
+ return { collections, types };
329
+ }
330
+ /** A binding's field as a path (§20) — the API's `fieldPath`
331
+ (lib/bindings.ts): `team.name`, a group's sub-field;
332
+ `sections[hero].heading`, a sub-field of the blocks of one type;
333
+ `sections[pricing].tiers.price`, a sub-field of the items of a
334
+ block's group sub-field; null for a field's plain key. */
335
+ export function fieldPath(field) {
336
+ const m = /^([^.[\]]+)(?:\[([^.[\]]+)\])?((?:\.[^.[\]]+)*)$/.exec(field);
337
+ if (!m || (m[2] === undefined && m[3] === ""))
338
+ return null;
339
+ return { head: m[1], type: m[2] ?? null, rest: m[3] === "" ? [] : m[3].slice(1).split(".") };
340
+ }
341
+ function reach(collection, key, types) {
342
+ const path = fieldPath(key);
343
+ const head = path?.head ?? key;
344
+ const renamedTo = collection.aliases.get(head) ?? null;
345
+ const field = collection.fields.get(renamedTo ?? head);
346
+ if (!field)
347
+ return { kind: "gone", why: `no field "${head}" in "${collection.displayName}"` };
348
+ if (!path || renamedTo)
349
+ return { kind: "field", type: field.type, displayName: field.displayName, renamedTo };
350
+ let sub;
351
+ if (field.sub) {
352
+ if (path.type !== null)
353
+ return { kind: "gone", why: `"${field.displayName}" is a group, not a list of blocks` };
354
+ sub = field.sub.get(path.rest[0]);
355
+ if (!sub)
356
+ return { kind: "gone", why: `no sub-field "${path.rest[0]}" in "${field.displayName}"` };
357
+ }
358
+ else if (field.allowed) {
359
+ if (path.type === null)
360
+ return { kind: "unchecked" };
361
+ const type = types.get(path.type);
362
+ if (!type)
363
+ return { kind: "gone", why: `no block type "${path.type}"` };
364
+ if (!field.allowed.includes(path.type))
365
+ return { kind: "gone", why: `"${field.displayName}" doesn't take "${type.displayName}" blocks` };
366
+ if (path.rest.length === 0)
367
+ return { kind: "unchecked" };
368
+ sub = type.fields.get(path.rest[0]);
369
+ if (!sub)
370
+ return { kind: "gone", why: `no sub-field "${path.rest[0]}" in "${type.displayName}"` };
371
+ }
372
+ else {
373
+ return { kind: "unchecked" };
374
+ }
375
+ // a block's group sub-field (§45.4): a sub-field of its items
376
+ if (sub.sub && path.rest.length > 1) {
377
+ const inner = sub.sub.get(path.rest[1]);
378
+ if (!inner)
379
+ return { kind: "gone", why: `no sub-field "${path.rest[1]}" in "${sub.displayName}"` };
380
+ if (path.rest.length > 2)
381
+ return { kind: "unchecked" };
382
+ return { kind: "field", type: inner.type, displayName: inner.displayName, renamedTo: null };
383
+ }
384
+ if (path.rest.length > 1)
385
+ return { kind: "unchecked" };
386
+ return { kind: "field", type: sub.type, displayName: sub.displayName, renamedTo: null };
319
387
  }
320
388
  function article(word) {
321
389
  return /^[aeiou]/i.test(word) ? "an" : "a";
@@ -346,23 +414,23 @@ export async function checkManifest(manifest, opts) {
346
414
  const at = `bindings[${i}]`;
347
415
  const who = `${at} (${b.key})`;
348
416
  if (index) {
349
- const collection = index.get(b.collection);
417
+ const collection = index.collections.get(b.collection);
350
418
  if (!collection)
351
419
  warn(`${at}.collection`, `${who}: no collection "${b.collection}" in the schema — Mapled marks it outdated.`);
352
420
  else if (b.field) {
353
- const field = collection.fields.get(b.field);
354
- const renamedTo = collection.aliases.get(b.field);
355
- if (!field && renamedTo) {
356
- warn(`${at}.field`, `${who}: "${b.field}" in "${collection.displayName}" was renamed to "${renamedTo}" — it still works through an alias, and Mapled marks the binding outdated. Read "${renamedTo}", then remove the alias.`);
421
+ const reached = reach(collection, b.field, index.types);
422
+ const head = fieldPath(b.field)?.head ?? b.field;
423
+ if (reached.kind === "gone") {
424
+ warn(`${at}.field`, `${who}: ${reached.why} — Mapled marks it outdated.`);
357
425
  }
358
- else if (!field) {
359
- warn(`${at}.field`, `${who}: no field "${b.field}" in "${collection.displayName}" — Mapled marks it outdated.`);
426
+ else if (reached.kind === "field" && reached.renamedTo) {
427
+ warn(`${at}.field`, `${who}: "${head}" in "${collection.displayName}" was renamed to "${reached.renamedTo}" — it still works through an alias, and Mapled marks the binding outdated. Read "${reached.renamedTo}", then remove the alias.`);
360
428
  }
361
- else {
429
+ else if (reached.kind === "field") {
362
430
  const allowed = COMPATIBLE[b.target];
363
- if (allowed && !allowed.includes(field.type)) {
364
- const label = typeLabel(field.type);
365
- warn(`${at}.target`, `${who}: "${field.displayName}" is ${article(label)} ${label} field, but the binding renders it as ${b.target.replace("_", " ")}.`);
431
+ if (allowed && !allowed.includes(reached.type)) {
432
+ const label = typeLabel(reached.type);
433
+ warn(`${at}.target`, `${who}: "${reached.displayName}" is ${article(label)} ${label} field, but the binding renders it as ${b.target.replace("_", " ")}.`);
366
434
  }
367
435
  }
368
436
  }