← back to Majilite Onboard
scripts/build.js
152 lines
#!/usr/bin/env node
/**
* Majilite "Metallic Specialties I" (Nytek) — Full Monte staging build.
* Reads data/source_swatches.json (160 original pattern/colorway swatches),
* assigns DW-series SKUs (DWCC-######, auto next-free series), stamps the
* ONE shared Nytek spec onto every SKU, buckets a color family + dot hex for
* the sort/color grid, and writes data/products.json + data/collection.json.
*
* HARD rules honored:
* - Original pattern + colorway names stay CUSTOMER-FACING (title = "Pattern Colorway").
* - Majilite is name-keyed → mfr_sku = "Pattern Colorway" (the orderable identifier).
* - No cost on the card → quote-only (display_prices=false), $4.25 sample, cost=null.
* - Images always local: images/DWCC-######.png
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const src = JSON.parse(fs.readFileSync(path.join(ROOT, 'data/source_swatches.json'), 'utf8'));
const VENDOR = 'Majilite';
const BRAND = 'Nytek';
const COLLECTION = 'Metallic Specialties I';
const SKU_PREFIX = 'DWCC'; // renamed DWMJ -> DWCC (Steve 2026-08-10); folds into DWCC house series
const SKU_BASE = 600000; // DW number block for this series (Steve, 2026-08-10) -> DWCC-600001..
const VENDOR_CODE = 'MAJ';
const PRODUCT_TYPE = 'Wallcovering';
const MATERIAL = 'Faux Leather (Nylon Fiber Matrix / Nytek®)';
const SAMPLE_PRICE = 4.25;
// One spec block for the entire collection (page 10 of the digital card).
const SPEC = {
material: 'Nytek® — specially engineered Nylon Fiber Matrix faux leather (PFAS-, PVC-, plasticizer-free)',
width: '54 in / 137 cm',
width_in: 54,
weight: '8.8 oz/sq yd / 300 g/m²',
thickness: '28 mils / 0.7 mm',
cleanability_code: 'S/W',
crocking: 'Wet 5 / Dry 5',
colorfastness: 'Excellent (Gas, Fadeometer, Weather-O-Meter, Sulfide Staining)',
durability: '300,000+ double rubs (Wyzenbeek wire screen, ASTM D-4157-82)',
flammability_inherent: 'UFAC/NFPA 260 (Class 1) & CAL Technical Bulletin 117-2013 Section 1 — Pass',
flammability_optional: 'Optional FR for ASTM-E84 (adhered), IMO 652, IMO 653, BS5852, BS476, EN13501-1',
features: [
'Breathable', 'Built-in stain resistance (no topcoat needed)',
'Easy cleaning with common commercial cleaners', 'No off-gassing',
'Hydrolysis resistance up to 10 weeks'
],
custom_options: 'Custom colors (100-yard min), embossing (small min), quilting, printing, weaving, laser cutting; artisan collaborations',
environmental: 'Free from PVCs, phthalates, formaldehyde, heavy metals, dioxins, ozone-depleting chemicals; made in Massachusetts via EPA-approved processes',
care: 'Clean with mild soap and water or EPA-approved disinfectants',
uses: 'Seating, wall coverings, flat surfaces, displays',
industries: 'Retail, hospitality, healthcare, aerospace, marine, automotive, interior design',
manufacturer: 'Majilite Corporation, 1530 Broadway Road, Dracut, MA 01826',
contact: { phone: '+1.978.441.6800', email: 'Sales@majilite.com', web: 'www.majilite.com' },
source: 'Majilite Metallic Specialties I Digital Card 2026 (p.10)'
};
// ---- Color-family bucketing (keyword -> family + representative dot hex) ----
// Ordered: first match wins. Tuned for this metallic/neutral faux-leather line.
const FAMILY_RULES = [
[/rose gold|blossom|petal|pink|metallic rose/i, 'Rose Gold', '#d6a4a0'],
[/gold|brass|mystic gold|antique gold|soft gold|gold leaf|gold coin|gold stardust|white gold|cream gold/i, 'Gold', '#c9a04e'],
[/champagne|chardonnay|zinfandel|cameo|pashmina/i, 'Champagne', '#e4d3ad'],
[/black|onyx|midnight|carbon|techno/i, 'Black', '#1c1c1e'],
[/pewter|slate|charcoal|graphite|granite|distinction grey|nuance grey|infinity grey|shimmer slate/i, 'Pewter/Charcoal', '#6b6f74'],
[/stainless|steel|mercury|nickel|silver|aluminum|chrome|platinum|paragon|status|iceberg|diamond|storm|dove|moonbeam|mist|igloo/i, 'Silver/Grey', '#b7bcc2'],
[/white|snow|polar|ice|pure white|glacier|icicle|bright star|salt/i, 'White', '#f2f2f0'],
[/wheat|buff|beige|natural|tan|sand|fawn|pebble|parchment|featherbed|gelato|eggshell|ivory|crème|creme|powder|taupe|penny|bronze|copper|espresso|coin/i, 'Neutral/Tan', '#cdbf9e'],
];
function familyFor(color) {
for (const [re, fam, hex] of FAMILY_RULES) if (re.test(color)) return { color_family: fam, color_hex: hex };
return { color_family: 'Metallic', color_hex: '#a9adb2' };
}
// Style / motif tags from the pattern texture family
function styleTagsFor(pattern) {
const p = pattern.toLowerCase();
const t = [];
if (/raindrop|starlite|hive|hammered|pebble|stature/.test(p)) t.push('Textured');
if (/legacy|nuance|charm|linea|cross-hatch|status|techno/.test(p)) t.push('Woven Look');
if (/reflection|shimmer|radiance|finesse|eclipse|brushed|burnished|metallic paragon|attache/.test(p)) t.push('Metallic Sheen');
if (!t.length) t.push('Faux Leather');
return t;
}
const nowISO = new Date().toISOString();
const products = src.swatches.map((sw, i) => {
const seq = i + 1;
const num = SKU_BASE + seq; // 600001 .. 600160
const dw_sku = `${SKU_PREFIX}-${num}`;
const title = `${sw.pattern} ${sw.color}`; // original names, customer-facing
const mfr_sku = `${sw.pattern} ${sw.color}`; // Majilite is name-keyed
const fam = familyFor(sw.color);
const tags = Array.from(new Set([
'Faux Leather', 'Metallic', VENDOR, BRAND, COLLECTION, 'Contract',
sw.pattern, sw.color, fam.color_family, ...styleTagsFor(sw.pattern)
]));
return {
seq,
dw_sku,
variant_sku: `${dw_sku}-Sample`,
mfr_sku,
title,
pattern: sw.pattern,
color: sw.color,
vendor: VENDOR,
brand: BRAND,
collection: COLLECTION,
product_type: PRODUCT_TYPE,
material: MATERIAL,
color_family: fam.color_family,
color_hex: fam.color_hex,
image: `images/${dw_sku}.png`,
tags,
pricing: {
unit: 'yard', model: 'sample', sellable_as: 'sample',
price: SAMPLE_PRICE, sample_price: SAMPLE_PRICE, cost: null, retail: null,
display_prices: true, // onboarded as a sellable $4.25 SAMPLE (Steve, 2026-08-10)
pricing_notes: 'Onboarded as a $4.25 sample. By-the-yard retail pending Majilite cost list.'
},
spec: SPEC, // <-- spec assigned to EVERY sku
source_page: sw.page,
created_at: nowISO,
onboard_status: 'sample' // sellable $4.25 sample (yard retail deferred)
};
});
const collection = {
vendor: VENDOR, vendor_code: VENDOR_CODE, brand: BRAND, collection: COLLECTION,
sku_prefix: SKU_PREFIX + '-', sku_range_start: SKU_BASE + 1, is_private_label: false, private_label_name: null,
pricing_unit: 'yard', pricing_model: 'sample', sample_price: SAMPLE_PRICE,
display_prices: true, onboard_as: 'sample', product_type: PRODUCT_TYPE, material: MATERIAL,
count: products.length, spec: SPEC,
source_pdf: src.source_pdf, built_at: nowISO,
gated_for_golive: [
'Shopify publish of the 160 $4.25 SAMPLE products (customer-facing) — Steve-gated',
'vendor_registry INSERT to dw_unified (Mac2-canonical)',
'Later: by-the-yard retail once Majilite sends the cost list (Kathryn Gabriel / Two Gabriels)'
]
};
fs.mkdirSync(path.join(ROOT, 'data'), { recursive: true });
fs.writeFileSync(path.join(ROOT, 'data/products.json'), JSON.stringify(products, null, 2));
fs.writeFileSync(path.join(ROOT, 'data/collection.json'), JSON.stringify(collection, null, 2));
// quick family tally for the console
const tally = {};
for (const p of products) tally[p.color_family] = (tally[p.color_family] || 0) + 1;
console.log(`Built ${products.length} SKUs ${products[0].dw_sku} .. ${products[products.length-1].dw_sku}`);
console.log('Color families:', tally);