create-cogenta 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blueprint-defaults.d.ts.map +1 -1
- package/dist/blueprint-defaults.js +6 -0
- package/dist/blueprint-defaults.js.map +1 -1
- package/dist/blueprints/assets/photos/association/community-cleanup.jpg +0 -0
- package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
- package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
- package/dist/blueprints/assets/photos/association/harvest-food-drive.jpg +0 -0
- package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
- package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/blog-runs-itself.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/editing.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/plain-text-editor.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/reading-nonfiction.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/small-tool.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/writing-schedule.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/business-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/business-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/business.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/culture-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/culture-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/culture.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/news-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/news-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/news.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/opinion-2.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/opinion-3.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/opinion.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/adatum-publishing.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/contoso-mobile-app.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fabrikam-annual-report.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/litware-signage.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/northwind-rebrand.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/proseware-launch.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tailspin-streaming.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/wingtip-wayfinding.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/charred-octopus.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/creme-brulee.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/house-red.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/house-white.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/pan-seared-trout.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/roasted-beet-salad.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/slow-roast-duck-leg.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/soup-of-the-day.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/sparkling-water.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/wild-mushroom-risotto.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/avatar-1.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/store/camp-blanket.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/ceramic-pour-over-set.jpg +0 -0
- package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
- package/dist/blueprints/assets/photos/store/everyday-tee.jpg +0 -0
- package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
- package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
- package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
- package/dist/blueprints/assets/photos/store/trail-tote.jpg +0 -0
- package/dist/blueprints/assets/photos/store/wool-beanie.jpg +0 -0
- package/dist/blueprints/assets/photos/store/wool-overshirt.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-1.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-2.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/avatar-3.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/dashboard.jpg +0 -0
- package/dist/blueprints/assets/photos/vitrine/hero.jpg +0 -0
- package/dist/blueprints/association.d.ts +93 -15
- package/dist/blueprints/association.d.ts.map +1 -1
- package/dist/blueprints/association.js +637 -130
- package/dist/blueprints/association.js.map +1 -1
- package/dist/blueprints/blog.d.ts +81 -94
- package/dist/blueprints/blog.d.ts.map +1 -1
- package/dist/blueprints/blog.js +513 -177
- package/dist/blueprints/blog.js.map +1 -1
- package/dist/blueprints/content-pack.d.ts +83 -2
- package/dist/blueprints/content-pack.d.ts.map +1 -1
- package/dist/blueprints/content-pack.js +54 -1
- package/dist/blueprints/content-pack.js.map +1 -1
- package/dist/blueprints/content-packs.d.ts +4 -3
- package/dist/blueprints/content-packs.d.ts.map +1 -1
- package/dist/blueprints/content-packs.js +6 -3
- package/dist/blueprints/content-packs.js.map +1 -1
- package/dist/blueprints/demo-media.d.ts +39 -0
- package/dist/blueprints/demo-media.d.ts.map +1 -0
- package/dist/blueprints/demo-media.js +30 -0
- package/dist/blueprints/demo-media.js.map +1 -0
- package/dist/blueprints/documentation.d.ts +78 -15
- package/dist/blueprints/documentation.d.ts.map +1 -1
- package/dist/blueprints/documentation.js +585 -104
- package/dist/blueprints/documentation.js.map +1 -1
- package/dist/blueprints/magazine.d.ts +81 -15
- package/dist/blueprints/magazine.d.ts.map +1 -1
- package/dist/blueprints/magazine.js +431 -85
- package/dist/blueprints/magazine.js.map +1 -1
- package/dist/blueprints/menus.d.ts +25 -0
- package/dist/blueprints/menus.d.ts.map +1 -0
- package/dist/blueprints/menus.js +33 -0
- package/dist/blueprints/menus.js.map +1 -0
- package/dist/blueprints/photo-assets.d.ts +3 -0
- package/dist/blueprints/photo-assets.d.ts.map +1 -0
- package/dist/blueprints/photo-assets.js +33 -0
- package/dist/blueprints/photo-assets.js.map +1 -0
- package/dist/blueprints/portfolio.d.ts +81 -13
- package/dist/blueprints/portfolio.d.ts.map +1 -1
- package/dist/blueprints/portfolio.js +605 -102
- package/dist/blueprints/portfolio.js.map +1 -1
- package/dist/blueprints/registry.d.ts +3 -1
- package/dist/blueprints/registry.d.ts.map +1 -1
- package/dist/blueprints/registry.js +4 -1
- package/dist/blueprints/registry.js.map +1 -1
- package/dist/blueprints/restaurant.d.ts +65 -20
- package/dist/blueprints/restaurant.d.ts.map +1 -1
- package/dist/blueprints/restaurant.js +350 -117
- package/dist/blueprints/restaurant.js.map +1 -1
- package/dist/blueprints/saas.d.ts +81 -14
- package/dist/blueprints/saas.d.ts.map +1 -1
- package/dist/blueprints/saas.js +469 -132
- package/dist/blueprints/saas.js.map +1 -1
- package/dist/blueprints/site-settings-seed.d.ts +17 -0
- package/dist/blueprints/site-settings-seed.d.ts.map +1 -0
- package/dist/blueprints/site-settings-seed.js +45 -0
- package/dist/blueprints/site-settings-seed.js.map +1 -0
- package/dist/blueprints/starting-skins.d.ts +19 -0
- package/dist/blueprints/starting-skins.d.ts.map +1 -0
- package/dist/blueprints/starting-skins.js +301 -0
- package/dist/blueprints/starting-skins.js.map +1 -0
- package/dist/blueprints/store.d.ts +173 -0
- package/dist/blueprints/store.d.ts.map +1 -0
- package/dist/blueprints/store.js +643 -0
- package/dist/blueprints/store.js.map +1 -0
- package/dist/blueprints/vitrine.d.ts +94 -16
- package/dist/blueprints/vitrine.d.ts.map +1 -1
- package/dist/blueprints/vitrine.js +421 -142
- package/dist/blueprints/vitrine.js.map +1 -1
- package/dist/demo-art/compositions.d.ts +48 -0
- package/dist/demo-art/compositions.d.ts.map +1 -0
- package/dist/demo-art/compositions.js +690 -0
- package/dist/demo-art/compositions.js.map +1 -0
- package/dist/demo-art/index.d.ts +8 -0
- package/dist/demo-art/index.d.ts.map +1 -0
- package/dist/demo-art/index.js +5 -0
- package/dist/demo-art/index.js.map +1 -0
- package/dist/demo-art/oklch.d.ts +39 -0
- package/dist/demo-art/oklch.d.ts.map +1 -0
- package/dist/demo-art/oklch.js +79 -0
- package/dist/demo-art/oklch.js.map +1 -0
- package/dist/demo-art/png.d.ts +24 -0
- package/dist/demo-art/png.d.ts.map +1 -0
- package/dist/demo-art/png.js +110 -0
- package/dist/demo-art/png.js.map +1 -0
- package/dist/demo-art/render.d.ts +228 -0
- package/dist/demo-art/render.d.ts.map +1 -0
- package/dist/demo-art/render.js +478 -0
- package/dist/demo-art/render.js.map +1 -0
- package/dist/playground-reset.d.ts.map +1 -1
- package/dist/playground-reset.js +22 -4
- package/dist/playground-reset.js.map +1 -1
- package/dist/prompts.d.ts +12 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +32 -0
- package/dist/prompts.js.map +1 -1
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +6 -0
- package/dist/recap.js.map +1 -1
- package/dist/scaffold.d.ts +22 -2
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +185 -23
- package/dist/scaffold.js.map +1 -1
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +4 -1
- package/dist/wizard.js.map +1 -1
- package/package.json +14 -11
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
import { companionTones, oklchToRgb, rgbToOklch, withChroma, withLightness } from './oklch.js';
|
|
2
|
+
import { mulberry32 } from './render.js';
|
|
3
|
+
// ---------------------------------------------------------------- colour helpers
|
|
4
|
+
function hexToRgb(hex) {
|
|
5
|
+
const clean = hex.replace('#', '');
|
|
6
|
+
const r = Number.parseInt(clean.slice(0, 2), 16) / 255;
|
|
7
|
+
const g = Number.parseInt(clean.slice(2, 4), 16) / 255;
|
|
8
|
+
const b = Number.parseInt(clean.slice(4, 6), 16) / 255;
|
|
9
|
+
return { r, g, b };
|
|
10
|
+
}
|
|
11
|
+
function mix(a, b, t) {
|
|
12
|
+
return { r: a.r + (b.r - a.r) * t, g: a.g + (b.g - a.g) * t, b: a.b + (b.b - a.b) * t };
|
|
13
|
+
}
|
|
14
|
+
const WHITE = { r: 1, g: 1, b: 1 };
|
|
15
|
+
const BLACK = { r: 0, g: 0, b: 0 };
|
|
16
|
+
function lighten(color, amount) {
|
|
17
|
+
return mix(color, WHITE, amount);
|
|
18
|
+
}
|
|
19
|
+
function darken(color, amount) {
|
|
20
|
+
return mix(color, BLACK, amount);
|
|
21
|
+
}
|
|
22
|
+
function toRgb(palette) {
|
|
23
|
+
return {
|
|
24
|
+
bg: hexToRgb(palette.bg),
|
|
25
|
+
fg: hexToRgb(palette.fg),
|
|
26
|
+
accent: hexToRgb(palette.accent),
|
|
27
|
+
accentFg: hexToRgb(palette.accentFg),
|
|
28
|
+
muted: hexToRgb(palette.muted),
|
|
29
|
+
mutedFg: hexToRgb(palette.mutedFg),
|
|
30
|
+
border: hexToRgb(palette.border),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Pulls a hue (degrees) into the amber/terracotta arc used for "warm"
|
|
35
|
+
* compositions, leaving it alone if it's already there. A small rotation of
|
|
36
|
+
* the accent's *own* hue (as `rotateHue` alone would do) keeps a cool accent
|
|
37
|
+
* — a store's teal, say — cool; the "arch & sun"/"warm" family needs its
|
|
38
|
+
* backdrop to read as warm regardless of the palette it was built from, so
|
|
39
|
+
* this clamps to the nearer edge of the warm arc instead of rotating by a
|
|
40
|
+
* fixed offset.
|
|
41
|
+
*/
|
|
42
|
+
function clampHueToWarmArc(hueDegrees) {
|
|
43
|
+
const warmLow = 25;
|
|
44
|
+
const warmHigh = 70;
|
|
45
|
+
if (hueDegrees >= warmLow && hueDegrees <= warmHigh)
|
|
46
|
+
return hueDegrees;
|
|
47
|
+
const circularDistance = (a, b) => Math.min(Math.abs(a - b), 360 - Math.abs(a - b));
|
|
48
|
+
return circularDistance(hueDegrees, warmLow) <= circularDistance(hueDegrees, warmHigh)
|
|
49
|
+
? warmLow
|
|
50
|
+
: warmHigh;
|
|
51
|
+
}
|
|
52
|
+
function buildTones(c) {
|
|
53
|
+
const spread = companionTones(c.accent, 2);
|
|
54
|
+
const hue1 = spread[0] ?? c.accent;
|
|
55
|
+
const hue2 = spread[1] ?? c.accent;
|
|
56
|
+
const accentOklch = rgbToOklch(c.accent);
|
|
57
|
+
const warmHue = clampHueToWarmArc(accentOklch.h);
|
|
58
|
+
return {
|
|
59
|
+
hue1,
|
|
60
|
+
hue2,
|
|
61
|
+
deep: darken(c.accent, 0.22),
|
|
62
|
+
pale: lighten(c.muted, 0.06),
|
|
63
|
+
warm: oklchToRgb({ l: 0.86, c: Math.max(accentOklch.c, 0.08), h: warmHue }),
|
|
64
|
+
ink: withLightness(withChroma(c.accent, 0.4), 0.15),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A straight, hairline-thin flat segment between two fractional points —
|
|
69
|
+
* used for grid connectors, editorial rules, and floor lines. `render.ts`
|
|
70
|
+
* only offers a length+rotation `LineLayer`, so this does the point-to-point
|
|
71
|
+
* trigonometry once, in one place, for every composition below.
|
|
72
|
+
*/
|
|
73
|
+
function segment(from, to, canvasWidth, canvasHeight, thickness, color, alpha = 1) {
|
|
74
|
+
const shorter = Math.min(canvasWidth, canvasHeight);
|
|
75
|
+
const x1 = from[0] * canvasWidth;
|
|
76
|
+
const y1 = from[1] * canvasHeight;
|
|
77
|
+
const x2 = to[0] * canvasWidth;
|
|
78
|
+
const y2 = to[1] * canvasHeight;
|
|
79
|
+
const dx = x2 - x1;
|
|
80
|
+
const dy = y2 - y1;
|
|
81
|
+
const lengthPx = Math.max(Math.sqrt(dx * dx + dy * dy), 1);
|
|
82
|
+
const rotation = (Math.atan2(dy, dx) * 180) / Math.PI;
|
|
83
|
+
const center = [(x1 + x2) / 2 / canvasWidth, (y1 + y2) / 2 / canvasHeight];
|
|
84
|
+
return {
|
|
85
|
+
kind: 'line',
|
|
86
|
+
center,
|
|
87
|
+
length: lengthPx / shorter,
|
|
88
|
+
thickness,
|
|
89
|
+
rotation,
|
|
90
|
+
color,
|
|
91
|
+
alpha,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const HERO_FAMILY = {
|
|
95
|
+
mesh: 'grid',
|
|
96
|
+
grid: 'grid',
|
|
97
|
+
geometric: 'blocks',
|
|
98
|
+
blocks: 'blocks',
|
|
99
|
+
diagonal: 'bands',
|
|
100
|
+
bands: 'bands',
|
|
101
|
+
radial: 'rings',
|
|
102
|
+
rings: 'rings',
|
|
103
|
+
dark: 'ink',
|
|
104
|
+
ink: 'ink',
|
|
105
|
+
warm: 'sun',
|
|
106
|
+
sun: 'sun',
|
|
107
|
+
};
|
|
108
|
+
const HERO_WIDTH = 1600;
|
|
109
|
+
const HERO_HEIGHT = 1000;
|
|
110
|
+
/**
|
|
111
|
+
* A 1600×1000 hero visual. Every theme frames the hero's media beside the
|
|
112
|
+
* title — a picture next to the text, never a backdrop under it — so the
|
|
113
|
+
* composition fills the whole canvas like a poster: an early version kept
|
|
114
|
+
* the left half a single flat colour "for the title" and rendered as a
|
|
115
|
+
* mostly blank frame on every home page. `variant` names one of the flat
|
|
116
|
+
* poster families (the same builders `coverArt` picks from, at hero size);
|
|
117
|
+
* the older names (`mesh`, `geometric`, `diagonal`, `radial`, `dark`, `warm`)
|
|
118
|
+
* stay as aliases so no blueprint needs to change.
|
|
119
|
+
*/
|
|
120
|
+
export function heroArt(palette, variant = 'mesh', seed = 1) {
|
|
121
|
+
const c = toRgb(palette);
|
|
122
|
+
const tones = buildTones(c);
|
|
123
|
+
const rng = mulberry32(seed);
|
|
124
|
+
const family = HERO_FAMILY[variant];
|
|
125
|
+
let layers;
|
|
126
|
+
switch (family) {
|
|
127
|
+
case 'grid':
|
|
128
|
+
layers = gridNodeCover(c, tones, rng);
|
|
129
|
+
break;
|
|
130
|
+
case 'blocks':
|
|
131
|
+
layers = colourBlockCover(c, tones, rng);
|
|
132
|
+
break;
|
|
133
|
+
case 'bands':
|
|
134
|
+
layers = stripeBandCover(c, tones, rng);
|
|
135
|
+
break;
|
|
136
|
+
case 'rings':
|
|
137
|
+
layers = concentricCover(c, tones, rng);
|
|
138
|
+
break;
|
|
139
|
+
case 'ink':
|
|
140
|
+
layers = editorialMarkCover(c, tones, rng);
|
|
141
|
+
break;
|
|
142
|
+
default:
|
|
143
|
+
layers = archSunCover(c, tones, rng);
|
|
144
|
+
}
|
|
145
|
+
return { width: HERO_WIDTH, height: HERO_HEIGHT, seed, layers };
|
|
146
|
+
}
|
|
147
|
+
// ---------------------------------------------------------------- cover art
|
|
148
|
+
const COVER_WIDTH = 1200;
|
|
149
|
+
const COVER_HEIGHT = 800;
|
|
150
|
+
function colourBlockCover(c, tones, rng) {
|
|
151
|
+
const angle = rng() < 0.5 ? 0 : 90;
|
|
152
|
+
const threeTone = rng() < 0.5;
|
|
153
|
+
const colors = threeTone ? [tones.pale, c.accent, tones.hue2] : [tones.pale, c.accent];
|
|
154
|
+
const accentCenter = [0.24 + rng() * 0.1, 0.72 + rng() * 0.06];
|
|
155
|
+
const accentIsDisc = rng() < 0.5;
|
|
156
|
+
return [
|
|
157
|
+
{ kind: 'bands', angle, colors, count: colors.length },
|
|
158
|
+
accentIsDisc
|
|
159
|
+
? { kind: 'disc', center: accentCenter, radius: 0.09, color: tones.deep, alpha: 1 }
|
|
160
|
+
: {
|
|
161
|
+
kind: 'rect',
|
|
162
|
+
center: accentCenter,
|
|
163
|
+
width: 0.16,
|
|
164
|
+
height: 0.16,
|
|
165
|
+
radius: 0.02,
|
|
166
|
+
color: tones.deep,
|
|
167
|
+
alpha: 1,
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
function gridNodeCover(c, tones, rng) {
|
|
172
|
+
const a = [0.28 + rng() * 0.08, 0.32 + rng() * 0.08];
|
|
173
|
+
const b = [0.6 + rng() * 0.08, 0.5];
|
|
174
|
+
const d = [0.46 + rng() * 0.08, 0.76 + rng() * 0.06];
|
|
175
|
+
return [
|
|
176
|
+
{ kind: 'fill', color: c.bg },
|
|
177
|
+
{ kind: 'dots', spacing: 0.05, radius: 0.006, color: c.border, alpha: 0.55 },
|
|
178
|
+
segment(a, b, COVER_WIDTH, COVER_HEIGHT, 0.004, c.mutedFg, 0.45),
|
|
179
|
+
segment(b, d, COVER_WIDTH, COVER_HEIGHT, 0.004, c.mutedFg, 0.45),
|
|
180
|
+
{ kind: 'disc', center: b, radius: 0.045, color: c.accent, alpha: 1 },
|
|
181
|
+
{ kind: 'disc', center: a, radius: 0.02, color: tones.hue1, alpha: 1 },
|
|
182
|
+
{ kind: 'disc', center: d, radius: 0.02, color: tones.deep, alpha: 1 },
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
function stripeBandCover(_c, tones, rng) {
|
|
186
|
+
const angles = [0, 45, 90, 135];
|
|
187
|
+
const angle = angles[Math.floor(rng() * angles.length) % angles.length] ?? 0;
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
kind: 'bands',
|
|
191
|
+
angle,
|
|
192
|
+
colors: [tones.hue2, tones.hue1, tones.deep],
|
|
193
|
+
count: 3 + Math.floor(rng() * 2),
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
function concentricCover(_c, tones, rng) {
|
|
198
|
+
const center = [0.62 + rng() * 0.08, 0.42 + rng() * 0.08];
|
|
199
|
+
return [
|
|
200
|
+
{ kind: 'fill', color: tones.pale },
|
|
201
|
+
{ kind: 'ring', center, innerRadius: 0.16, outerRadius: 0.176, color: tones.deep, alpha: 1 },
|
|
202
|
+
{ kind: 'ring', center, innerRadius: 0.23, outerRadius: 0.242, color: tones.hue1, alpha: 1 },
|
|
203
|
+
{
|
|
204
|
+
kind: 'disc',
|
|
205
|
+
center: [0.24 + rng() * 0.08, 0.74],
|
|
206
|
+
radius: 0.07,
|
|
207
|
+
color: tones.deep,
|
|
208
|
+
alpha: 1,
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
function editorialMarkCover(c, _tones, rng) {
|
|
213
|
+
const markKind = Math.floor(rng() * 3) % 3;
|
|
214
|
+
let mark;
|
|
215
|
+
if (markKind === 0) {
|
|
216
|
+
mark = {
|
|
217
|
+
kind: 'polygon',
|
|
218
|
+
center: [0.7, 0.62],
|
|
219
|
+
radius: 0.18,
|
|
220
|
+
sides: 3,
|
|
221
|
+
rotation: 0,
|
|
222
|
+
color: c.accent,
|
|
223
|
+
alpha: 1,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
else if (markKind === 1) {
|
|
227
|
+
// Centred exactly on the bottom edge: only the top half of the disc is on-canvas — a semicircle for free.
|
|
228
|
+
mark = { kind: 'disc', center: [0.72, 1], radius: 0.32, color: c.accent, alpha: 1 };
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Centred exactly on a corner: only one quarter of the disc is on-canvas.
|
|
232
|
+
mark = { kind: 'disc', center: [1, 1], radius: 0.4, color: c.accent, alpha: 1 };
|
|
233
|
+
}
|
|
234
|
+
return [
|
|
235
|
+
{ kind: 'fill', color: c.bg },
|
|
236
|
+
segment([0.12, 0.18], [0.5, 0.18], COVER_WIDTH, COVER_HEIGHT, 0.004, c.fg, 0.8),
|
|
237
|
+
mark,
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
function isometricStackCover(c, tones, rng) {
|
|
241
|
+
const shadow = mix(c.fg, tones.pale, 0.7);
|
|
242
|
+
const originX = 0.4 + rng() * 0.06;
|
|
243
|
+
const originY = 0.4 + rng() * 0.06;
|
|
244
|
+
const step = 0.12 + rng() * 0.03;
|
|
245
|
+
const tilt = rng() < 0.5 ? 1 : -1;
|
|
246
|
+
const positions = [
|
|
247
|
+
[originX, originY],
|
|
248
|
+
[originX + step, originY + step * 0.85],
|
|
249
|
+
[originX + step * 2, originY + step * 1.7],
|
|
250
|
+
];
|
|
251
|
+
const palette = [tones.hue2, c.accent, tones.deep];
|
|
252
|
+
// A seed-driven rotation of which colour lands on which card, so two
|
|
253
|
+
// seeds landing on this family don't just shift position — they read as
|
|
254
|
+
// genuinely different stacks.
|
|
255
|
+
const shift = Math.floor(rng() * palette.length);
|
|
256
|
+
const colors = palette.map((_, i) => palette[(i + shift) % palette.length] ?? c.accent);
|
|
257
|
+
const layers = [{ kind: 'fill', color: tones.pale }];
|
|
258
|
+
positions.forEach((pos, i) => {
|
|
259
|
+
const rotation = tilt * (i - 1) * (4 + rng() * 4);
|
|
260
|
+
layers.push({
|
|
261
|
+
kind: 'rect',
|
|
262
|
+
center: [pos[0] + 0.015, pos[1] + 0.02],
|
|
263
|
+
width: 0.34,
|
|
264
|
+
height: 0.22,
|
|
265
|
+
radius: 0.02,
|
|
266
|
+
rotation,
|
|
267
|
+
color: shadow,
|
|
268
|
+
alpha: 0.3,
|
|
269
|
+
}, {
|
|
270
|
+
kind: 'rect',
|
|
271
|
+
center: pos,
|
|
272
|
+
width: 0.34,
|
|
273
|
+
height: 0.22,
|
|
274
|
+
radius: 0.02,
|
|
275
|
+
rotation,
|
|
276
|
+
color: colors[i] ?? c.accent,
|
|
277
|
+
alpha: 1,
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
return layers;
|
|
281
|
+
}
|
|
282
|
+
function archSunCover(c, tones, rng) {
|
|
283
|
+
const center = [0.42 + rng() * 0.16, 0.5 + rng() * 0.12];
|
|
284
|
+
const radius = 0.18 + rng() * 0.08;
|
|
285
|
+
const sunColor = rng() < 0.5 ? c.accent : tones.hue1;
|
|
286
|
+
const layers = [
|
|
287
|
+
{ kind: 'fill', color: tones.warm },
|
|
288
|
+
{ kind: 'disc', center, radius, color: sunColor, alpha: 1 },
|
|
289
|
+
];
|
|
290
|
+
if (rng() < 0.7) {
|
|
291
|
+
layers.push({
|
|
292
|
+
kind: 'ring',
|
|
293
|
+
center,
|
|
294
|
+
innerRadius: radius + 0.04,
|
|
295
|
+
outerRadius: radius + 0.05,
|
|
296
|
+
color: tones.deep,
|
|
297
|
+
alpha: 0.6,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// The ground band crops the sun at ~60% of its radius below centre —
|
|
301
|
+
// enough of an arch to read as sunrise/sunset, never the full disc.
|
|
302
|
+
const groundHeight = 0.5;
|
|
303
|
+
const horizonY = center[1] + radius * 0.6;
|
|
304
|
+
layers.push({
|
|
305
|
+
kind: 'rect',
|
|
306
|
+
center: [0.5, horizonY + groundHeight / 2],
|
|
307
|
+
width: 1.6,
|
|
308
|
+
height: groundHeight,
|
|
309
|
+
color: tones.deep,
|
|
310
|
+
alpha: 1,
|
|
311
|
+
});
|
|
312
|
+
return layers;
|
|
313
|
+
}
|
|
314
|
+
function checkerCover(c, rng) {
|
|
315
|
+
return [
|
|
316
|
+
{ kind: 'fill', color: c.bg },
|
|
317
|
+
{
|
|
318
|
+
kind: 'checker',
|
|
319
|
+
center: [0.66, 0.5],
|
|
320
|
+
width: 0.9,
|
|
321
|
+
height: 1.05,
|
|
322
|
+
cell: 0.045,
|
|
323
|
+
rotation: rng() < 0.5 ? 0 : 8,
|
|
324
|
+
color: c.accent,
|
|
325
|
+
alpha: 0.85,
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
}
|
|
329
|
+
function duotoneBlobCover(tones, rng) {
|
|
330
|
+
const jitter = () => (rng() - 0.5) * 0.05;
|
|
331
|
+
return [
|
|
332
|
+
{ kind: 'fill', color: tones.pale },
|
|
333
|
+
{
|
|
334
|
+
kind: 'blob',
|
|
335
|
+
points: [
|
|
336
|
+
{ at: [0.32 + jitter(), 0.38 + jitter()], radius: 0.28 },
|
|
337
|
+
{ at: [0.5 + jitter(), 0.3 + jitter()], radius: 0.22 },
|
|
338
|
+
{ at: [0.4 + jitter(), 0.55 + jitter()], radius: 0.2 },
|
|
339
|
+
],
|
|
340
|
+
smoothing: 0.09,
|
|
341
|
+
color: tones.hue1,
|
|
342
|
+
alpha: 1,
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
kind: 'blob',
|
|
346
|
+
points: [
|
|
347
|
+
{ at: [0.68 + jitter(), 0.62 + jitter()], radius: 0.26 },
|
|
348
|
+
{ at: [0.82 + jitter(), 0.7 + jitter()], radius: 0.2 },
|
|
349
|
+
],
|
|
350
|
+
smoothing: 0.08,
|
|
351
|
+
color: tones.deep,
|
|
352
|
+
alpha: 1,
|
|
353
|
+
},
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
const COVER_FAMILIES = [
|
|
357
|
+
colourBlockCover,
|
|
358
|
+
gridNodeCover,
|
|
359
|
+
stripeBandCover,
|
|
360
|
+
concentricCover,
|
|
361
|
+
editorialMarkCover,
|
|
362
|
+
isometricStackCover,
|
|
363
|
+
archSunCover,
|
|
364
|
+
(c, _tones, rng) => checkerCover(c, rng),
|
|
365
|
+
(_c, tones, rng) => duotoneBlobCover(tones, rng),
|
|
366
|
+
];
|
|
367
|
+
/** A family of ≥8 visibly different, flat layouts from one palette, picked deterministically by `seed` (D5: colour blocks, grid & node, stripe bands, concentric rings, editorial marks, isometric stacks, arch & sun, checker/half-tone, duotone blobs). */
|
|
368
|
+
export function coverArt(palette, seed = 1) {
|
|
369
|
+
const c = toRgb(palette);
|
|
370
|
+
const tones = buildTones(c);
|
|
371
|
+
const rng = mulberry32(seed);
|
|
372
|
+
// The family is picked by the seed itself, not by the first random draw:
|
|
373
|
+
// a blueprint seeds its covers with consecutive seeds (1, 2, 3…), and a
|
|
374
|
+
// random pick sent three of eight blog posts to the same "arch & sun"
|
|
375
|
+
// frame. Consecutive seeds now walk the families in order — eight
|
|
376
|
+
// covers, eight different layouts — while `rng` still varies each one.
|
|
377
|
+
const familyIndex = ((Math.floor(seed) % COVER_FAMILIES.length) + COVER_FAMILIES.length) % COVER_FAMILIES.length;
|
|
378
|
+
const build = COVER_FAMILIES[familyIndex];
|
|
379
|
+
const layers = build(c, tones, rng);
|
|
380
|
+
return { width: COVER_WIDTH, height: COVER_HEIGHT, seed, layers };
|
|
381
|
+
}
|
|
382
|
+
// ---------------------------------------------------------------- avatar
|
|
383
|
+
const AVATAR_SIZE = 600;
|
|
384
|
+
/** A 600×600 abstract "person" mark: a disc (head) over a disc (shoulders, cropped by the frame edge) on a flat companion-tone field — never a real photo of a person. */
|
|
385
|
+
export function avatarArt(palette, seed = 1) {
|
|
386
|
+
const c = toRgb(palette);
|
|
387
|
+
const tones = buildTones(c);
|
|
388
|
+
const rng = mulberry32(seed);
|
|
389
|
+
const field = rng() < 0.5 ? tones.pale : mix(c.muted, tones.hue2, 0.25);
|
|
390
|
+
const mark = rng() < 0.5 ? c.accent : tones.deep;
|
|
391
|
+
const layers = [
|
|
392
|
+
{ kind: 'fill', color: field },
|
|
393
|
+
{ kind: 'disc', center: [0.5, 1.08], radius: 0.52, color: mark, alpha: 1 },
|
|
394
|
+
{ kind: 'disc', center: [0.5, 0.4], radius: 0.2, color: mark, alpha: 1 },
|
|
395
|
+
];
|
|
396
|
+
if (rng() < 0.5) {
|
|
397
|
+
layers.push({
|
|
398
|
+
kind: 'ring',
|
|
399
|
+
center: [0.5, 0.5],
|
|
400
|
+
innerRadius: 0.42,
|
|
401
|
+
outerRadius: 0.432,
|
|
402
|
+
color: c.border,
|
|
403
|
+
alpha: 0.7,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return { width: AVATAR_SIZE, height: AVATAR_SIZE, seed, layers };
|
|
407
|
+
}
|
|
408
|
+
// ---------------------------------------------------------------- logo
|
|
409
|
+
const LOGO_WIDTH = 400;
|
|
410
|
+
const LOGO_HEIGHT = 160;
|
|
411
|
+
/** A 400×160 neutral, abstract wordmark stand-in — a geometric mark plus two bars, always in flat mid-grey so it reads as a client logo in a logo strip, never a real brand. 8 distinct marks across seeds. */
|
|
412
|
+
export function logoArt(seed = 1) {
|
|
413
|
+
const rng = mulberry32(seed);
|
|
414
|
+
const grey = { r: 0.42, g: 0.44, b: 0.47 };
|
|
415
|
+
const markKind = Math.floor(rng() * 8) % 8;
|
|
416
|
+
const center = [0.16, 0.5];
|
|
417
|
+
let mark;
|
|
418
|
+
switch (markKind) {
|
|
419
|
+
case 0:
|
|
420
|
+
mark = {
|
|
421
|
+
kind: 'polygon',
|
|
422
|
+
center,
|
|
423
|
+
radius: 0.24,
|
|
424
|
+
sides: 3,
|
|
425
|
+
rotation: 0,
|
|
426
|
+
color: grey,
|
|
427
|
+
alpha: 0.85,
|
|
428
|
+
};
|
|
429
|
+
break;
|
|
430
|
+
case 1:
|
|
431
|
+
mark = {
|
|
432
|
+
kind: 'ring',
|
|
433
|
+
center,
|
|
434
|
+
innerRadius: 0.16,
|
|
435
|
+
outerRadius: 0.24,
|
|
436
|
+
color: grey,
|
|
437
|
+
alpha: 0.85,
|
|
438
|
+
};
|
|
439
|
+
break;
|
|
440
|
+
case 2:
|
|
441
|
+
mark = {
|
|
442
|
+
kind: 'rect',
|
|
443
|
+
center,
|
|
444
|
+
width: 0.32,
|
|
445
|
+
height: 0.32,
|
|
446
|
+
radius: 0.06,
|
|
447
|
+
rotation: 8,
|
|
448
|
+
color: grey,
|
|
449
|
+
alpha: 0.85,
|
|
450
|
+
};
|
|
451
|
+
break;
|
|
452
|
+
case 3:
|
|
453
|
+
mark = {
|
|
454
|
+
kind: 'polygon',
|
|
455
|
+
center,
|
|
456
|
+
radius: 0.24,
|
|
457
|
+
sides: 6,
|
|
458
|
+
rotation: 0,
|
|
459
|
+
color: grey,
|
|
460
|
+
alpha: 0.85,
|
|
461
|
+
};
|
|
462
|
+
break;
|
|
463
|
+
case 4:
|
|
464
|
+
mark = {
|
|
465
|
+
kind: 'rect',
|
|
466
|
+
center,
|
|
467
|
+
width: 0.3,
|
|
468
|
+
height: 0.3,
|
|
469
|
+
radius: 0.02,
|
|
470
|
+
rotation: 45,
|
|
471
|
+
color: grey,
|
|
472
|
+
alpha: 0.85,
|
|
473
|
+
};
|
|
474
|
+
break;
|
|
475
|
+
case 5:
|
|
476
|
+
mark = { kind: 'disc', center, radius: 0.22, color: grey, alpha: 0.85 };
|
|
477
|
+
break;
|
|
478
|
+
case 6:
|
|
479
|
+
mark = {
|
|
480
|
+
kind: 'polygon',
|
|
481
|
+
center,
|
|
482
|
+
radius: 0.24,
|
|
483
|
+
sides: 3,
|
|
484
|
+
rotation: 90,
|
|
485
|
+
color: grey,
|
|
486
|
+
alpha: 0.85,
|
|
487
|
+
};
|
|
488
|
+
break;
|
|
489
|
+
default:
|
|
490
|
+
mark = { kind: 'disc', center, radius: 0.22, color: grey, alpha: 0.85 };
|
|
491
|
+
}
|
|
492
|
+
const layers = [{ kind: 'fill', color: WHITE }, mark];
|
|
493
|
+
if (markKind === 7) {
|
|
494
|
+
// A white bar crops the disc into a "circle + bar" mark distinct from case 5's plain disc.
|
|
495
|
+
layers.push({
|
|
496
|
+
kind: 'rect',
|
|
497
|
+
center,
|
|
498
|
+
width: 0.07,
|
|
499
|
+
height: 0.32,
|
|
500
|
+
color: WHITE,
|
|
501
|
+
alpha: 1,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
layers.push({
|
|
505
|
+
kind: 'rect',
|
|
506
|
+
center: [0.52, 0.42],
|
|
507
|
+
width: 0.5,
|
|
508
|
+
height: 0.14,
|
|
509
|
+
radius: 0.02,
|
|
510
|
+
color: grey,
|
|
511
|
+
alpha: 0.55,
|
|
512
|
+
}, {
|
|
513
|
+
kind: 'rect',
|
|
514
|
+
center: [0.46, 0.62],
|
|
515
|
+
width: 0.36,
|
|
516
|
+
height: 0.1,
|
|
517
|
+
radius: 0.02,
|
|
518
|
+
color: grey,
|
|
519
|
+
alpha: 0.35,
|
|
520
|
+
});
|
|
521
|
+
return { width: LOGO_WIDTH, height: LOGO_HEIGHT, seed, layers };
|
|
522
|
+
}
|
|
523
|
+
// ---------------------------------------------------------------- product
|
|
524
|
+
const PRODUCT_SIZE = 1000;
|
|
525
|
+
/** A 1000×1000 centred, flat stylised object — a store's demo catalogue. Hard offset shadows (a darker solid shape, never blurred), a solid or two-tone backdrop, a floor line, one flat highlight shape per object. 6 shapes across seeds. */
|
|
526
|
+
export function productArt(palette, seed = 1) {
|
|
527
|
+
const c = toRgb(palette);
|
|
528
|
+
const tones = buildTones(c);
|
|
529
|
+
const rng = mulberry32(seed);
|
|
530
|
+
const objectColor = rng() < 0.5 ? c.accent : tones.deep;
|
|
531
|
+
const shape = Math.floor(rng() * 6) % 6;
|
|
532
|
+
const backdrop = rng() < 0.5 ? c.muted : tones.pale;
|
|
533
|
+
const twoTone = rng() < 0.5;
|
|
534
|
+
const shadowColor = mix(c.fg, backdrop, 0.55);
|
|
535
|
+
const layers = twoTone
|
|
536
|
+
? [{ kind: 'bands', angle: 90, colors: [backdrop, darken(backdrop, 0.06)], count: 2 }]
|
|
537
|
+
: [{ kind: 'fill', color: backdrop }];
|
|
538
|
+
layers.push(segment([0.14, 0.78], [0.86, 0.78], PRODUCT_SIZE, PRODUCT_SIZE, 0.004, c.border, 0.8));
|
|
539
|
+
switch (shape) {
|
|
540
|
+
case 0: {
|
|
541
|
+
// Bottle/capsule.
|
|
542
|
+
layers.push({
|
|
543
|
+
kind: 'rect',
|
|
544
|
+
center: [0.52, 0.55],
|
|
545
|
+
width: 0.22,
|
|
546
|
+
height: 0.58,
|
|
547
|
+
radius: 0.11,
|
|
548
|
+
color: shadowColor,
|
|
549
|
+
alpha: 0.3,
|
|
550
|
+
}, {
|
|
551
|
+
kind: 'rect',
|
|
552
|
+
center: [0.5, 0.53],
|
|
553
|
+
width: 0.22,
|
|
554
|
+
height: 0.58,
|
|
555
|
+
radius: 0.11,
|
|
556
|
+
color: objectColor,
|
|
557
|
+
alpha: 1,
|
|
558
|
+
}, {
|
|
559
|
+
kind: 'rect',
|
|
560
|
+
center: [0.5, 0.22],
|
|
561
|
+
width: 0.09,
|
|
562
|
+
height: 0.1,
|
|
563
|
+
radius: 0.02,
|
|
564
|
+
color: objectColor,
|
|
565
|
+
alpha: 1,
|
|
566
|
+
});
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case 1: {
|
|
570
|
+
// Box.
|
|
571
|
+
layers.push({
|
|
572
|
+
kind: 'rect',
|
|
573
|
+
center: [0.53, 0.55],
|
|
574
|
+
width: 0.42,
|
|
575
|
+
height: 0.42,
|
|
576
|
+
radius: 0.03,
|
|
577
|
+
color: shadowColor,
|
|
578
|
+
alpha: 0.3,
|
|
579
|
+
}, {
|
|
580
|
+
kind: 'rect',
|
|
581
|
+
center: [0.5, 0.52],
|
|
582
|
+
width: 0.42,
|
|
583
|
+
height: 0.42,
|
|
584
|
+
radius: 0.03,
|
|
585
|
+
color: objectColor,
|
|
586
|
+
alpha: 1,
|
|
587
|
+
}, segment([0.5, 0.31], [0.5, 0.73], PRODUCT_SIZE, PRODUCT_SIZE, 0.006, lighten(objectColor, 0.3), 0.6));
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
case 2: {
|
|
591
|
+
// Sphere, flattened, with a crescent highlight (a lighter disc partly re-covered by an object-coloured disc).
|
|
592
|
+
const radius = 0.28;
|
|
593
|
+
layers.push({ kind: 'disc', center: [0.53, 0.55], radius, color: shadowColor, alpha: 0.3 }, { kind: 'disc', center: [0.5, 0.52], radius, color: objectColor, alpha: 1 }, {
|
|
594
|
+
kind: 'disc',
|
|
595
|
+
center: [0.5 - radius * 0.35, 0.52 - radius * 0.35],
|
|
596
|
+
radius: radius * 0.36,
|
|
597
|
+
color: lighten(objectColor, 0.4),
|
|
598
|
+
alpha: 1,
|
|
599
|
+
}, {
|
|
600
|
+
kind: 'disc',
|
|
601
|
+
center: [0.5 - radius * 0.22, 0.52 - radius * 0.22],
|
|
602
|
+
radius: radius * 0.3,
|
|
603
|
+
color: objectColor,
|
|
604
|
+
alpha: 1,
|
|
605
|
+
});
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
case 3: {
|
|
609
|
+
// Ring.
|
|
610
|
+
layers.push({
|
|
611
|
+
kind: 'ring',
|
|
612
|
+
center: [0.53, 0.55],
|
|
613
|
+
innerRadius: 0.2,
|
|
614
|
+
outerRadius: 0.3,
|
|
615
|
+
color: shadowColor,
|
|
616
|
+
alpha: 0.3,
|
|
617
|
+
}, {
|
|
618
|
+
kind: 'ring',
|
|
619
|
+
center: [0.5, 0.52],
|
|
620
|
+
innerRadius: 0.2,
|
|
621
|
+
outerRadius: 0.3,
|
|
622
|
+
color: objectColor,
|
|
623
|
+
alpha: 1,
|
|
624
|
+
});
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
case 4: {
|
|
628
|
+
// Stacked cards.
|
|
629
|
+
const positions = [
|
|
630
|
+
[0.44, 0.62],
|
|
631
|
+
[0.5, 0.52],
|
|
632
|
+
[0.56, 0.42],
|
|
633
|
+
];
|
|
634
|
+
const colors = [tones.hue2, tones.hue1, objectColor];
|
|
635
|
+
positions.forEach((pos, i) => {
|
|
636
|
+
const rotation = (i - 1) * 6;
|
|
637
|
+
layers.push({
|
|
638
|
+
kind: 'rect',
|
|
639
|
+
center: [pos[0] + 0.02, pos[1] + 0.03],
|
|
640
|
+
width: 0.34,
|
|
641
|
+
height: 0.22,
|
|
642
|
+
radius: 0.02,
|
|
643
|
+
rotation,
|
|
644
|
+
color: shadowColor,
|
|
645
|
+
alpha: 0.25,
|
|
646
|
+
}, {
|
|
647
|
+
kind: 'rect',
|
|
648
|
+
center: pos,
|
|
649
|
+
width: 0.34,
|
|
650
|
+
height: 0.22,
|
|
651
|
+
radius: 0.02,
|
|
652
|
+
rotation,
|
|
653
|
+
color: colors[i] ?? objectColor,
|
|
654
|
+
alpha: 1,
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
default: {
|
|
660
|
+
// Hexagonal tile.
|
|
661
|
+
layers.push({
|
|
662
|
+
kind: 'polygon',
|
|
663
|
+
center: [0.53, 0.55],
|
|
664
|
+
radius: 0.28,
|
|
665
|
+
sides: 6,
|
|
666
|
+
rotation: 0,
|
|
667
|
+
color: shadowColor,
|
|
668
|
+
alpha: 0.3,
|
|
669
|
+
}, {
|
|
670
|
+
kind: 'polygon',
|
|
671
|
+
center: [0.5, 0.52],
|
|
672
|
+
radius: 0.28,
|
|
673
|
+
sides: 6,
|
|
674
|
+
rotation: 0,
|
|
675
|
+
color: objectColor,
|
|
676
|
+
alpha: 1,
|
|
677
|
+
}, {
|
|
678
|
+
kind: 'polygon',
|
|
679
|
+
center: [0.5, 0.52],
|
|
680
|
+
radius: 0.12,
|
|
681
|
+
sides: 6,
|
|
682
|
+
rotation: 0,
|
|
683
|
+
color: lighten(objectColor, 0.3),
|
|
684
|
+
alpha: 1,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return { width: PRODUCT_SIZE, height: PRODUCT_SIZE, seed, layers };
|
|
689
|
+
}
|
|
690
|
+
//# sourceMappingURL=compositions.js.map
|