@@ -84,29 +84,37 @@ jobs:
|
|||||||
ca-certificates
|
ca-certificates
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
timeout-minutes: 20
|
timeout-minutes: 15
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--dns-result-order=ipv4first'
|
NODE_OPTIONS: '--dns-result-order=ipv4first'
|
||||||
# Faster/safer git deps (lockfile must use https; also rewrite any ssh URLs)
|
|
||||||
GIT_TERMINAL_PROMPT: '0'
|
GIT_TERMINAL_PROMPT: '0'
|
||||||
# Electron postinstall (~100MB+) — fail fast rather than hang forever
|
|
||||||
ELECTRON_GET_USE_PROXY: '0'
|
ELECTRON_GET_USE_PROXY: '0'
|
||||||
npm_config_fetch_retries: '3'
|
# Never clone git deps over SSH (slow/hangs without keys)
|
||||||
npm_config_fetch_retry_mintimeout: '10000'
|
npm_config_fetch_retries: '2'
|
||||||
npm_config_fetch_retry_maxtimeout: '60000'
|
npm_config_fetch_retry_mintimeout: '5000'
|
||||||
npm_config_fetch_timeout: '120000'
|
npm_config_fetch_retry_maxtimeout: '30000'
|
||||||
npm_config_maxsockets: '20'
|
npm_config_fetch_timeout: '90000'
|
||||||
|
npm_config_maxsockets: '50'
|
||||||
npm_config_progress: 'false'
|
npm_config_progress: 'false'
|
||||||
npm_config_fund: 'false'
|
npm_config_fund: 'false'
|
||||||
npm_config_audit: 'false'
|
npm_config_audit: 'false'
|
||||||
|
npm_config_loglevel: 'error'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# CI runners often cannot use [email protected] SSH — force HTTPS for GitHub
|
# Hard fail if lockfile regressed to git+ssh (clones hang forever on CI)
|
||||||
|
if grep -E 'git\+ssh://|git@github\.com:' package-lock.json >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: package-lock.json contains git+ssh deps — use npm registry/overrides"
|
||||||
|
grep -nE 'git\+ssh://|git@github\.com:' package-lock.json | head -20
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# If any remaining git+https github deps appear, force HTTPS (not SSH)
|
||||||
git config --global url."https://github.com/".insteadOf "ssh://[email protected]/"
|
git config --global url."https://github.com/".insteadOf "ssh://[email protected]/"
|
||||||
git config --global url."https://github.com/".insteadOf "[email protected]:"
|
git config --global url."https://github.com/".insteadOf "[email protected]:"
|
||||||
# --prefer-offline without an Actions cache just wastes time checking emptiness
|
git config --global url."https://github.com/".insteadOf "git+ssh://[email protected]/"
|
||||||
# Electron + bare-runtime multi-arch prebuilds typically take several minutes.
|
# Timed install — electron + bare-runtime prebuilds are the bulk of the work
|
||||||
npm ci --no-audit --no-fund --no-progress --loglevel=warn
|
echo "npm ci starting $(date -u +%H:%M:%S)"
|
||||||
|
time npm ci --no-audit --no-fund --no-progress --loglevel=error
|
||||||
|
echo "npm ci finished $(date -u +%H:%M:%S)"
|
||||||
|
|
||||||
# Ad-hoc codesign for darwin .app bundles produced on Linux (fixes Gatekeeper "damaged")
|
# Ad-hoc codesign for darwin .app bundles produced on Linux (fixes Gatekeeper "damaged")
|
||||||
- name: Install rcodesign (macOS ad-hoc signing on Linux)
|
- name: Install rcodesign (macOS ad-hoc signing on Linux)
|
||||||
|
|||||||
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 516 B |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 177 KiB |
@@ -17,12 +17,22 @@ Canonical list: `scripts/hosts.cjs` → `ALL_64`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Branding
|
||||||
|
|
||||||
|
Source kit: `peardock-branding/` (logos, palette, app icons).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run brand:sync # → build/icon.{png,icns,ico} + ui/brand/*
|
||||||
|
```
|
||||||
|
|
||||||
|
CI / `forge` prePackage / bare-standalone all run this so binary icons and UI match the brand kit.
|
||||||
|
|
||||||
## Builders
|
## Builders
|
||||||
|
|
||||||
| Artifact | Tooling |
|
| Artifact | Tooling |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| **peardock-server** | `scripts/bare-standalone.cjs` — `bare-pack` with **bare-node-runtime** `imports` + stubs, then embed **bare-runtime** prebuild (`bare-build` platform hooks) |
|
| **peardock-server** | `scripts/bare-standalone.cjs` — `bare-pack` with **bare-node-runtime** `imports` + stubs, then embed **bare-runtime** prebuild (`bare-build` platform hooks) + brand icon |
|
||||||
| **peardock-client** | `esbuild` → `electron/app.bundle.cjs`, then `electron-forge package --platform … --arch …` |
|
| **peardock-client** | `brand:sync` → `esbuild` → `electron/app.bundle.cjs`, then `electron-forge package --platform … --arch …` + macOS codesign |
|
||||||
|
|
||||||
Server packing uses `package.json` `imports` so Node builtins resolve to `bare-*` under Bare. Optional natives (`ssh2`, `@grpc/*`) map to `build/stubs/*`.
|
Server packing uses `package.json` `imports` so Node builtins resolve to `bare-*` under Bare. Optional natives (`ssh2`, `@grpc/*`) map to `build/stubs/*`.
|
||||||
|
|
||||||
|
|||||||
@@ -207,12 +207,33 @@ async function startHolesailControl() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveAppIcon() {
|
||||||
|
const candidates = [
|
||||||
|
path.join(__dirname, '..', 'build', 'icon.png'),
|
||||||
|
path.join(__dirname, '..', 'ui', 'brand', 'mark.png'),
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'peardock-branding',
|
||||||
|
'app-icons',
|
||||||
|
'ios',
|
||||||
|
'peardock-512.png'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for (const p of candidates) {
|
||||||
|
if (fs.existsSync(p)) return p
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
|
const icon = resolveAppIcon()
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
width: pkg.pear?.gui?.width || 1280,
|
width: pkg.pear?.gui?.width || 1280,
|
||||||
height: pkg.pear?.gui?.height || 800,
|
height: pkg.pear?.gui?.height || 800,
|
||||||
backgroundColor: pkg.pear?.gui?.backgroundColor || '#0a0c10',
|
backgroundColor: pkg.pear?.gui?.backgroundColor || '#0F172A',
|
||||||
title: appName,
|
title: appName === 'peardock' ? 'PearDock' : appName,
|
||||||
|
...(icon ? { icon } : {}),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: buildPreloadPath(),
|
preload: buildPreloadPath(),
|
||||||
// Full peardock UI imports HyperDHT etc. as ESM node packages — same as pear UI.
|
// Full peardock UI imports HyperDHT etc. as ESM node packages — same as pear UI.
|
||||||
@@ -264,6 +285,25 @@ app.whenReady().then(async () => {
|
|||||||
// App root: project root in dev; Electron app path (app.asar or app/) when packaged
|
// App root: project root in dev; Electron app path (app.asar or app/) when packaged
|
||||||
const appRoot = app.isPackaged ? app.getAppPath() : path.resolve(__dirname, '..')
|
const appRoot = app.isPackaged ? app.getAppPath() : path.resolve(__dirname, '..')
|
||||||
|
|
||||||
|
// Brand: dock / about panel (macOS)
|
||||||
|
try {
|
||||||
|
const icon = resolveAppIcon()
|
||||||
|
if (process.platform === 'darwin' && app.dock && icon) {
|
||||||
|
const { nativeImage } = require('electron')
|
||||||
|
app.dock.setIcon(nativeImage.createFromPath(icon))
|
||||||
|
}
|
||||||
|
if (process.platform === 'darwin' && app.setAboutPanelParameters) {
|
||||||
|
app.setAboutPanelParameters({
|
||||||
|
applicationName: 'PearDock',
|
||||||
|
applicationVersion: pkg.version,
|
||||||
|
copyright: 'PearDock — P2P Docker',
|
||||||
|
...(icon ? { iconPath: icon } : {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[peardock] brand dock/about skipped:', err.message || err)
|
||||||
|
}
|
||||||
|
|
||||||
await startStaticServer(appRoot)
|
await startStaticServer(appRoot)
|
||||||
await startHolesailControl()
|
await startHolesailControl()
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ const path = require('path')
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const pkg = require('./package.json')
|
const pkg = require('./package.json')
|
||||||
const appName = pkg.productName || pkg.name || 'peardock'
|
// Stable on-disk names (out/peardock-darwin-arm64/…) for CI; UI title uses productName
|
||||||
|
const appName = pkg.name || 'peardock'
|
||||||
|
const displayName = pkg.productName || 'PearDock'
|
||||||
|
|
||||||
/** Path prefixes (packager paths start with /) to exclude from the app bundle */
|
/** Path prefixes (packager paths start with /) to exclude from the app bundle */
|
||||||
const IGNORE_PREFIXES = [
|
const IGNORE_PREFIXES = [
|
||||||
@@ -29,6 +31,8 @@ const IGNORE_PREFIXES = [
|
|||||||
'/scripts',
|
'/scripts',
|
||||||
'/bin',
|
'/bin',
|
||||||
'/workers',
|
'/workers',
|
||||||
|
// Source branding kit is large; runtime uses build/ + ui/brand/ after brand:sync
|
||||||
|
'/peardock-branding',
|
||||||
'/build/stubs',
|
'/build/stubs',
|
||||||
'/build/shims',
|
'/build/shims',
|
||||||
// Packaging / Bare server toolchain (not needed at Electron runtime)
|
// Packaging / Bare server toolchain (not needed at Electron runtime)
|
||||||
@@ -114,9 +118,16 @@ module.exports = {
|
|||||||
name: appName,
|
name: appName,
|
||||||
executableName: 'peardock-client',
|
executableName: 'peardock-client',
|
||||||
appBundleId: 'com.peardock.app',
|
appBundleId: 'com.peardock.app',
|
||||||
|
appCopyright: `${displayName} — P2P Docker`,
|
||||||
|
appCategoryType: 'public.app-category.developer-tools',
|
||||||
|
// Prefer generated pack icons from peardock-branding (npm run brand:sync)
|
||||||
icon: fs.existsSync(path.join(__dirname, 'build', 'icon.png'))
|
icon: fs.existsSync(path.join(__dirname, 'build', 'icon.png'))
|
||||||
? path.join(__dirname, 'build', 'icon')
|
? path.join(__dirname, 'build', 'icon')
|
||||||
: undefined,
|
: fs.existsSync(
|
||||||
|
path.join(__dirname, 'peardock-branding', 'app-icons', 'ios', 'peardock-1024.png')
|
||||||
|
)
|
||||||
|
? path.join(__dirname, 'peardock-branding', 'app-icons', 'ios', 'peardock-1024')
|
||||||
|
: undefined,
|
||||||
// CJS require() works inside asar (Electron patches it). Unpack natives only.
|
// CJS require() works inside asar (Electron patches it). Unpack natives only.
|
||||||
// asar:false made "Finalizing package" copy the entire tree as loose files → CI stalls.
|
// asar:false made "Finalizing package" copy the entire tree as loose files → CI stalls.
|
||||||
asar: {
|
asar: {
|
||||||
@@ -161,6 +172,12 @@ module.exports = {
|
|||||||
|
|
||||||
hooks: {
|
hooks: {
|
||||||
prePackage: async () => {
|
prePackage: async () => {
|
||||||
|
// Brand icons/assets then GUI bundle (order matters for packager icon path)
|
||||||
|
require('child_process').execFileSync(
|
||||||
|
process.execPath,
|
||||||
|
[path.join(__dirname, 'scripts', 'sync-branding.cjs')],
|
||||||
|
{ stdio: 'inherit', cwd: __dirname }
|
||||||
|
)
|
||||||
require('child_process').execFileSync(
|
require('child_process').execFileSync(
|
||||||
process.execPath,
|
process.execPath,
|
||||||
[path.join(__dirname, 'scripts', 'build-client-bundle.cjs')],
|
[path.join(__dirname, 'scripts', 'build-client-bundle.cjs')],
|
||||||
|
|||||||
@@ -4,7 +4,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>peardock</title>
|
<title>PearDock</title>
|
||||||
|
<meta name="description" content="PearDock — P2P Docker management over HyperDHT">
|
||||||
|
<meta name="theme-color" content="#0F172A">
|
||||||
|
<link rel="icon" href="ui/brand/favicon/favicon.ico" sizes="any">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="ui/brand/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="ui/brand/favicon/favicon-16x16.png">
|
||||||
|
<link rel="apple-touch-icon" href="ui/brand/favicon/apple-touch-icon.png">
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
@@ -13,6 +19,7 @@
|
|||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="ui/brand/brand.css">
|
||||||
<link rel="stylesheet" href="ui/styles.css">
|
<link rel="stylesheet" href="ui/styles.css">
|
||||||
<link rel="stylesheet" href="ui/modern.css">
|
<link rel="stylesheet" href="ui/modern.css">
|
||||||
<link rel="stylesheet" href="ui/ops.css">
|
<link rel="stylesheet" href="ui/ops.css">
|
||||||
@@ -30,8 +37,10 @@
|
|||||||
<div class="titlebar-left">
|
<div class="titlebar-left">
|
||||||
<pear-ctrl></pear-ctrl>
|
<pear-ctrl></pear-ctrl>
|
||||||
<div class="app-brand">
|
<div class="app-brand">
|
||||||
<div class="app-brand-mark">🍐</div>
|
<div class="app-brand-mark" aria-hidden="true">
|
||||||
<div class="app-brand-text">peardock <span>P2P Docker</span></div>
|
<img src="ui/brand/mark.png" alt="" width="22" height="22" draggable="false">
|
||||||
|
</div>
|
||||||
|
<div class="app-brand-text">PearDock <span>P2P Docker</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-right">
|
<div class="titlebar-right">
|
||||||
|
|||||||
@@ -462,9 +462,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/node-gyp": {
|
"node_modules/@electron/node-gyp": {
|
||||||
"version": "10.2.0-electron.1",
|
"version": "10.2.0-electron.2",
|
||||||
"resolved": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
|
"resolved": "https://registry.npmjs.org/@electron/node-gyp/-/node-gyp-10.2.0-electron.2.tgz",
|
||||||
"integrity": "sha512-lBSgDMQqt7QWMuIjS8zNAq5FI5o5RVBAcJUGWGI6GgoQITJt3msAkUrHp8YHj3RTVE+h70ndqMGqURjp3IfRyQ==",
|
"integrity": "sha512-OhO6fwqpetMO1vWI3+J8mb3a4s4A405tgKoUCJsgd4nyQDdFh0VvZm+gj/Cc70iRLQoIYUfSaAgYSVwmLsQHig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -644,7 +644,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/node-gyp": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
|
"@electron/node-gyp": "10.2.0-electron.2",
|
||||||
"@malept/cross-spawn-promise": "^2.0.0",
|
"@malept/cross-spawn-promise": "^2.0.0",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"productName": "peardock",
|
"productName": "PearDock",
|
||||||
"imports": {
|
"imports": {
|
||||||
"assert": {
|
"assert": {
|
||||||
"bare": "bare-assert",
|
"bare": "bare-assert",
|
||||||
@@ -490,10 +490,10 @@
|
|||||||
},
|
},
|
||||||
"pear": {
|
"pear": {
|
||||||
"pre": "pear-electron/pre",
|
"pre": "pear-electron/pre",
|
||||||
"name": "peardock",
|
"name": "PearDock",
|
||||||
"gui": {
|
"gui": {
|
||||||
"main": "index.html",
|
"main": "index.html",
|
||||||
"backgroundColor": "#0a0c10",
|
"backgroundColor": "#0F172A",
|
||||||
"height": 800,
|
"height": 800,
|
||||||
"width": 1280
|
"width": 1280
|
||||||
},
|
},
|
||||||
@@ -520,8 +520,9 @@
|
|||||||
"soak:24h": "SOAK_DURATION_MS=86400000 node scripts/soak.js",
|
"soak:24h": "SOAK_DURATION_MS=86400000 node scripts/soak.js",
|
||||||
"release:checksums": "bash scripts/release-checksums.sh",
|
"release:checksums": "bash scripts/release-checksums.sh",
|
||||||
"sbom:notes": "node -e \"console.log('See docs/SBOM.md')\"",
|
"sbom:notes": "node -e \"console.log('See docs/SBOM.md')\"",
|
||||||
|
"brand:sync": "node scripts/sync-branding.cjs",
|
||||||
"build:client-bundle": "node scripts/build-client-bundle.cjs",
|
"build:client-bundle": "node scripts/build-client-bundle.cjs",
|
||||||
"start:client": "npm run build:client-bundle && electron electron/main.cjs",
|
"start:client": "npm run brand:sync && npm run build:client-bundle && electron electron/main.cjs",
|
||||||
"package:client": "electron-forge package",
|
"package:client": "electron-forge package",
|
||||||
"make": "node scripts/make.cjs both",
|
"make": "node scripts/make.cjs both",
|
||||||
"make:server": "node scripts/make.cjs server",
|
"make:server": "node scripts/make.cjs server",
|
||||||
@@ -615,5 +616,8 @@
|
|||||||
},
|
},
|
||||||
"upgrade": "pear://u8z9cp4jp33zf14z5krkt83agc8n566nyxtf5rprkr5q7mq7mkeo",
|
"upgrade": "pear://u8z9cp4jp33zf14z5krkt83agc8n566nyxtf5rprkr5q7mq7mkeo",
|
||||||
"upgradeServer": "pear://wx6ki5nwxf6zq8aeqti639fr1isu5ppq1hs51hhpgkfhzp44ncuy",
|
"upgradeServer": "pear://wx6ki5nwxf6zq8aeqti639fr1isu5ppq1hs51hhpgkfhzp44ncuy",
|
||||||
"upgradeClient": "pear://cw9t45wouuzzu6bai5tookzzowo4iy84czua3n3ek9n4jtmhehiy"
|
"upgradeClient": "pear://cw9t45wouuzzu6bai5tookzzowo4iy84czua3n3ek9n4jtmhehiy",
|
||||||
|
"overrides": {
|
||||||
|
"@electron/node-gyp": "10.2.0-electron.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# PearDock Branding Package
|
||||||
|
|
||||||
|
**Complete professional branding kit for PearDock**
|
||||||
|
P2P Container Runtime • Powered by Pear
|
||||||
|
https://git.ssh.surf/snxraven/peardock
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 What's Included
|
||||||
|
|
||||||
|
### 1. Brand Guidelines (PDF)
|
||||||
|
- `guidelines/PearDock-Brand-Guidelines.pdf`
|
||||||
|
Full 7-page professional guidelines covering brand story, logo system, color palette, typography, app icons, do's & don'ts, and usage rules.
|
||||||
|
|
||||||
|
### 2. Logos (`logos/`)
|
||||||
|
| Folder | Contents |
|
||||||
|
|--------|----------|
|
||||||
|
| **primary/** | Transparent master + white / light / dark / soft-blue backgrounds + high-res 512 / 1024 / 2048 px |
|
||||||
|
| **icon-only/** | Tight-cropped icon perfect for app icons and avatars |
|
||||||
|
| **horizontal/** | Logo + "PearDock" wordmark side-by-side (headers, nav) |
|
||||||
|
| **wordmark/** | Stacked logo + text (splash screens, about pages) |
|
||||||
|
| **monochrome/** | Pure black & pure white versions |
|
||||||
|
| **social/** | OG image, GitHub social preview, Instagram square |
|
||||||
|
|
||||||
|
### 3. App Icons (`app-icons/`)
|
||||||
|
- **iOS**: 16 → 1024 px (regular + rounded)
|
||||||
|
- **Android**: 48 → 512 px
|
||||||
|
- **Favicon package**: 16, 32, 48, apple-touch-icon (180), android-chrome-192/512, **favicon.ico**
|
||||||
|
|
||||||
|
### 4. Colors (`colors/`)
|
||||||
|
- Visual palette swatches (`peardock-color-palette.png`)
|
||||||
|
- `palette.md` with Hex / RGB / usage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Official Color Palette
|
||||||
|
|
||||||
|
| Name | Hex | Usage |
|
||||||
|
|-------------------|-----------|--------------------------------|
|
||||||
|
| Primary Blue | `#2563EB` | Whale, primary brand, CTAs |
|
||||||
|
| Pear Green | `#84CC16` | Pear body, success, accents |
|
||||||
|
| Molecule Cyan | `#06B6D4` | Network / docking elements |
|
||||||
|
| Deep Navy | `#0F172A` | Text, dark backgrounds |
|
||||||
|
| Stem Brown | `#B45309` | Organic detail only |
|
||||||
|
| Soft Sky | `#E0F2FE` | Light backgrounds |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 App integration (peardock repo)
|
||||||
|
|
||||||
|
From the peardock project root, sync this kit into packaging + UI paths:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run brand:sync
|
||||||
|
# → build/icon.png, build/icon.icns, build/icon.ico
|
||||||
|
# → ui/brand/* (titlebar mark, logos, favicons, brand.css)
|
||||||
|
```
|
||||||
|
|
||||||
|
That runs automatically before Electron package (`forge` prePackage) and rolling release CI.
|
||||||
|
|
||||||
|
## 🚀 Quick Start Recommendations
|
||||||
|
|
||||||
|
**Website / Docs header**
|
||||||
|
→ `logos/horizontal/peardock-horizontal-transparent.png`
|
||||||
|
|
||||||
|
**App icon / PWA / mobile**
|
||||||
|
→ `app-icons/ios/peardock-1024.png` or the rounded variants
|
||||||
|
|
||||||
|
**Favicon**
|
||||||
|
→ Drop the entire `app-icons/favicon/` folder into your site root
|
||||||
|
(also synced to `ui/brand/favicon/` for the desktop shell)
|
||||||
|
|
||||||
|
**Dark mode**
|
||||||
|
→ Use `logos/monochrome/peardock-logo-white.png` or the dark-bg versions
|
||||||
|
|
||||||
|
**GitHub / Social**
|
||||||
|
→ `logos/social/github-social-preview.png` and `og-image-peardock.png`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 Clear Space & Rules
|
||||||
|
|
||||||
|
- Always keep ≥ 20% of logo height as clear space on all sides
|
||||||
|
- Minimum full-logo height: **32 px**
|
||||||
|
- Never recolor the pear or whale arbitrarily
|
||||||
|
- Never remove the keyhole or molecule
|
||||||
|
- Prefer the transparent masters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 File Tree
|
||||||
|
|
||||||
|
```
|
||||||
|
peardock-branding/
|
||||||
|
├── README.md ← you are here
|
||||||
|
├── guidelines/
|
||||||
|
│ └── PearDock-Brand-Guidelines.pdf
|
||||||
|
├── logos/
|
||||||
|
│ ├── primary/
|
||||||
|
│ ├── icon-only/
|
||||||
|
│ ├── horizontal/
|
||||||
|
│ ├── wordmark/
|
||||||
|
│ ├── monochrome/
|
||||||
|
│ └── social/
|
||||||
|
├── app-icons/
|
||||||
|
│ ├── ios/
|
||||||
|
│ ├── android/
|
||||||
|
│ └── favicon/
|
||||||
|
├── colors/
|
||||||
|
│ ├── peardock-color-palette.png
|
||||||
|
│ └── palette.md
|
||||||
|
└── exports/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Generated for PearDock • July 2026**
|
||||||
|
Built with ❤️ for the Pear ecosystem
|
||||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 672 B |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 672 B |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
# PearDock Color Palette
|
||||||
|
|
||||||
|
| Name | Hex | RGB | Usage |
|
||||||
|
|------|-----|-----|-------|
|
||||||
|
| Primary Blue | `#2563EB` | 37,99,235 | Whale body, primary brand |
|
||||||
|
| Pear Green | `#84CC16` | 132,204,22 | Pear, accent, success |
|
||||||
|
| Molecule Cyan | `#06B6D4` | 6,182,212 | Network / docking elements |
|
||||||
|
| Deep Navy | `#0F172A` | 15,23,42 | Text, dark backgrounds |
|
||||||
|
| Stem Brown | `#B45309` | 180,83,9 | Organic detail |
|
||||||
|
| Soft Sky | `#E0F2FE` | 224,242,254 | Light backgrounds |
|
||||||
|
| White | `#FFFFFF` | 255,255,255 | Clean backgrounds |
|
||||||
|
| Slate | `#334155` | 51,65,85 | Secondary text |
|
||||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1 @@
|
|||||||
|
Full vector SVG of the complex logo is best created in Figma/Illustrator from the master PNG. This package provides production-ready high-res PNGs.
|
||||||
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 656 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 632 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 200 KiB |
@@ -144,6 +144,24 @@ async function buildOne(host, outRoot) {
|
|||||||
entry = entry.unmount(baseURL)
|
entry = entry.unmount(baseURL)
|
||||||
entry.id = id(entry).toString('hex')
|
entry.id = id(entry).toString('hex')
|
||||||
|
|
||||||
|
// Brand icons for standalone binary (mac .icns / win .ico / png)
|
||||||
|
try {
|
||||||
|
require('child_process').execFileSync(
|
||||||
|
process.execPath,
|
||||||
|
[path.join(root, 'scripts', 'sync-branding.cjs')],
|
||||||
|
{ stdio: 'inherit', cwd: root }
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[bare-standalone] brand:sync skipped:', err.message || err)
|
||||||
|
}
|
||||||
|
const iconPng = path.join(root, 'build', 'icon.png')
|
||||||
|
const iconIco = path.join(root, 'build', 'icon.ico')
|
||||||
|
const iconIcns = path.join(root, 'build', 'icon.icns')
|
||||||
|
let icon
|
||||||
|
if (host.startsWith('win32') && fs.existsSync(iconIco)) icon = iconIco
|
||||||
|
else if (host.startsWith('darwin') && fs.existsSync(iconIcns)) icon = iconIcns
|
||||||
|
else if (fs.existsSync(iconPng)) icon = iconPng
|
||||||
|
|
||||||
const platform = platformForHost(host)
|
const platform = platformForHost(host)
|
||||||
const opts = {
|
const opts = {
|
||||||
name,
|
name,
|
||||||
@@ -156,6 +174,7 @@ async function buildOne(host, outRoot) {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
package: false,
|
package: false,
|
||||||
base: root,
|
base: root,
|
||||||
|
...(icon ? { icon } : {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[bare-standalone] embedding bare-runtime for ${host}…`)
|
console.log(`[bare-standalone] embedding bare-runtime for ${host}…`)
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ export npm_config_build_from_source="${npm_config_build_from_source:-false}"
|
|||||||
# Skip @electron/rebuild during forge package (prebuilds only) — avoids multi-minute "Finalizing"
|
# Skip @electron/rebuild during forge package (prebuilds only) — avoids multi-minute "Finalizing"
|
||||||
export PEARDOCK_SKIP_REBUILD="${PEARDOCK_SKIP_REBUILD:-1}"
|
export PEARDOCK_SKIP_REBUILD="${PEARDOCK_SKIP_REBUILD:-1}"
|
||||||
|
|
||||||
|
# Brand kit → build/icon.{png,icns,ico} + ui/brand/* (Electron + bare-build icons)
|
||||||
|
log "syncing peardock-branding"
|
||||||
|
node scripts/sync-branding.cjs
|
||||||
|
|
||||||
# --- server: all hosts (bare cross-compile) ---
|
# --- server: all hosts (bare cross-compile) ---
|
||||||
log "building server binaries for: $PEARDOCK_SERVER_HOSTS"
|
log "building server binaries for: $PEARDOCK_SERVER_HOSTS"
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ function makeServer(hosts = SERVER_HOSTS) {
|
|||||||
|
|
||||||
function makeClient(hosts = CLIENT_HOSTS) {
|
function makeClient(hosts = CLIENT_HOSTS) {
|
||||||
console.log(`[make] client hosts: ${hosts.join(', ')}`)
|
console.log(`[make] client hosts: ${hosts.join(', ')}`)
|
||||||
|
npmRun('brand:sync')
|
||||||
npmRun('build:client-bundle')
|
npmRun('build:client-bundle')
|
||||||
for (const host of hosts) {
|
for (const host of hosts) {
|
||||||
const script = clientNpmScript(host)
|
const script = clientNpmScript(host)
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Sync peardock-branding/ → build/ + ui/brand/ for the app and binary packaging.
|
||||||
|
*
|
||||||
|
* Generates:
|
||||||
|
* build/icon.png — Electron / generic (1024)
|
||||||
|
* build/icon.icns — macOS (iconutil)
|
||||||
|
* build/icon.ico — Windows (from branding favicon package)
|
||||||
|
* build/icon-*.png — common sizes
|
||||||
|
* ui/brand/* — runtime UI assets (titlebar, etc.)
|
||||||
|
* peardock-branding/app-icons/{macos,windows,web}/ — filled platform packs
|
||||||
|
*
|
||||||
|
* Requires macOS `sips` + `iconutil` for .icns; otherwise skips icns with a warning.
|
||||||
|
* Safe to run on Linux CI for png/ico/ui copies.
|
||||||
|
*
|
||||||
|
* node scripts/sync-branding.cjs
|
||||||
|
* npm run brand:sync
|
||||||
|
*/
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const { spawnSync, execFileSync } = require('child_process')
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..')
|
||||||
|
const brand = path.join(root, 'peardock-branding')
|
||||||
|
const buildDir = path.join(root, 'build')
|
||||||
|
const uiBrand = path.join(root, 'ui', 'brand')
|
||||||
|
|
||||||
|
const SRC = {
|
||||||
|
master1024: path.join(brand, 'app-icons', 'ios', 'peardock-1024.png'),
|
||||||
|
master512: path.join(brand, 'app-icons', 'ios', 'peardock-512.png'),
|
||||||
|
tight: path.join(brand, 'logos', 'icon-only', 'peardock-icon-tight-transparent.png'),
|
||||||
|
horizontalWhite: path.join(brand, 'logos', 'horizontal', 'peardock-horizontal-white.png'),
|
||||||
|
horizontalTrans: path.join(
|
||||||
|
brand,
|
||||||
|
'logos',
|
||||||
|
'horizontal',
|
||||||
|
'peardock-horizontal-transparent.png'
|
||||||
|
),
|
||||||
|
logoWhite: path.join(brand, 'logos', 'monochrome', 'peardock-logo-white.png'),
|
||||||
|
logoTrans: path.join(brand, 'logos', 'primary', 'peardock-logo-transparent-512.png'),
|
||||||
|
faviconDir: path.join(brand, 'app-icons', 'favicon'),
|
||||||
|
faviconIco: path.join(brand, 'app-icons', 'favicon', 'favicon.ico'),
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(...a) {
|
||||||
|
console.log('[brand:sync]', ...a)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mustExist(p, label) {
|
||||||
|
if (!fs.existsSync(p)) throw new Error(`Missing branding asset (${label}): ${p}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFile(src, dest) {
|
||||||
|
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
||||||
|
fs.copyFileSync(src, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
function which(cmd) {
|
||||||
|
const r = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
})
|
||||||
|
return r.status === 0 ? r.stdout.trim().split(/\r?\n/)[0] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function sipsResize(src, dest, size) {
|
||||||
|
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
||||||
|
execFileSync(
|
||||||
|
'sips',
|
||||||
|
['-z', String(size), String(size), src, '--out', dest],
|
||||||
|
{ stdio: 'pipe' }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildIcns(src1024, outIcns) {
|
||||||
|
if (!which('sips') || !which('iconutil')) {
|
||||||
|
log('skip .icns (sips/iconutil not available — run brand:sync on macOS for full set)')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const iconset = path.join(buildDir, 'icon.iconset')
|
||||||
|
fs.rmSync(iconset, { recursive: true, force: true })
|
||||||
|
fs.mkdirSync(iconset, { recursive: true })
|
||||||
|
|
||||||
|
const pairs = [
|
||||||
|
[16, 'icon_16x16.png'],
|
||||||
|
[32, '[email protected]'],
|
||||||
|
[32, 'icon_32x32.png'],
|
||||||
|
[64, '[email protected]'],
|
||||||
|
[128, 'icon_128x128.png'],
|
||||||
|
[256, '[email protected]'],
|
||||||
|
[256, 'icon_256x256.png'],
|
||||||
|
[512, '[email protected]'],
|
||||||
|
[512, 'icon_512x512.png'],
|
||||||
|
[1024, '[email protected]'],
|
||||||
|
]
|
||||||
|
for (const [size, name] of pairs) {
|
||||||
|
sipsResize(src1024, path.join(iconset, name), size)
|
||||||
|
}
|
||||||
|
execFileSync('iconutil', ['-c', 'icns', iconset, '-o', outIcns], { stdio: 'pipe' })
|
||||||
|
fs.rmSync(iconset, { recursive: true, force: true })
|
||||||
|
log('wrote', path.relative(root, outIcns))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncUiBrand() {
|
||||||
|
fs.mkdirSync(uiBrand, { recursive: true })
|
||||||
|
copyFile(SRC.tight, path.join(uiBrand, 'mark.png'))
|
||||||
|
copyFile(SRC.logoWhite, path.join(uiBrand, 'logo-white.png'))
|
||||||
|
copyFile(SRC.logoTrans, path.join(uiBrand, 'logo-transparent.png'))
|
||||||
|
copyFile(SRC.horizontalWhite, path.join(uiBrand, 'horizontal-white.png'))
|
||||||
|
copyFile(SRC.horizontalTrans, path.join(uiBrand, 'horizontal-transparent.png'))
|
||||||
|
// favicon set for the desktop shell
|
||||||
|
const favOut = path.join(uiBrand, 'favicon')
|
||||||
|
fs.mkdirSync(favOut, { recursive: true })
|
||||||
|
for (const name of fs.readdirSync(SRC.faviconDir)) {
|
||||||
|
if (name.startsWith('.')) continue
|
||||||
|
copyFile(path.join(SRC.faviconDir, name), path.join(favOut, name))
|
||||||
|
}
|
||||||
|
log('wrote ui/brand/*')
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncBuildIcons() {
|
||||||
|
fs.mkdirSync(buildDir, { recursive: true })
|
||||||
|
// Master PNG for electron-forge (path without extension: build/icon)
|
||||||
|
copyFile(SRC.master1024, path.join(buildDir, 'icon.png'))
|
||||||
|
if (which('sips')) {
|
||||||
|
for (const size of [16, 32, 48, 64, 128, 256, 512]) {
|
||||||
|
sipsResize(SRC.master1024, path.join(buildDir, `icon-${size}.png`), size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Linux CI: copy existing ios sizes where possible
|
||||||
|
const map = {
|
||||||
|
16: 'peardock-16.png',
|
||||||
|
32: 'peardock-32.png',
|
||||||
|
48: 'peardock-48.png',
|
||||||
|
64: 'peardock-64.png',
|
||||||
|
128: 'peardock-128.png',
|
||||||
|
256: 'peardock-256.png',
|
||||||
|
512: 'peardock-512.png',
|
||||||
|
}
|
||||||
|
for (const [size, name] of Object.entries(map)) {
|
||||||
|
const src = path.join(brand, 'app-icons', 'ios', name)
|
||||||
|
if (fs.existsSync(src)) copyFile(src, path.join(buildDir, `icon-${size}.png`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Multi-size .ico (PNG-in-ICO, Vista+) for Windows Electron / bare-build
|
||||||
|
writeMultiSizeIco(
|
||||||
|
[16, 32, 48, 64, 128, 256].map((s) => path.join(buildDir, `icon-${s}.png`)),
|
||||||
|
path.join(buildDir, 'icon.ico')
|
||||||
|
)
|
||||||
|
buildIcns(SRC.master1024, path.join(buildDir, 'icon.icns'))
|
||||||
|
log('wrote build/icon.png (+ sizes)')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a multi-resolution ICO containing PNG images (supported since Windows Vista).
|
||||||
|
* @param {string[]} pngPaths existing PNG files (missing sizes skipped)
|
||||||
|
* @param {string} outIco
|
||||||
|
*/
|
||||||
|
function writeMultiSizeIco(pngPaths, outIco) {
|
||||||
|
const images = []
|
||||||
|
for (const p of pngPaths) {
|
||||||
|
if (!fs.existsSync(p)) continue
|
||||||
|
const data = fs.readFileSync(p)
|
||||||
|
// read IHDR width/height
|
||||||
|
if (data.length < 24 || data[0] !== 0x89) continue
|
||||||
|
const w = data.readUInt32BE(16)
|
||||||
|
const h = data.readUInt32BE(20)
|
||||||
|
images.push({ w: w >= 256 ? 0 : w, h: h >= 256 ? 0 : h, data })
|
||||||
|
}
|
||||||
|
if (!images.length) {
|
||||||
|
if (fs.existsSync(SRC.faviconIco)) {
|
||||||
|
copyFile(SRC.faviconIco, outIco)
|
||||||
|
log('wrote build/icon.ico (fallback favicon)')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const count = images.length
|
||||||
|
const headerSize = 6
|
||||||
|
const dirEntrySize = 16
|
||||||
|
let offset = headerSize + dirEntrySize * count
|
||||||
|
const header = Buffer.alloc(headerSize)
|
||||||
|
header.writeUInt16LE(0, 0) // reserved
|
||||||
|
header.writeUInt16LE(1, 2) // type icon
|
||||||
|
header.writeUInt16LE(count, 4)
|
||||||
|
const dirs = []
|
||||||
|
const blobs = []
|
||||||
|
for (const img of images) {
|
||||||
|
const dir = Buffer.alloc(dirEntrySize)
|
||||||
|
dir.writeUInt8(img.w > 255 ? 0 : img.w, 0)
|
||||||
|
dir.writeUInt8(img.h > 255 ? 0 : img.h, 1)
|
||||||
|
dir.writeUInt8(0, 2) // color palette
|
||||||
|
dir.writeUInt8(0, 3)
|
||||||
|
dir.writeUInt16LE(1, 4) // planes
|
||||||
|
dir.writeUInt16LE(32, 6) // bit count
|
||||||
|
dir.writeUInt32LE(img.data.length, 8)
|
||||||
|
dir.writeUInt32LE(offset, 12)
|
||||||
|
offset += img.data.length
|
||||||
|
dirs.push(dir)
|
||||||
|
blobs.push(img.data)
|
||||||
|
}
|
||||||
|
fs.writeFileSync(outIco, Buffer.concat([header, ...dirs, ...blobs]))
|
||||||
|
log('wrote build/icon.ico (', count, 'sizes )')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillPlatformFolders() {
|
||||||
|
// macos
|
||||||
|
const macDir = path.join(brand, 'app-icons', 'macos')
|
||||||
|
fs.mkdirSync(macDir, { recursive: true })
|
||||||
|
if (fs.existsSync(path.join(buildDir, 'icon.icns'))) {
|
||||||
|
copyFile(path.join(buildDir, 'icon.icns'), path.join(macDir, 'peardock.icns'))
|
||||||
|
}
|
||||||
|
copyFile(SRC.master1024, path.join(macDir, 'peardock-1024.png'))
|
||||||
|
if (fs.existsSync(path.join(buildDir, 'icon-512.png'))) {
|
||||||
|
copyFile(path.join(buildDir, 'icon-512.png'), path.join(macDir, 'peardock-512.png'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// windows
|
||||||
|
const winDir = path.join(brand, 'app-icons', 'windows')
|
||||||
|
fs.mkdirSync(winDir, { recursive: true })
|
||||||
|
if (fs.existsSync(path.join(buildDir, 'icon.ico'))) {
|
||||||
|
copyFile(path.join(buildDir, 'icon.ico'), path.join(winDir, 'peardock.ico'))
|
||||||
|
}
|
||||||
|
copyFile(SRC.master1024, path.join(winDir, 'peardock-1024.png'))
|
||||||
|
|
||||||
|
// web
|
||||||
|
const webDir = path.join(brand, 'app-icons', 'web')
|
||||||
|
fs.mkdirSync(webDir, { recursive: true })
|
||||||
|
for (const name of fs.readdirSync(SRC.faviconDir)) {
|
||||||
|
if (name.startsWith('.')) continue
|
||||||
|
copyFile(path.join(SRC.faviconDir, name), path.join(webDir, name))
|
||||||
|
}
|
||||||
|
log('filled peardock-branding/app-icons/{macos,windows,web}')
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeBrandCss() {
|
||||||
|
const css = `/**
|
||||||
|
* PearDock brand tokens — generated by scripts/sync-branding.cjs
|
||||||
|
* Source: peardock-branding/colors/palette.md
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--pd-blue: #2563eb;
|
||||||
|
--pd-green: #84cc16;
|
||||||
|
--pd-cyan: #06b6d4;
|
||||||
|
--pd-navy: #0f172a;
|
||||||
|
--pd-stem: #b45309;
|
||||||
|
--pd-sky: #e0f2fe;
|
||||||
|
--pd-white: #ffffff;
|
||||||
|
--pd-slate: #334155;
|
||||||
|
|
||||||
|
/* App surface aliases (dark desktop shell) */
|
||||||
|
--pd-bg: #0a0c10;
|
||||||
|
--pd-bg-elevated: #0f172a;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
fs.writeFileSync(path.join(uiBrand, 'brand.css'), css)
|
||||||
|
log('wrote ui/brand/brand.css')
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
if (!fs.existsSync(brand)) {
|
||||||
|
throw new Error('peardock-branding/ not found — add the branding kit to the repo root')
|
||||||
|
}
|
||||||
|
mustExist(SRC.master1024, 'ios 1024 icon')
|
||||||
|
mustExist(SRC.tight, 'icon-only mark')
|
||||||
|
mustExist(SRC.faviconDir, 'favicon package')
|
||||||
|
|
||||||
|
syncBuildIcons()
|
||||||
|
syncUiBrand()
|
||||||
|
fillPlatformFolders()
|
||||||
|
writeBrandCss()
|
||||||
|
log('done')
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* PearDock brand tokens — generated by scripts/sync-branding.cjs
|
||||||
|
* Source: peardock-branding/colors/palette.md
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--pd-blue: #2563eb;
|
||||||
|
--pd-green: #84cc16;
|
||||||
|
--pd-cyan: #06b6d4;
|
||||||
|
--pd-navy: #0f172a;
|
||||||
|
--pd-stem: #b45309;
|
||||||
|
--pd-sky: #e0f2fe;
|
||||||
|
--pd-white: #ffffff;
|
||||||
|
--pd-slate: #334155;
|
||||||
|
|
||||||
|
/* App surface aliases (dark desktop shell) */
|
||||||
|
--pd-bg: #0a0c10;
|
||||||
|
--pd-bg-elevated: #0f172a;
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 650 B |