← back to Patterndesignlab
designers: 15 studio personas + coherent balanced catalog split, auth schema, generated portraits
53f0f0528d9416f56591664279625d394cfedd10 · 2026-07-05 18:02:41 -0700 · Steve
Files touched
A db/002_designer_accounts.sqlM package-lock.jsonM package.jsonA public/assets/designers/atelier-solene.pngA public/assets/designers/bellamy-house.pngA public/assets/designers/casa-lumo.pngA public/assets/designers/cirrus-studio.pngA public/assets/designers/house-of-jill.pngA public/assets/designers/indigo-field.pngA public/assets/designers/juniper-lane.pngA public/assets/designers/marlowe-finch.pngA public/assets/designers/nordheim-design.pngA public/assets/designers/oleander-oak.pngA public/assets/designers/peony-pine.pngA public/assets/designers/studio-fernwood.pngA public/assets/designers/studio-mesa.pngA public/assets/designers/verre-noir.pngA public/assets/designers/wren-wilder.pngA scripts/seed-designers.jsM scripts/settlement-vision-pass.js
Diff
commit 53f0f0528d9416f56591664279625d394cfedd10
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Jul 5 18:02:41 2026 -0700
designers: 15 studio personas + coherent balanced catalog split, auth schema, generated portraits
---
db/002_designer_accounts.sql | 22 +++
package-lock.json | 230 ++++++++++++++++++++++++++++
package.json | 3 +
public/assets/designers/atelier-solene.png | Bin 0 -> 1170468 bytes
public/assets/designers/bellamy-house.png | Bin 0 -> 1304576 bytes
public/assets/designers/casa-lumo.png | Bin 0 -> 1598803 bytes
public/assets/designers/cirrus-studio.png | Bin 0 -> 1162628 bytes
public/assets/designers/house-of-jill.png | Bin 0 -> 1355710 bytes
public/assets/designers/indigo-field.png | Bin 0 -> 1207809 bytes
public/assets/designers/juniper-lane.png | Bin 0 -> 1712827 bytes
public/assets/designers/marlowe-finch.png | Bin 0 -> 1304286 bytes
public/assets/designers/nordheim-design.png | Bin 0 -> 1117932 bytes
public/assets/designers/oleander-oak.png | Bin 0 -> 1801215 bytes
public/assets/designers/peony-pine.png | Bin 0 -> 1301954 bytes
public/assets/designers/studio-fernwood.png | Bin 0 -> 1650227 bytes
public/assets/designers/studio-mesa.png | Bin 0 -> 1533313 bytes
public/assets/designers/verre-noir.png | Bin 0 -> 1040278 bytes
public/assets/designers/wren-wilder.png | Bin 0 -> 1509749 bytes
scripts/seed-designers.js | 155 +++++++++++++++++++
scripts/settlement-vision-pass.js | 4 +-
20 files changed, 413 insertions(+), 1 deletion(-)
diff --git a/db/002_designer_accounts.sql b/db/002_designer_accounts.sql
new file mode 100644
index 0000000..5adf6da
--- /dev/null
+++ b/db/002_designer_accounts.sql
@@ -0,0 +1,22 @@
+-- 002 — designer accounts + full profile fields (additive, idempotent).
+-- Turns the bare designers table into patternbank-style studio profiles with sign-in.
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS founder_name TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS tagline TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS email TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS password_hash TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS city TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS state_region TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS logo TEXT; -- designer-uploaded logo (separate from avatar photo)
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS accent_hex TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS website TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS instagram TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS pinterest TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS tiktok TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS facebook TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS twitter TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS youtube TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS linkedin TEXT;
+ALTER TABLE designers ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT now();
+
+-- unique login email (partial: only when set)
+CREATE UNIQUE INDEX IF NOT EXISTS designers_email_uniq ON designers (lower(email)) WHERE email IS NOT NULL;
diff --git a/package-lock.json b/package-lock.json
index 01ab185..91b908c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,10 @@
"name": "patterndesignlab",
"version": "0.1.0",
"dependencies": {
+ "bcryptjs": "^3.0.3",
"express": "^4.22.2",
+ "jsonwebtoken": "^9.0.3",
+ "multer": "^2.2.0",
"pg": "^8.22.0"
},
"optionalDependencies": {
@@ -39,12 +42,27 @@
"node": ">= 0.6"
}
},
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
+ "license": "MIT"
+ },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
"node_modules/body-parser": {
"version": "1.20.5",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
@@ -69,6 +87,29 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -107,6 +148,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/concat-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
+ "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+ "engines": [
+ "node >= 6.0"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.0.2",
+ "typedarray": "^0.0.6"
+ }
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -198,6 +254,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -469,6 +534,97 @@
"node": ">= 0.10"
}
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -544,6 +700,25 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/multer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz",
+ "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.6.0",
+ "concat-stream": "^2.0.0",
+ "type-is": "^1.6.18"
+ },
+ "engines": {
+ "node": ">= 10.16.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -773,6 +948,20 @@
"node": ">= 0.8"
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -799,6 +988,18 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
@@ -940,6 +1141,23 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/stripe": {
"version": "14.25.0",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-14.25.0.tgz",
@@ -976,6 +1194,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+ "license": "MIT"
+ },
"node_modules/undici-types": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
@@ -992,6 +1216,12 @@
"node": ">= 0.8"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
diff --git a/package.json b/package.json
index 6571d0f..18b5ee3 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,10 @@
"schema": "psql -h 127.0.0.1 -U dw_admin -d patterndesignlab -f db/schema.sql"
},
"dependencies": {
+ "bcryptjs": "^3.0.3",
"express": "^4.22.2",
+ "jsonwebtoken": "^9.0.3",
+ "multer": "^2.2.0",
"pg": "^8.22.0"
},
"optionalDependencies": {
diff --git a/public/assets/designers/atelier-solene.png b/public/assets/designers/atelier-solene.png
new file mode 100644
index 0000000..f015406
Binary files /dev/null and b/public/assets/designers/atelier-solene.png differ
diff --git a/public/assets/designers/bellamy-house.png b/public/assets/designers/bellamy-house.png
new file mode 100644
index 0000000..21f204f
Binary files /dev/null and b/public/assets/designers/bellamy-house.png differ
diff --git a/public/assets/designers/casa-lumo.png b/public/assets/designers/casa-lumo.png
new file mode 100644
index 0000000..1947686
Binary files /dev/null and b/public/assets/designers/casa-lumo.png differ
diff --git a/public/assets/designers/cirrus-studio.png b/public/assets/designers/cirrus-studio.png
new file mode 100644
index 0000000..a5af067
Binary files /dev/null and b/public/assets/designers/cirrus-studio.png differ
diff --git a/public/assets/designers/house-of-jill.png b/public/assets/designers/house-of-jill.png
new file mode 100644
index 0000000..aada412
Binary files /dev/null and b/public/assets/designers/house-of-jill.png differ
diff --git a/public/assets/designers/indigo-field.png b/public/assets/designers/indigo-field.png
new file mode 100644
index 0000000..a46ac45
Binary files /dev/null and b/public/assets/designers/indigo-field.png differ
diff --git a/public/assets/designers/juniper-lane.png b/public/assets/designers/juniper-lane.png
new file mode 100644
index 0000000..9251459
Binary files /dev/null and b/public/assets/designers/juniper-lane.png differ
diff --git a/public/assets/designers/marlowe-finch.png b/public/assets/designers/marlowe-finch.png
new file mode 100644
index 0000000..5fd47cb
Binary files /dev/null and b/public/assets/designers/marlowe-finch.png differ
diff --git a/public/assets/designers/nordheim-design.png b/public/assets/designers/nordheim-design.png
new file mode 100644
index 0000000..7317d98
Binary files /dev/null and b/public/assets/designers/nordheim-design.png differ
diff --git a/public/assets/designers/oleander-oak.png b/public/assets/designers/oleander-oak.png
new file mode 100644
index 0000000..c5018ab
Binary files /dev/null and b/public/assets/designers/oleander-oak.png differ
diff --git a/public/assets/designers/peony-pine.png b/public/assets/designers/peony-pine.png
new file mode 100644
index 0000000..6d2b269
Binary files /dev/null and b/public/assets/designers/peony-pine.png differ
diff --git a/public/assets/designers/studio-fernwood.png b/public/assets/designers/studio-fernwood.png
new file mode 100644
index 0000000..f1ba6ba
Binary files /dev/null and b/public/assets/designers/studio-fernwood.png differ
diff --git a/public/assets/designers/studio-mesa.png b/public/assets/designers/studio-mesa.png
new file mode 100644
index 0000000..c0bef72
Binary files /dev/null and b/public/assets/designers/studio-mesa.png differ
diff --git a/public/assets/designers/verre-noir.png b/public/assets/designers/verre-noir.png
new file mode 100644
index 0000000..15296f8
Binary files /dev/null and b/public/assets/designers/verre-noir.png differ
diff --git a/public/assets/designers/wren-wilder.png b/public/assets/designers/wren-wilder.png
new file mode 100644
index 0000000..df298aa
Binary files /dev/null and b/public/assets/designers/wren-wilder.png differ
diff --git a/scripts/seed-designers.js b/scripts/seed-designers.js
new file mode 100644
index 0000000..770c61e
--- /dev/null
+++ b/scripts/seed-designers.js
@@ -0,0 +1,155 @@
+#!/usr/bin/env node
+/**
+ * Seed the 15 shared designer personas into patterndesignlab and split the
+ * existing catalog across them by style lane. Idempotent.
+ * - upserts 15 designers (name, bio, socials, generated portrait as avatar)
+ * - copies portraits → public/assets/designers/<slug>.png
+ * - reassigns EVERY design.designer_id to the best-matching persona (keyword
+ * score on style/motif/technique/title/tags; zero-match → round-robin)
+ * - removes stale designers (the 3 seed placeholders)
+ * - generates ONE shared credential per persona (works on both platforms)
+ */
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+const bcrypt = require('bcryptjs');
+const { Pool } = require('pg');
+
+const SHARED = path.join(process.env.HOME, 'Projects/_shared/designer-personas');
+const personas = JSON.parse(fs.readFileSync(path.join(SHARED, 'personas.json'), 'utf8')).designers;
+const ASSETS = path.join(__dirname, '..', 'public', 'assets', 'designers');
+fs.mkdirSync(ASSETS, { recursive: true });
+
+const pool = new Pool({
+ host: process.env.PGHOST || '127.0.0.1',
+ user: process.env.PGUSER || 'dw_admin',
+ database: process.env.PGDATABASE || 'patterndesignlab',
+ password: process.env.PGPASSWORD || undefined,
+});
+
+// ---- shared credentials (generate once, reuse across platforms) ----
+function loadOrMakeCreds() {
+ const p = path.join(SHARED, 'credentials.json');
+ let creds = {};
+ if (fs.existsSync(p)) creds = JSON.parse(fs.readFileSync(p, 'utf8'));
+ let changed = false;
+ for (const d of personas) {
+ if (!creds[d.slug]) {
+ const pw = crypto.randomBytes(9).toString('base64').replace(/[^a-zA-Z0-9]/g, '').slice(0, 12) + '2x';
+ creds[d.slug] = { email: `${d.slug}@designerstudios.com`, password: pw };
+ changed = true;
+ }
+ }
+ if (changed) fs.writeFileSync(p, JSON.stringify(creds, null, 2));
+ return creds;
+}
+
+function copyPortrait(slug) {
+ const src = path.join(SHARED, 'portraits', `${slug}.png`);
+ const dst = path.join(ASSETS, `${slug}.png`);
+ if (fs.existsSync(src)) { fs.copyFileSync(src, dst); return `/assets/designers/${slug}.png`; }
+ return null;
+}
+
+function scoreDesign(design, kw) {
+ const hay = ' ' + [design.style, design.motif, design.technique, design.title, (design.tags || []).join(' ')]
+ .filter(Boolean).join(' ').toLowerCase() + ' ';
+ const words = new Set(hay.split(/[^a-z]+/).filter(Boolean)); // whole-word tokens
+ let s = 0;
+ for (const k of kw) {
+ if (k.includes(' ')) { if (hay.includes(' ' + k + ' ') || hay.includes(k)) s++; } // phrase
+ else if (words.has(k)) s++; // whole word only
+ }
+ return s;
+}
+
+(async () => {
+ const creds = loadOrMakeCreds();
+ const slugs = personas.map((d) => d.slug);
+
+ // 1. upsert 15 designers
+ for (const d of personas) {
+ const avatar = copyPortrait(d.slug);
+ const hash = await bcrypt.hash(creds[d.slug].password, 10);
+ const s = d.socials || {};
+ await pool.query(
+ `INSERT INTO designers
+ (slug,name,founder_name,tagline,bio,country,city,state_region,avatar,accent_hex,email,password_hash,
+ website,instagram,pinterest,tiktok,facebook,twitter,youtube,linkedin,updated_at)
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,now())
+ ON CONFLICT (slug) DO UPDATE SET
+ name=EXCLUDED.name, founder_name=EXCLUDED.founder_name, tagline=EXCLUDED.tagline,
+ bio=EXCLUDED.bio, country=EXCLUDED.country, city=EXCLUDED.city, state_region=EXCLUDED.state_region,
+ avatar=COALESCE(EXCLUDED.avatar, designers.avatar), accent_hex=EXCLUDED.accent_hex,
+ email=EXCLUDED.email, password_hash=COALESCE(designers.password_hash, EXCLUDED.password_hash),
+ website=EXCLUDED.website, instagram=EXCLUDED.instagram, pinterest=EXCLUDED.pinterest,
+ tiktok=EXCLUDED.tiktok, facebook=EXCLUDED.facebook, twitter=EXCLUDED.twitter,
+ youtube=EXCLUDED.youtube, linkedin=EXCLUDED.linkedin, updated_at=now()`,
+ [d.slug, d.studio_name, d.founder_name, d.tagline, d.bio, d.location.country,
+ d.location.city, d.location.state_region || null, avatar, d.accent_hex,
+ creds[d.slug].email, hash, s.website || null, s.instagram || null, s.pinterest || null,
+ s.tiktok || null, s.facebook || null, s.twitter || null, s.youtube || null, s.linkedin || null]);
+ }
+
+ // 2. resolve persona slug -> id
+ const idBySlug = {};
+ for (const row of (await pool.query('SELECT id,slug FROM designers WHERE slug = ANY($1)', [slugs])).rows) {
+ idBySlug[row.slug] = row.id;
+ }
+
+ // 3. CLUSTER the catalog into 15 coherent, balanced chunks, then assign the
+ // best-fitting persona to each chunk. Sorting by style→motif→colorway→hex
+ // makes each contiguous slice internally consistent (one signature style),
+ // and popular styles (chinoiserie/stripe) naturally span several studios —
+ // like a real marketplace.
+ const designs = (await pool.query(
+ `SELECT id,style,motif,colorway,dominant_hex,title,tags FROM designs
+ ORDER BY style NULLS LAST, motif NULLS LAST, colorway NULLS LAST, dominant_hex NULLS LAST, id`)).rows;
+ const buckets = {}; slugs.forEach((s) => (buckets[s] = []));
+ const K = slugs.length;
+ const chunkSize = Math.ceil(designs.length / K);
+ const chunks = [];
+ for (let i = 0; i < K; i++) chunks.push(designs.slice(i * chunkSize, (i + 1) * chunkSize));
+
+ const dominantStyle = (chunk) => {
+ const m = {}; chunk.forEach((d) => { const s = d.style || 'Mixed'; m[s] = (m[s] || 0) + 1; });
+ return Object.entries(m).sort((a, b) => b[1] - a[1])[0][0];
+ };
+ const personaChunkScore = (p, chunk) => {
+ let s = 0; for (const d of chunk.slice(0, 60)) s += scoreDesign(d, p.style_lane.keywords); return s;
+ };
+ // greedy best-fit persona↔chunk assignment
+ const pairs = [];
+ chunks.forEach((c, ci) => personas.forEach((p) => pairs.push({ ci, slug: p.slug, sc: personaChunkScore(p, c) })));
+ pairs.sort((a, b) => b.sc - a.sc);
+ const assign = {}; const chunkTaken = new Set(); const usedPersona = new Set();
+ for (const pr of pairs) {
+ if (chunkTaken.has(pr.ci) || usedPersona.has(pr.slug) || pr.sc === 0) continue;
+ assign[pr.ci] = pr.slug; chunkTaken.add(pr.ci); usedPersona.add(pr.slug);
+ }
+ const leftover = slugs.filter((s) => !usedPersona.has(s));
+ chunks.forEach((c, ci) => { if (!assign[ci]) assign[ci] = leftover.shift(); });
+ chunks.forEach((c, ci) => c.forEach((d) => buckets[assign[ci]].push(d.id)));
+ // stash dominant style per studio for the report
+ const domBySlug = {}; chunks.forEach((c, ci) => (domBySlug[assign[ci]] = dominantStyle(c)));
+
+ // 4. apply reassignment
+ for (const slug of slugs) {
+ if (buckets[slug].length) {
+ await pool.query('UPDATE designs SET designer_id=$1 WHERE id = ANY($2)', [idBySlug[slug], buckets[slug]]);
+ }
+ }
+
+ // 5. remove stale designers (now unreferenced)
+ const del = await pool.query('DELETE FROM designers WHERE slug <> ALL($1) RETURNING slug', [slugs]);
+
+ // 6. report
+ const dist = (await pool.query(
+ `SELECT d.slug, count(x.id)::int n FROM designers d LEFT JOIN designs x ON x.designer_id=d.id
+ GROUP BY d.slug ORDER BY n DESC`)).rows;
+ console.log('Catalog split across 15 studios (signature style):');
+ dist.forEach((r) => console.log(` ${r.slug.padEnd(20)} ${String(r.n).padStart(4)} ${domBySlug[r.slug] || ''}`));
+ console.log(`Removed ${del.rowCount} stale designer(s): ${del.rows.map((r) => r.slug).join(', ') || '(none)'}`);
+ console.log(`\nCredentials written → ${path.join(SHARED, 'credentials.json')}`);
+ await pool.end();
+})().catch((e) => { console.error(e); process.exit(1); });
diff --git a/scripts/settlement-vision-pass.js b/scripts/settlement-vision-pass.js
index 89056f2..d5fe5b3 100644
--- a/scripts/settlement-vision-pass.js
+++ b/scripts/settlement-vision-pass.js
@@ -175,8 +175,10 @@ async function main() {
// ---- run mode ----
const pg = new Client({ database: 'patterndesignlab' });
await pg.connect();
+ // Pre-public go-live: screen EVERY still-unscreened design (novelty + non-auto-title
+ // credibles included) so nothing goes public unscreened. Was previously credible auto-title only.
const { rows } = await pg.query(
- "SELECT id FROM designs WHERE content_class='credible' AND title ~ 'No\\.[0-9]+$' AND settlement_status IS NULL ORDER BY id"
+ "SELECT id FROM designs WHERE settlement_status IS NULL ORDER BY id"
);
const ids = rows.map((r) => r.id).slice(0, LIMIT === Infinity ? undefined : LIMIT);
console.log(`[run] ${ids.length} designs to screen (ceiling $${CEILING})`);
← d92f3a8 Go-live prep for prestige.agentabrams.com: vision-screen all
·
back to Patterndesignlab
·
designer accounts: bcrypt login + JWT cookie, self-edit endp a4e12d5 →