#!/usr/bin/env node /* * Generates a tileable hexagonal camouflage SVG, matching the Mattermost * brand book "Hexagonal Camouflage" pattern (denim variant, primary * brand background). * * Output: docs-site/static/img/patterns/hex-camo-denim.svg * * Re-run if you want to tweak the shade mix / density / cell size. */ import {writeFileSync, mkdirSync} from 'node:fs'; import {dirname, join, resolve} from 'node:path'; import {fileURLToPath} from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const OUT = resolve(HERE, '../static/img/patterns/hex-camo-denim.svg'); // Tile dimensions. Side=9 → ~3x smaller cells than v1 (was side=24). // Reads as fine grain / texture rather than chunky mosaic. const SIDE = 9; const HEX_W = 2 * SIDE; const HEX_H = Math.sqrt(3) * SIDE; const COL_STEP = 1.5 * SIDE; const ROW_STEP = HEX_H; const COLS = 8; // 8 cols × 8 rows ≈ 108×125 tile const ROWS = 8; const TILE_W = COL_STEP * COLS; const TILE_H = ROW_STEP * ROWS; // Tight shade band — every cell is within a narrow window of denim-500. // The base color matches the hero's solid bg, so most cells visually // disappear; only the slightly-lighter cells provide the texture beat. // No marigold / no high-contrast outliers. const SHADES = [ {color: '#1E325C', weight: 14}, // denim-500 (matches hero bg — invisible cells) {color: '#22386A', weight: 5}, // +4 lightness — faintly visible {color: '#172A4F', weight: 3}, // -2 lightness — adds depth shadow ]; // Stable PRNG so the SVG is deterministic and reproducible across runs. function mulberry32(seed) { return () => { seed |= 0; seed = (seed + 0x6D2B79F5) | 0; let t = seed; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } const rand = mulberry32(0xCAFE); function pickShade() { const total = SHADES.reduce((s, x) => s + x.weight, 0); let r = rand() * total; for (const s of SHADES) { if (r < s.weight) return s.color; r -= s.weight; } return SHADES[0].color; } function hexPath(cx, cy) { const pts = []; for (let i = 0; i < 6; i++) { const a = Math.PI / 3 * i; pts.push(`${(cx + SIDE * Math.cos(a)).toFixed(2)},${(cy + SIDE * Math.sin(a)).toFixed(2)}`); } return `M${pts.join(' L')}Z`; } const polygons = []; // Render extra cells beyond the tile edge so the seam is hidden when tiled. for (let col = -1; col <= COLS + 1; col++) { for (let row = -1; row <= ROWS + 1; row++) { const cx = col * COL_STEP; const cy = row * ROW_STEP + (col % 2 ? ROW_STEP / 2 : 0); polygons.push(``); } } const svg = ` ${polygons.join('\n ')} `; mkdirSync(dirname(OUT), {recursive: true}); writeFileSync(OUT, svg); console.log(`wrote ${OUT} (${TILE_W}×${TILE_H.toFixed(1)})`);