diff --git a/.gitignore b/.gitignore index 075c239..fffb21f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,11 @@ out/ .test-fixtures/ *.log *.png +# Canonical brand icons are generated from @mind-studio/ui/brand (npm run gen:icons) +# and committed so the favicon/PWA/apple-touch assets ship with the app. +!src/app/apple-icon.png +!public/icon-192.png +!public/icon-512.png .env .env*.local .DS_Store diff --git a/package-lock.json b/package-lock.json index 5b802aa..834d3b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@inrupt/solid-client-authn-browser": "^4.0.0", "@inrupt/solid-client-authn-node": "^3.1.0", "@mind-studio/core": "^0.7.0", - "@mind-studio/ui": "^0.5.0", + "@mind-studio/ui": "^0.7.0", "lucide-react": "^0.460.0", "next": "16.2.6", "react": "19.2.4", @@ -27,6 +27,7 @@ "@types/react-dom": "^19", "husky": "^9.1.7", "lint-staged": "^17.0.7", + "sharp": "^0.34.5", "tailwindcss": "^4", "tsx": "^4.22.3", "typescript": "^5", @@ -379,6 +380,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -892,8 +894,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=18" } @@ -905,6 +907,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -927,6 +930,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -949,6 +953,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -965,6 +970,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -981,6 +987,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -997,6 +1004,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1013,6 +1021,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1029,6 +1038,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1045,6 +1055,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1061,6 +1072,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1077,6 +1089,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1093,6 +1106,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1109,6 +1123,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1131,6 +1146,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1153,6 +1169,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1175,6 +1192,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1197,6 +1215,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1219,6 +1238,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1241,6 +1261,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1263,6 +1284,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1285,6 +1307,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -1304,6 +1327,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1323,6 +1347,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1342,6 +1367,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -1514,9 +1540,9 @@ } }, "node_modules/@mind-studio/ui": { - "version": "0.5.0", - "resolved": "https://npm.pkg.github.com/download/@mind-studio/ui/0.5.0/3fc873067e04a306cb44015874f24eeaf683a6d9", - "integrity": "sha512-nJJN2RuP4KvFDyZYJSRnG68kHt6WXFyxyVYH8jCpK6l0Bj+T0+lj7lNuAUrW0bm2atE04NGkZjMAkmZoxjBxmQ==", + "version": "0.7.0", + "resolved": "https://npm.pkg.github.com/download/@mind-studio/ui/0.7.0/70071acee41c5d7bcc3bed636a6473e35dac9f6a", + "integrity": "sha512-PvkztbjfiFM7W8Zk0/rWYlMn26DiIakuhBQfxphi5les6J6eo9W7zZzLhbGpEp7ytB1+SaKev95ZFOxVhKx6cw==", "license": "MIT", "dependencies": { "@base-ui/react": "^1.5.0", @@ -6177,8 +6203,8 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "devOptional": true, "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -6190,9 +6216,9 @@ "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", diff --git a/package.json b/package.json index b196191..127b088 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev -p 3060", + "gen:icons": "node scripts/gen-icons.mjs", "build": "next build", "start": "next start -p 3060", "seed:demo": "tsx scripts/seed-demo.ts", @@ -20,7 +21,7 @@ "@inrupt/solid-client-authn-browser": "^4.0.0", "@inrupt/solid-client-authn-node": "^3.1.0", "@mind-studio/core": "^0.7.0", - "@mind-studio/ui": "^0.5.0", + "@mind-studio/ui": "^0.7.0", "lucide-react": "^0.460.0", "next": "16.2.6", "react": "19.2.4", @@ -35,6 +36,7 @@ "@types/react-dom": "^19", "husky": "^9.1.7", "lint-staged": "^17.0.7", + "sharp": "^0.34.5", "tailwindcss": "^4", "tsx": "^4.22.3", "typescript": "^5", diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000..6ef4ebe Binary files /dev/null and b/public/icon-192.png differ diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000..0b247e7 Binary files /dev/null and b/public/icon-512.png differ diff --git a/scripts/gen-icons.mjs b/scripts/gen-icons.mjs new file mode 100644 index 0000000..e9a5c78 --- /dev/null +++ b/scripts/gen-icons.mjs @@ -0,0 +1,79 @@ +/* + * Generates Drive's brand icons from the canonical Mind mark. + * + * The artwork is imported from `@mind-studio/ui/brand` — the design system's + * single source of truth — so these icons can never drift from the published + * logo. Drive shares the one Mind mark (icons are mark-only). Re-run after + * bumping the package: `npm run gen:icons`. + * + * Outputs: + * src/app/icon.svg favicon — gradient weave, transparent, square + * src/app/apple-icon.png 180×180 apple-touch — white mark on green, 22% radius + * public/icon-192.png 192×192 maskable — white mark on full-bleed green + * public/icon-512.png 512×512 maskable — white mark on full-bleed green + * + * The maskable icons bleed the green to the edges (the platform mask crops the + * corners) and keep the mark inside the ~80% safe zone. + */ + +import { writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { MIND_GREEN, MIND_MARK_SVG, monoMarkSvg } from "@mind-studio/ui/brand"; +import sharp from "sharp"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, ".."); +const out = (p) => resolve(root, p); + +// Brand green backplate (the solid surface behind the white mono mark). +const GREEN = MIND_GREEN.base; // #16B88A + +/* ---------------------------------------------------------------- favicon.svg */ +// Nest the full-colour mark inside a square canvas, centred with a little +// breathing room (meet → preserves the mark's aspect, no distortion). +const nested = MIND_MARK_SVG.replace( + ' +${nested} + +`; +writeFileSync(out("src/app/icon.svg"), faviconSvg, "utf8"); + +/* ------------------------------------------------------------------ raster ops */ +// Rasterise the monochrome mark to a transparent square of the given size. +async function rasterMark(px, color = "#ffffff") { + return sharp(Buffer.from(monoMarkSvg(color))) + .resize(px, px, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }) + .png() + .toBuffer(); +} + +// Maskable: white mark on a full-bleed green square; mark kept in the safe zone. +async function maskable(size, file) { + const mark = await rasterMark(Math.round(size * 0.66)); + await sharp({ create: { width: size, height: size, channels: 4, background: GREEN } }) + .composite([{ input: mark, gravity: "center" }]) + .png() + .toFile(out(file)); +} + +// Apple touch: white mark on a green backplate with a 22% corner radius. +async function appleIcon(size, file) { + const r = Math.round(size * 0.22); + const backplate = Buffer.from( + ``, + ); + const mark = await rasterMark(Math.round(size * 0.62)); + await sharp(backplate).composite([{ input: mark, gravity: "center" }]).png().toFile(out(file)); +} + +await Promise.all([ + maskable(192, "public/icon-192.png"), + maskable(512, "public/icon-512.png"), + appleIcon(180, "src/app/apple-icon.png"), +]); + +console.log("[gen:icons] wrote icon.svg, apple-icon.png, icon-192.png, icon-512.png"); diff --git a/src/app/apple-icon.png b/src/app/apple-icon.png new file mode 100644 index 0000000..76f3c17 Binary files /dev/null and b/src/app/apple-icon.png differ diff --git a/src/app/icon.svg b/src/app/icon.svg index 7e5988e..9ac8266 100644 --- a/src/app/icon.svg +++ b/src/app/icon.svg @@ -1,6 +1,66 @@ - - - M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b785948..3f4a901 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,4 @@ -import { Button, ThemeProvider, Toaster } from "@mind-studio/ui"; +import { Button, Logo, ThemeProvider, Toaster } from "@mind-studio/ui"; import { mind } from "@mind-studio/ui/themes"; import type { Metadata } from "next"; import { Fraunces, Hanken_Grotesk, JetBrains_Mono } from "next/font/google"; @@ -67,8 +67,8 @@ function Masthead() { return (
- - Mind Drive + + ● files in your pod @@ -92,7 +92,7 @@ function Colophon() { return (