Skip to content

Commit 8bf1d3e

Browse files
authored
Merge pull request #142 from engmung/dev
Dev
2 parents 5c65db6 + 933386d commit 8bf1d3e

15 files changed

Lines changed: 681 additions & 25 deletions

File tree

12 KB
Binary file not shown.
-1.37 KB
Binary file not shown.
27.1 KB
Binary file not shown.

web/content/build.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Build your own.
3-
subtitle: Two enclosure paths, two electronics paths, one firmware.
3+
subtitle: Follow the guides to build your own Patternflow.
44
meta:
55
- label: Current guide
66
value: "3D print + custom PCB"

web/public/product_v2.jpg

2.05 MB
Loading

web/src/app/globals.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@
181181
line-height: 1.55;
182182
color: var(--ink-muted);
183183
margin: 0 0 12px;
184-
max-width: 34ch;
184+
max-width: 44ch;
185185
}
186186
.hero-cta-row {
187187
display: flex;

web/src/components/sections/Hero.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ export default function Hero() {
1111
<h1>
1212
<em className="wordmark">Patternflow</em>
1313
</h1>
14-
<div className="kicker">An open-source LED synthesizer played with the fingertips.</div>
14+
<div className="kicker" style={{ marginBottom: '24px' }}>An open-source LED synthesizer played with the fingertips.</div>
15+
<div style={{ marginBottom: '24px', overflow: 'hidden', border: '1px solid var(--pf-rule)' }}>
16+
<img
17+
src="/product_v2.jpg"
18+
alt="Patternflow physical device"
19+
style={{ width: '100%', height: 'auto', display: 'block' }}
20+
/>
21+
</div>
1522
<p className="lede">
16-
Interactive media art has felt expensive and exclusive.
17-
<br />
18-
Patternflow makes it something anyone can make.
19-
<br />
20-
<br />
2123
A reinterpretation of{" "}
2224
<a
2325
className="has-tip"
@@ -55,14 +57,12 @@ export default function Hero() {
5557
Patternflow puts creation in everyone&apos;s hands.
5658
<br />
5759
<br />
58-
So it&apos;s not a single device, but an open system
59-
<br />
60-
to build, code, and share your own light.
60+
<span style={{ fontWeight: 500 }}>Create and share your own light.</span>
6161
</p>
6262
<p className="hero-kit-note">
63-
All source files are on GitHub.
63+
All source files and guides are on GitHub.
6464
<br />
65-
Build one now, or join the kit waitlist.
65+
Build one now, or join the waitlist.
6666
</p>
6767
<div className="hero-cta-row" aria-label="Patternflow actions">
6868
<a

web/src/components/sections/PatternPanel.tsx

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ const INSTAGRAM_URL = 'https://www.instagram.com/patternflow.work/';
258258
export default function PatternPanel({ content }: PatternPanelProps) {
259259
const [mode, setMode] = useState<PatternMode>('create');
260260
const [activePresetId, setActivePresetId] = useState<string | null>(null);
261+
const [showAllPresets, setShowAllPresets] = useState(false);
261262
const activePatternId = useAppStore(state => state.activePatternId);
262263
const customJsCode = useAppStore(state => state.customJsCode);
263264
const setCustomJsCode = useAppStore(state => state.setCustomJsCode);
@@ -337,6 +338,17 @@ export default function PatternPanel({ content }: PatternPanelProps) {
337338
});
338339
};
339340

341+
const handleRandomPreset = () => {
342+
if (livePresets.length === 0) return;
343+
let nextPreset = livePresets[Math.floor(Math.random() * livePresets.length)];
344+
if (activePresetId && livePresets.length > 1) {
345+
while (nextPreset.id === activePresetId) {
346+
nextPreset = livePresets[Math.floor(Math.random() * livePresets.length)];
347+
}
348+
}
349+
handleLoadPreset(nextPreset.id);
350+
};
351+
340352
const handleCopyVariantPrompt = () => {
341353
navigator.clipboard.writeText(getVariantPrompt(customJsCode));
342354
captureEvent('copy_variants_prompt_clicked', {
@@ -495,18 +507,57 @@ export default function PatternPanel({ content }: PatternPanelProps) {
495507
/>
496508
<div className={styles.presetChips} aria-label="Live editor presets">
497509
<span className={styles.presetChipsLabel}>Try a preset</span>
498-
{livePresets.map((preset) => (
499-
<button
500-
key={preset.id}
501-
type="button"
502-
className={activePresetId === preset.id ? `${styles.presetChip} ${styles.active}` : styles.presetChip}
503-
aria-pressed={activePresetId === preset.id}
504-
onClick={() => handleLoadPreset(preset.id)}
505-
title={preset.desc}
506-
>
507-
{preset.name}
508-
</button>
509-
))}
510+
{!showAllPresets ? (
511+
<>
512+
<button
513+
type="button"
514+
className={styles.presetChip}
515+
onClick={handleRandomPreset}
516+
title="Select a random pattern preset"
517+
>
518+
🎲 Shuffle
519+
</button>
520+
<button
521+
type="button"
522+
className={styles.presetChip}
523+
onClick={() => setShowAllPresets(true)}
524+
title="Show all available presets"
525+
>
526+
Show All ({livePresets.length})
527+
</button>
528+
</>
529+
) : (
530+
<>
531+
<button
532+
type="button"
533+
className={styles.presetChip}
534+
onClick={handleRandomPreset}
535+
title="Select a random pattern preset"
536+
>
537+
🎲 Shuffle
538+
</button>
539+
<button
540+
type="button"
541+
className={styles.presetChip}
542+
onClick={() => setShowAllPresets(false)}
543+
title="Hide preset list"
544+
>
545+
Collapse
546+
</button>
547+
{livePresets.map((preset) => (
548+
<button
549+
key={preset.id}
550+
type="button"
551+
className={activePresetId === preset.id ? `${styles.presetChip} ${styles.active}` : styles.presetChip}
552+
aria-pressed={activePresetId === preset.id}
553+
onClick={() => handleLoadPreset(preset.id)}
554+
title={preset.desc}
555+
>
556+
{preset.name}
557+
</button>
558+
))}
559+
</>
560+
)}
510561
<span className={styles.presetChipsSeparator}>|</span>
511562
<span className={styles.presetChipsLabel}>More community patterns:</span>
512563
<a

web/src/lib/presets/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ import { preset as pattern_0530 } from "./pattern-0530";
3333
import { preset as pattern_0531 } from "./pattern-0531";
3434
import { preset as pattern_0601 } from "./pattern-0601";
3535
import { preset as pattern_0602 } from "./pattern-0602";
36+
import { preset as pattern_0609 } from "./pattern-0609";
37+
import { preset as pattern_0614 } from "./pattern-0614";
38+
import { preset as pattern_0614_2 } from "./pattern-0614-2";
39+
import { preset as pattern_0619 } from "./pattern-0619";
40+
import { preset as pattern_0622 } from "./pattern-0622";
41+
import { preset as pattern_0624 } from "./pattern-0624";
3642
import { preset as pattern_a_big_hit } from "./pattern-a-big-hit";
3743

3844
const presets: LivePreset[] = [
@@ -64,6 +70,12 @@ const presets: LivePreset[] = [
6470
pattern_0531,
6571
pattern_0601,
6672
pattern_0602,
73+
pattern_0609,
74+
pattern_0614,
75+
pattern_0614_2,
76+
pattern_0619,
77+
pattern_0622,
78+
pattern_0624,
6779
pattern_a_big_hit
6880
];
6981

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { LivePreset } from "./types";
2+
3+
export const preset: LivePreset = {
4+
id: "pattern-0609",
5+
num: 609,
6+
name: "0609",
7+
desc: "Molten Magma Heat-Map with cooling crusts",
8+
author: "Seunghun LEE",
9+
license: "CC-BY-SA-4.0",
10+
date: "2026-06-09",
11+
lineage: "AI generated and curated",
12+
code: `// Pattern: 0609
13+
// Author: Seunghun LEE
14+
// SPDX-License-Identifier: CC-BY-SA-4.0
15+
// Date: 2026-06-09
16+
// Lineage: AI generated and curated
17+
//
18+
// Molten Magma Heat-Map — Emulates thermal energy color bands where bright hotspots cooling down leave dark crusts.
19+
// Knob 1: Thermal Fluid Viscosity (0.0 to 1.0)
20+
// Knob 2: Boiling Flow Speed (0.1 to 10.0)
21+
// Knob 3: Hotspot Core Expansion (0.0 to 4.9)
22+
// Knob 4: Cool Crust Fracturing (0.0 to 1.0)
23+
24+
export function setup(params) {
25+
params.viscosity = 0.5;
26+
params.speed = 2.0;
27+
params.expansion = 2.0;
28+
params.crust = 0.4;
29+
params.timeAcc = 0.0;
30+
}
31+
32+
export function update(dt, input, params) {
33+
if (input && input.knobValues) {
34+
params.viscosity = input.knobValues[0];
35+
params.speed = input.knobValues[1];
36+
params.expansion = input.knobValues[2];
37+
params.crust = input.knobValues[3];
38+
}
39+
params.timeAcc += dt * params.speed;
40+
}
41+
42+
export function draw(display, params, time) {
43+
let w = display.width;
44+
let h = display.height;
45+
let t = params.timeAcc;
46+
47+
let visc = 0.02 + params.viscosity * 0.08;
48+
let coreShift = params.expansion - 2.5;
49+
50+
for (let y = 0; y < h; y++) {
51+
for (let x = 0; x < w; x++) {
52+
53+
// Multiple layers of wave signals create fluid thermal paths
54+
let n1 = Math.sin(x * visc + t) * Math.cos(y * visc - t);
55+
let n2 = Math.sin((x - w/2) * 0.05 - t * 0.4) * Math.sin((y - h/2) * 0.05 + t * 0.6);
56+
let n3 = Math.cos(Math.sqrt((x-w/2)*(x-w/2) + (y-h/2)*(y-h/2)) * 0.1 - t * 1.5);
57+
58+
let heatSum = (n1 + n2 * 0.7 + n3 * 0.5) / 2.2;
59+
heatSum = heatSum + coreShift * 0.3; // Manual shift balancing
60+
let temp = Math.max(0.0, Math.min(1.0, (heatSum + 1.0) * 0.5));
61+
62+
let r = 0, g = 0, b = 0;
63+
64+
// Heat map color palette step assignments
65+
if (temp > 0.85) {
66+
// Incandescent Superhot White Core
67+
r = 255; g = 255; b = 230;
68+
} else if (temp > 0.65) {
69+
// Liquid Yellow Plasma
70+
r = 255; g = 180 + Math.floor((temp - 0.65) * 375); b = 20;
71+
} else if (temp > 0.4) {
72+
// Flowing Viscous Orange-Red
73+
r = 220; g = Math.floor((temp - 0.4) * 700); b = 5;
74+
} else if (temp > 0.18) {
75+
// Deep Cooled Dormant Crimson
76+
r = 40 + Math.floor((temp - 0.18) * 800); g = 0; b = 0;
77+
} else {
78+
// Charcoal Rock Base
79+
r = 10; g = 5; b = 15;
80+
}
81+
82+
// Introduce cool surface fractures/cracking maps
83+
if (params.crust > 0.05) {
84+
let crackPattern = Math.sin(x * 1.5) * Math.cos(y * 1.5);
85+
if (crackPattern > 1.0 - params.crust && temp < 0.6) {
86+
// Reduce thermal radiation, exposing dark deep cracks
87+
r = Math.floor(r * 0.15);
88+
g = 0;
89+
b = Math.floor(b * 0.1);
90+
}
91+
}
92+
93+
display.setPixel(x, y, r, g, b);
94+
}
95+
}
96+
}`,
97+
};

0 commit comments

Comments
 (0)