create-dig-app 0.3.0 → 0.3.2

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.
@@ -23,14 +23,7 @@
23
23
  // hash: metadata_hash = sha256(canonical_json_bytes); data/license_hash = sha256(bytes)
24
24
 
25
25
  import { createHash } from "node:crypto";
26
- import {
27
- existsSync,
28
- mkdirSync,
29
- readFileSync,
30
- readdirSync,
31
- statSync,
32
- writeFileSync,
33
- } from "node:fs";
26
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
34
27
  import { basename, dirname, extname, join } from "node:path";
35
28
  import { fileURLToPath } from "node:url";
36
29
 
@@ -52,7 +45,10 @@ function attrValue(v) {
52
45
 
53
46
  function normalizeAttributes(attrs) {
54
47
  return (attrs ?? [])
55
- .map((a) => ({ trait_type: String(a?.trait_type ?? a?.traitType ?? "").trim(), value: attrValue(a?.value) }))
48
+ .map((a) => ({
49
+ trait_type: String(a?.trait_type ?? a?.traitType ?? "").trim(),
50
+ value: attrValue(a?.value),
51
+ }))
56
52
  .filter((a) => a.trait_type !== "");
57
53
  }
58
54
 
@@ -84,7 +80,8 @@ function buildChip0007Metadata(input) {
84
80
  if (attributes.length > 0) md.attributes = attributes;
85
81
  if (input?.series_number != null) md.series_number = Number(input.series_number);
86
82
  if (input?.series_total != null) md.series_total = Number(input.series_total);
87
- if (input?.minting_tool != null && String(input.minting_tool) !== "") md.minting_tool = String(input.minting_tool);
83
+ if (input?.minting_tool != null && String(input.minting_tool) !== "")
84
+ md.minting_tool = String(input.minting_tool);
88
85
  return md;
89
86
  }
90
87
 
@@ -119,7 +116,9 @@ const FILE_KEYS = new Set(["file", "filename", "image", "media", "asset"]);
119
116
  const DESC_KEYS = new Set(["description", "desc"]);
120
117
 
121
118
  function classifyKey(key) {
122
- const k = String(key ?? "").trim().toLowerCase();
119
+ const k = String(key ?? "")
120
+ .trim()
121
+ .toLowerCase();
123
122
  if (NAME_KEYS.has(k)) return "name";
124
123
  if (FILE_KEYS.has(k)) return "file";
125
124
  if (DESC_KEYS.has(k)) return "description";
@@ -134,58 +133,79 @@ function splitCsvLine(line) {
134
133
  const ch = line[i];
135
134
  if (inQuotes) {
136
135
  if (ch === '"') {
137
- if (line[i + 1] === '"') { cur += '"'; i++; }
138
- else inQuotes = false;
136
+ if (line[i + 1] === '"') {
137
+ cur += '"';
138
+ i++;
139
+ } else inQuotes = false;
139
140
  } else cur += ch;
140
141
  } else if (ch === '"') inQuotes = true;
141
- else if (ch === ",") { out.push(cur); cur = ""; }
142
- else cur += ch;
142
+ else if (ch === ",") {
143
+ out.push(cur);
144
+ cur = "";
145
+ } else cur += ch;
143
146
  }
144
147
  out.push(cur);
145
148
  return out.map((c) => c.trim());
146
149
  }
147
150
 
148
151
  function recordToItem(rec) {
149
- let name = "", description, file;
152
+ let name = "",
153
+ description,
154
+ file;
150
155
  const attributes = [];
151
156
  for (const [col, raw] of Object.entries(rec)) {
152
157
  const role = classifyKey(col);
153
158
  const val = raw == null ? "" : String(raw).trim();
154
- if (role === "name") { if (!name) name = val; }
155
- else if (role === "file") { if (!file && val) file = val; }
156
- else if (role === "description") { if (!description && val) description = val; }
157
- else if (val !== "") attributes.push({ trait_type: col, value: val });
159
+ if (role === "name") {
160
+ if (!name) name = val;
161
+ } else if (role === "file") {
162
+ if (!file && val) file = val;
163
+ } else if (role === "description") {
164
+ if (!description && val) description = val;
165
+ } else if (val !== "") attributes.push({ trait_type: col, value: val });
158
166
  }
159
167
  if (!name) throw new Error("Every item needs a name (a `name` or `title` column).");
160
168
  return { name, description: description || undefined, file: file || undefined, attributes };
161
169
  }
162
170
 
163
171
  function parseTraitsCsv(text) {
164
- const lines = String(text ?? "").split(/\r?\n/).filter((l) => l.trim() !== "");
172
+ const lines = String(text ?? "")
173
+ .split(/\r?\n/)
174
+ .filter((l) => l.trim() !== "");
165
175
  if (lines.length < 2) return [];
166
176
  const header = splitCsvLine(lines[0]);
167
177
  const items = [];
168
178
  for (let i = 1; i < lines.length; i++) {
169
179
  const cells = splitCsvLine(lines[i]);
170
180
  const rec = {};
171
- header.forEach((h, j) => { rec[h] = cells[j] ?? ""; });
181
+ header.forEach((h, j) => {
182
+ rec[h] = cells[j] ?? "";
183
+ });
172
184
  items.push(recordToItem(rec));
173
185
  }
174
186
  return items;
175
187
  }
176
188
 
177
189
  function jsonObjToItem(obj) {
178
- let name = "", description, file;
190
+ let name = "",
191
+ description,
192
+ file;
179
193
  let attributes = [];
180
194
  for (const [key, raw] of Object.entries(obj ?? {})) {
181
195
  const k = String(key).toLowerCase();
182
- if (k === "attributes" && Array.isArray(raw)) { attributes = normalizeAttributes(raw); continue; }
196
+ if (k === "attributes" && Array.isArray(raw)) {
197
+ attributes = normalizeAttributes(raw);
198
+ continue;
199
+ }
183
200
  const role = classifyKey(key);
184
201
  const val = raw == null ? "" : String(raw).trim();
185
- if (role === "name") { if (!name) name = val; }
186
- else if (role === "file") { if (!file && val) file = val; }
187
- else if (role === "description") { if (!description && val) description = val; }
188
- else if (val !== "") attributes.push({ trait_type: key, value: val });
202
+ if (role === "name") {
203
+ if (!name) name = val;
204
+ } else if (role === "file") {
205
+ if (!file && val) file = val;
206
+ } else if (role === "description") {
207
+ if (!description && val) description = val;
208
+ } else if (val !== "") attributes.push({ trait_type: key, value: val });
189
209
  }
190
210
  if (!name) throw new Error("Every item needs a name (a `name` or `title` field).");
191
211
  return { name, description: description || undefined, file: file || undefined, attributes };
@@ -205,7 +225,9 @@ function itemsFromImages(fileNames) {
205
225
  return (fileNames ?? []).map((file) => {
206
226
  const stem = String(file).replace(/\.[^.]+$/, "");
207
227
  const name = stem
208
- .replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim()
228
+ .replace(/[_-]+/g, " ")
229
+ .replace(/\s+/g, " ")
230
+ .trim()
209
231
  .replace(/\b\w/g, (c) => c.toUpperCase());
210
232
  return { name: name || stem, file, attributes: [] };
211
233
  });
@@ -214,7 +236,9 @@ function itemsFromImages(fileNames) {
214
236
  function capsuleResourceUris({ storeId, root, resource }) {
215
237
  const sid = String(storeId ?? "").trim();
216
238
  const r = String(root ?? "").trim();
217
- const res = String(resource ?? "").trim().replace(/^\/+/, "");
239
+ const res = String(resource ?? "")
240
+ .trim()
241
+ .replace(/^\/+/, "");
218
242
  const urn = `urn:dig:chia:${sid}${r ? `:${r}` : ""}/${res}`;
219
243
  const https = `https://${sid}.${CAPSULE_HTTPS_GATEWAY}/${res}`;
220
244
  return { urn, https, uris: [urn, https] };
@@ -225,11 +249,13 @@ const LICENSES = {
225
249
  title: "CC0 1.0 — public domain dedication",
226
250
  text: ({ holder = "the creator", year = new Date().getFullYear() } = {}) =>
227
251
  [
228
- "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication", "",
252
+ "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
253
+ "",
229
254
  `The work associated with this NFT is dedicated to the public domain by ${holder} (${year}).`,
230
255
  "To the extent possible under law, the creator has waived all copyright and related or",
231
256
  "neighboring rights to this work. You can copy, modify, distribute and perform the work, even",
232
- "for commercial purposes, all without asking permission.", "",
257
+ "for commercial purposes, all without asking permission.",
258
+ "",
233
259
  "SPDX-License-Identifier: CC0-1.0",
234
260
  "Full text: https://creativecommons.org/publicdomain/zero/1.0/legalcode",
235
261
  ].join("\n") + "\n",
@@ -238,10 +264,13 @@ const LICENSES = {
238
264
  title: "CC BY 4.0 — attribution required",
239
265
  text: ({ holder = "the creator", year = new Date().getFullYear() } = {}) =>
240
266
  [
241
- "Creative Commons Attribution 4.0 International (CC BY 4.0)", "",
242
- `Copyright (c) ${year} ${holder}.`, "",
267
+ "Creative Commons Attribution 4.0 International (CC BY 4.0)",
268
+ "",
269
+ `Copyright (c) ${year} ${holder}.`,
270
+ "",
243
271
  "You are free to share and adapt this work for any purpose, even commercially, provided you give",
244
- "appropriate credit, provide a link to the license, and indicate if changes were made.", "",
272
+ "appropriate credit, provide a link to the license, and indicate if changes were made.",
273
+ "",
245
274
  "SPDX-License-Identifier: CC-BY-4.0",
246
275
  "Full text: https://creativecommons.org/licenses/by/4.0/legalcode",
247
276
  ].join("\n") + "\n",
@@ -250,8 +279,10 @@ const LICENSES = {
250
279
  title: "All rights reserved — no reuse without permission",
251
280
  text: ({ holder = "the creator", year = new Date().getFullYear() } = {}) =>
252
281
  [
253
- "All Rights Reserved", "",
254
- `Copyright (c) ${year} ${holder}. All rights reserved.`, "",
282
+ "All Rights Reserved",
283
+ "",
284
+ `Copyright (c) ${year} ${holder}. All rights reserved.`,
285
+ "",
255
286
  "No part of the work associated with this NFT may be reproduced, distributed, or transmitted in",
256
287
  "any form or by any means without the prior written permission of the copyright holder, except",
257
288
  "for the holder's own personal display of the NFT they own.",
@@ -261,12 +292,15 @@ const LICENSES = {
261
292
  title: "Limited commercial license — holder may use commercially",
262
293
  text: ({ holder = "the creator", year = new Date().getFullYear() } = {}) =>
263
294
  [
264
- "Limited Commercial License (DIG Commercial 1.0)", "",
265
- `Copyright (c) ${year} ${holder}.`, "",
295
+ "Limited Commercial License (DIG Commercial 1.0)",
296
+ "",
297
+ `Copyright (c) ${year} ${holder}.`,
298
+ "",
266
299
  "The verified owner of this NFT is granted a worldwide, non-exclusive license to use, reproduce,",
267
300
  "and display the associated artwork for commercial purposes, up to gross revenues of US$100,000",
268
301
  "per year attributable to the artwork. This license transfers with ownership of the NFT and",
269
- "terminates on transfer. All other rights are reserved by the copyright holder.", "",
302
+ "terminates on transfer. All other rights are reserved by the copyright holder.",
303
+ "",
270
304
  "SPDX-License-Identifier: LicenseRef-DIG-Commercial-1.0",
271
305
  ].join("\n") + "\n",
272
306
  },
@@ -280,12 +314,16 @@ function licenseText(id, opts = {}) {
280
314
  }
281
315
 
282
316
  class ValidationError extends Error {
283
- constructor(message) { super(message); this.name = "ValidationError"; }
317
+ constructor(message) {
318
+ super(message);
319
+ this.name = "ValidationError";
320
+ }
284
321
  }
285
322
 
286
323
  function validateMetadata(md, checks = {}) {
287
324
  if (!md || typeof md !== "object") throw new ValidationError("metadata must be an object");
288
- if (md.format !== CHIP0007_FORMAT) throw new ValidationError(`format must be "${CHIP0007_FORMAT}" (got ${JSON.stringify(md.format)})`);
325
+ if (md.format !== CHIP0007_FORMAT)
326
+ throw new ValidationError(`format must be "${CHIP0007_FORMAT}" (got ${JSON.stringify(md.format)})`);
289
327
  if (typeof md.name !== "string" || md.name === "") throw new ValidationError("name is required");
290
328
  if ("attributes" in md) {
291
329
  if (!Array.isArray(md.attributes)) throw new ValidationError("attributes must be an array");
@@ -297,19 +335,27 @@ function validateMetadata(md, checks = {}) {
297
335
  }
298
336
  if ("collection" in md) {
299
337
  const c = md.collection;
300
- if (typeof c?.id !== "string" || typeof c?.name !== "string") throw new ValidationError("collection must have a string id and name");
338
+ if (typeof c?.id !== "string" || typeof c?.name !== "string")
339
+ throw new ValidationError("collection must have a string id and name");
301
340
  }
302
341
  if (checks.metadata_hash != null) {
303
342
  const got = metadataHashHex(md);
304
- if (got !== String(checks.metadata_hash).toLowerCase()) throw new ValidationError(`metadata_hash mismatch: computed ${got}, expected ${checks.metadata_hash}`);
343
+ if (got !== String(checks.metadata_hash).toLowerCase())
344
+ throw new ValidationError(`metadata_hash mismatch: computed ${got}, expected ${checks.metadata_hash}`);
305
345
  }
306
346
  if (checks.media?.data_hash != null && checks.media?.data_bytes != null) {
307
347
  const got = sha256Hex(checks.media.data_bytes);
308
- if (got !== String(checks.media.data_hash).toLowerCase()) throw new ValidationError(`data_hash mismatch: image bytes hash to ${got}, expected ${checks.media.data_hash}`);
348
+ if (got !== String(checks.media.data_hash).toLowerCase())
349
+ throw new ValidationError(
350
+ `data_hash mismatch: image bytes hash to ${got}, expected ${checks.media.data_hash}`,
351
+ );
309
352
  }
310
353
  if (checks.license?.license_hash != null && checks.license?.license_bytes != null) {
311
354
  const got = sha256Hex(checks.license.license_bytes);
312
- if (got !== String(checks.license.license_hash).toLowerCase()) throw new ValidationError(`license_hash mismatch: license bytes hash to ${got}, expected ${checks.license.license_hash}`);
355
+ if (got !== String(checks.license.license_hash).toLowerCase())
356
+ throw new ValidationError(
357
+ `license_hash mismatch: license bytes hash to ${got}, expected ${checks.license.license_hash}`,
358
+ );
313
359
  }
314
360
  }
315
361
 
@@ -332,7 +378,8 @@ function listImages(root) {
332
378
  const files = readdirSync(dir)
333
379
  .filter((f) => statSync(join(dir, f)).isFile() && IMAGE_EXT.has(extname(f).toLowerCase()))
334
380
  .sort();
335
- if (files.length === 0) throw new Error("no images found in images/ — add your item art (png/jpg/gif/webp/svg) first.");
381
+ if (files.length === 0)
382
+ throw new Error("no images found in images/ — add your item art (png/jpg/gif/webp/svg) first.");
336
383
  return files;
337
384
  }
338
385
 
@@ -358,7 +405,8 @@ function readGeneratedLicense(root, collection, storeId = PLACEHOLDER_STORE_ID)
358
405
  }
359
406
 
360
407
  function generateMetadata(root, opts = {}) {
361
- const storeId = opts.storeId ?? PLACEHOLDER_STORE_ID;
408
+ // Empty/whitespace storeId falls back to the placeholder — never an empty-store-id URI. (#1065)
409
+ const storeId = opts.storeId && String(opts.storeId).trim() ? opts.storeId : PLACEHOLDER_STORE_ID;
362
410
  const collection = loadCollection(root);
363
411
  const imageFiles = listImages(root);
364
412
  const items = resolveItems(root, imageFiles);
@@ -386,9 +434,12 @@ function generateMetadata(root, opts = {}) {
386
434
  description: item.description || undefined,
387
435
  attributes: md.attributes || [],
388
436
  media: {
389
- data_uris: data.uris, data_hash: dataHash,
390
- metadata_uris: meta.uris, metadata_hash: metadataHash,
391
- license_uris: license ? license.uris : [], license_hash: license ? license.hash : null,
437
+ data_uris: data.uris,
438
+ data_hash: dataHash,
439
+ metadata_uris: meta.uris,
440
+ metadata_hash: metadataHash,
441
+ license_uris: license ? license.uris : [],
442
+ license_hash: license ? license.hash : null,
392
443
  },
393
444
  });
394
445
  }
@@ -401,7 +452,8 @@ function generateLicense(root) {
401
452
  const collection = loadCollection(root);
402
453
  const id = collection?.license;
403
454
  if (!id) throw new Error('collection.json has no `license` field — set one (e.g. "cc0").');
404
- if (!LICENSES[id]) throw new Error(`Unknown license "${id}". Available: ${Object.keys(LICENSES).join(", ")}.`);
455
+ if (!LICENSES[id])
456
+ throw new Error(`Unknown license "${id}". Available: ${Object.keys(LICENSES).join(", ")}.`);
405
457
  const holder = collection.creator || collection.name || "the creator";
406
458
  const text = licenseText(id, { holder, year: new Date().getFullYear() });
407
459
  const licensesDir = join(root, "licenses");
@@ -422,14 +474,20 @@ function validateProject(root) {
422
474
  const manifestPath = join(root, "items.json");
423
475
  if (!existsSync(manifestPath)) throw new Error("items.json not found — run the metadata generator first.");
424
476
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
425
- if (!Array.isArray(manifest) || manifest.length === 0) throw new Error("items.json is empty — nothing to validate.");
477
+ if (!Array.isArray(manifest) || manifest.length === 0)
478
+ throw new Error("items.json is empty — nothing to validate.");
426
479
  const metadataDir = join(root, "metadata");
427
- const metaFiles = existsSync(metadataDir) ? readdirSync(metadataDir).filter((f) => f.endsWith(".json")).sort() : [];
480
+ const metaFiles = existsSync(metadataDir)
481
+ ? readdirSync(metadataDir)
482
+ .filter((f) => f.endsWith(".json"))
483
+ .sort()
484
+ : [];
428
485
  for (const f of metaFiles) {
429
486
  const raw = readFileSync(join(metadataDir, f), "utf8");
430
487
  const md = JSON.parse(raw);
431
488
  validateMetadata(md);
432
- if (raw.trim() !== canonicalJson(md)) throw new Error(`metadata/${f} is not in canonical form (re-run the metadata generator).`);
489
+ if (raw.trim() !== canonicalJson(md))
490
+ throw new Error(`metadata/${f} is not in canonical form (re-run the metadata generator).`);
433
491
  }
434
492
  let checked = 0;
435
493
  for (const item of manifest) {
@@ -437,7 +495,8 @@ function validateProject(root) {
437
495
  const md = buildChip0007Metadata({ name: item.name, attributes: item.attributes });
438
496
  if (media.metadata_hash != null) {
439
497
  const matches = metaFiles.some(
440
- (f) => metadataHashHex(JSON.parse(readFileSync(join(metadataDir, f), "utf8"))) === media.metadata_hash,
498
+ (f) =>
499
+ metadataHashHex(JSON.parse(readFileSync(join(metadataDir, f), "utf8"))) === media.metadata_hash,
441
500
  );
442
501
  if (!matches && metadataHashHex(md) !== media.metadata_hash) {
443
502
  throw new Error(`item "${item.name}": metadata_hash does not match any metadata/*.json`);
@@ -453,7 +512,9 @@ function validateProject(root) {
453
512
  if (licFile && media.license_hash != null) {
454
513
  const licPath = join(root, licFile);
455
514
  if (!existsSync(licPath)) throw new Error(`item "${item.name}": license not found at ${licFile}`);
456
- validateMetadata(md, { license: { license_hash: media.license_hash, license_bytes: readFileSync(licPath) } });
515
+ validateMetadata(md, {
516
+ license: { license_hash: media.license_hash, license_bytes: readFileSync(licPath) },
517
+ });
457
518
  }
458
519
  checked++;
459
520
  }
@@ -471,18 +532,34 @@ function flagValue(args, name) {
471
532
  return undefined;
472
533
  }
473
534
 
535
+ /** True if `name` appears in the argv slice as a flag (with or without a value). */
536
+ function flagPresent(args, name) {
537
+ return args.some((a) => a === name || a.startsWith(`${name}=`));
538
+ }
539
+
474
540
  function main() {
475
541
  const cmd = process.argv[2];
476
542
  const args = process.argv.slice(3);
477
543
  try {
478
544
  if (cmd === "metadata") {
479
545
  const storeId = flagValue(args, "--store-id");
546
+ if (flagPresent(args, "--store-id") && !(storeId && storeId.trim())) {
547
+ console.error(
548
+ "Error: --store-id was given but empty. Omit it for the pre-publish placeholder pass, " +
549
+ "or pass the real store id from `digstore deploy` (e.g. --store-id <id>).",
550
+ );
551
+ process.exit(2);
552
+ }
480
553
  const r = generateMetadata(ROOT, storeId ? { storeId } : {});
481
554
  console.log(`Generated ${r.count} CHIP-0007 metadata file(s) → metadata/ and the items.json manifest.`);
482
555
  if (storeId) {
483
- console.log(`Baked store id ${storeId} into every URI. Next: \`npm run validate\`, then \`digstore collection mint --collection collection.json --manifest items.json\`.`);
556
+ console.log(
557
+ `Baked store id ${storeId} into every URI. Next: \`npm run validate\`, then \`digstore collection mint --collection collection.json --manifest items.json\`.`,
558
+ );
484
559
  } else {
485
- console.log("Used the placeholder store id. After `digstore deploy` gives you the real store id, re-run `npm run generate:metadata -- --store-id <id>` to bake it in — otherwise the minted NFT keeps the placeholder. Then `npm run validate` and `digstore collection mint`.");
560
+ console.log(
561
+ "Used the placeholder store id. After `digstore deploy` gives you the real store id, re-run `npm run generate:metadata -- --store-id <id>` to bake it in — otherwise the minted NFT keeps the placeholder. Then `npm run validate` and `digstore collection mint`.",
562
+ );
486
563
  }
487
564
  } else if (cmd === "license") {
488
565
  const r = generateLicense(ROOT);
@@ -490,11 +567,17 @@ function main() {
490
567
  console.log("Re-run `npm run generate:metadata` to wire the license URI + hash into items.json.");
491
568
  } else if (cmd === "validate") {
492
569
  const r = validateProject(ROOT);
493
- console.log(`OK — ${r.checked} item(s) valid: CHIP-0007 schema + data/metadata/license hashes agree with the real bytes.`);
570
+ console.log(
571
+ `OK — ${r.checked} item(s) valid: CHIP-0007 schema + data/metadata/license hashes agree with the real bytes.`,
572
+ );
494
573
  } else {
495
574
  console.error("Usage: node scripts/dig-nft.mjs <metadata|license|validate> [--store-id <id>]");
496
- console.error(" metadata generate CHIP-0007 metadata/*.json + items.json from images/ + collection.json");
497
- console.error(" --store-id <id> bake the REAL published store id into URIs (after `digstore deploy`)");
575
+ console.error(
576
+ " metadata generate CHIP-0007 metadata/*.json + items.json from images/ + collection.json",
577
+ );
578
+ console.error(
579
+ " --store-id <id> bake the REAL published store id into URIs (after `digstore deploy`)",
580
+ );
498
581
  console.error(" license write the license chosen in collection.json into licenses/");
499
582
  console.error(" validate re-verify schema + URI/hash agreement before minting");
500
583
  process.exit(2);
@@ -6,10 +6,10 @@
6
6
  --error: #dc2626;
7
7
 
8
8
  /* DIG brand accent — violet -> magenta (SYSTEM.md visual theme; matches dig.net/hub/status). */
9
- --dig-violet: #5800D6;
10
- --dig-magenta: #FF00DE;
9
+ --dig-violet: #5800d6;
10
+ --dig-magenta: #ff00de;
11
11
  --accent: var(--dig-violet);
12
- --grad-brand: linear-gradient(115deg, #5800D6 0%, #FF00DE 100%);
12
+ --grad-brand: linear-gradient(115deg, #5800d6 0%, #ff00de 100%);
13
13
 
14
14
  /* DIG brand fonts — named first, system stack as the offline/no-fetch fallback so a published
15
15
  capsule never depends on an external font host (keeps the collection page self-contained). */
@@ -17,9 +17,15 @@
17
17
  --dig-mono: "Space Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
18
18
  }
19
19
  @media (prefers-color-scheme: dark) {
20
- :root { --fg: #e2e8f0; --muted: #94a3b8; --bg: #0b1120; }
20
+ :root {
21
+ --fg: #e2e8f0;
22
+ --muted: #94a3b8;
23
+ --bg: #0b1120;
24
+ }
25
+ }
26
+ * {
27
+ box-sizing: border-box;
21
28
  }
22
- * { box-sizing: border-box; }
23
29
  body {
24
30
  margin: 0;
25
31
  min-height: 100vh;
@@ -27,8 +33,17 @@ body {
27
33
  color: var(--fg);
28
34
  background: var(--bg);
29
35
  }
30
- .banner { width: 100%; max-height: 320px; object-fit: cover; display: block; }
31
- .wrap { max-width: 64rem; margin: 0 auto; padding: 2rem; }
36
+ .banner {
37
+ width: 100%;
38
+ max-height: 320px;
39
+ object-fit: cover;
40
+ display: block;
41
+ }
42
+ .wrap {
43
+ max-width: 64rem;
44
+ margin: 0 auto;
45
+ padding: 2rem;
46
+ }
32
47
  h1 {
33
48
  font-size: 2.25rem;
34
49
  margin: 0 0 0.5rem;
@@ -38,8 +53,12 @@ h1 {
38
53
  background-clip: text;
39
54
  color: transparent;
40
55
  }
41
- .muted { color: var(--muted); }
42
- .error { color: var(--error); }
56
+ .muted {
57
+ color: var(--muted);
58
+ }
59
+ .error {
60
+ color: var(--error);
61
+ }
43
62
  button {
44
63
  font: inherit;
45
64
  padding: 0.6rem 1.1rem;
@@ -49,11 +68,31 @@ button {
49
68
  color: white;
50
69
  cursor: pointer;
51
70
  }
52
- .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; margin-top: 1.5rem; }
53
- .item { border-radius: 0.75rem; overflow: hidden; background: rgba(127, 127, 127, 0.12); }
54
- .item img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
55
- .item .label { padding: 0.5rem 0.75rem; font-weight: 600; }
56
- code { font-family: var(--dig-mono); word-break: break-all; }
71
+ .grid {
72
+ display: grid;
73
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
74
+ gap: 1rem;
75
+ margin-top: 1.5rem;
76
+ }
77
+ .item {
78
+ border-radius: 0.75rem;
79
+ overflow: hidden;
80
+ background: rgba(127, 127, 127, 0.12);
81
+ }
82
+ .item img {
83
+ width: 100%;
84
+ aspect-ratio: 1;
85
+ object-fit: cover;
86
+ display: block;
87
+ }
88
+ .item .label {
89
+ padding: 0.5rem 0.75rem;
90
+ font-weight: 600;
91
+ }
92
+ code {
93
+ font-family: var(--dig-mono);
94
+ word-break: break-all;
95
+ }
57
96
  pre {
58
97
  font-family: var(--dig-mono);
59
98
  margin-top: 1.5rem;
@@ -44,13 +44,13 @@ export default function App() {
44
44
  <main className="wrap">
45
45
  <h1>__DISPLAY_NAME__</h1>
46
46
  <p>
47
- An NFT drop on the <strong>DIG Network</strong> — mint NFTs whose media can live in a DIG
48
- capsule, truly permanent on Chia.
47
+ An NFT drop on the <strong>DIG Network</strong> — mint NFTs whose media can live in a DIG capsule,
48
+ truly permanent on Chia.
49
49
  </p>
50
50
  <p className="muted">
51
- Building and previewing this mint page are <strong>free</strong> — no mint, no chain, no spend
52
- when you scaffold or run it. Publishing the page is one <code>digstore deploy</code> ($DIG).
53
- Minting the NFTs is a separate, wallet-signed on-chain action a visitor triggers.
51
+ Building and previewing this mint page are <strong>free</strong> — no mint, no chain, no spend when
52
+ you scaffold or run it. Publishing the page is one <code>digstore deploy</code> ($DIG). Minting the
53
+ NFTs is a separate, wallet-signed on-chain action a visitor triggers.
54
54
  </p>
55
55
 
56
56
  {address ? (
@@ -6,10 +6,10 @@
6
6
  --error: #dc2626;
7
7
 
8
8
  /* DIG brand accent — violet -> magenta (SYSTEM.md visual theme; matches dig.net/hub/status). */
9
- --dig-violet: #5800D6;
10
- --dig-magenta: #FF00DE;
9
+ --dig-violet: #5800d6;
10
+ --dig-magenta: #ff00de;
11
11
  --accent: var(--dig-violet);
12
- --grad-brand: linear-gradient(115deg, #5800D6 0%, #FF00DE 100%);
12
+ --grad-brand: linear-gradient(115deg, #5800d6 0%, #ff00de 100%);
13
13
 
14
14
  /* DIG brand fonts — named first, system stack as the offline/no-fetch fallback so a scaffolded
15
15
  capsule never depends on an external font host (keeps the site self-contained + offline-safe). */
@@ -17,9 +17,15 @@
17
17
  --dig-mono: "Space Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
18
18
  }
19
19
  @media (prefers-color-scheme: dark) {
20
- :root { --fg: #e2e8f0; --muted: #94a3b8; --bg: #0b1120; }
20
+ :root {
21
+ --fg: #e2e8f0;
22
+ --muted: #94a3b8;
23
+ --bg: #0b1120;
24
+ }
25
+ }
26
+ * {
27
+ box-sizing: border-box;
21
28
  }
22
- * { box-sizing: border-box; }
23
29
  body {
24
30
  margin: 0;
25
31
  min-height: 100vh;
@@ -29,7 +35,10 @@ body {
29
35
  color: var(--fg);
30
36
  background: var(--bg);
31
37
  }
32
- .wrap { max-width: 42rem; padding: 2rem; }
38
+ .wrap {
39
+ max-width: 42rem;
40
+ padding: 2rem;
41
+ }
33
42
  h1 {
34
43
  font-size: 2.25rem;
35
44
  margin: 0 0 0.5rem;
@@ -39,8 +48,12 @@ h1 {
39
48
  background-clip: text;
40
49
  color: transparent;
41
50
  }
42
- .muted { color: var(--muted); }
43
- .error { color: var(--error); }
51
+ .muted {
52
+ color: var(--muted);
53
+ }
54
+ .error {
55
+ color: var(--error);
56
+ }
44
57
  button {
45
58
  font: inherit;
46
59
  padding: 0.6rem 1.1rem;
@@ -58,8 +71,16 @@ button {
58
71
  display: grid;
59
72
  gap: 0.5rem;
60
73
  }
61
- .row { display: flex; gap: 0.75rem; align-items: baseline; flex-wrap: wrap; }
62
- code { font-family: var(--dig-mono); word-break: break-all; }
74
+ .row {
75
+ display: flex;
76
+ gap: 0.75rem;
77
+ align-items: baseline;
78
+ flex-wrap: wrap;
79
+ }
80
+ code {
81
+ font-family: var(--dig-mono);
82
+ word-break: break-all;
83
+ }
63
84
  pre {
64
85
  font-family: var(--dig-mono);
65
86
  margin-top: 1.5rem;
@@ -9,10 +9,10 @@
9
9
  <body>
10
10
  <main>
11
11
  <h1>__DISPLAY_NAME__</h1>
12
- <p>A static site on the <strong>DIG Network</strong> — no host can read it, change it, or take it down.</p>
13
- <p class="muted">
14
- Build and preview for <strong>free</strong>. You only spend $DIG when you publish.
12
+ <p>
13
+ A static site on the <strong>DIG Network</strong> — no host can read it, change it, or take it down.
15
14
  </p>
15
+ <p class="muted">Build and preview for <strong>free</strong>. You only spend $DIG when you publish.</p>
16
16
  <pre><code>digstore dev # preview, free
17
17
  digstore deploy # publish a capsule ($DIG)</code></pre>
18
18
  </main>