Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to Patternflow will be documented in this file, newest first

- **/build lets you pick the case, and the 3D view shows the one you pick.** The Build panel's switch (the official v3.9 case, Besoiobiy's printed case, SimonePDA's laser-cut one; `?case=` links to one) brings up that case's card, a remix's opening with its README on GitHub, and swaps the device in the preview for that case, with the same v3.9 board, DevKit and LED panel inside (and, on Besoiobiy's case, its own knob caps). SimonePDA's case comes in its two finishes, acrylic and MDF. Each model is made by `tools/case-models` from the case's own files in this repository (the guide's v3.9 case, Besoiobiy's STLs, SimonePDA's cutting layout) and sits beside the case's photos, `web/public/cases/<id>/model.glb`. The model the page loads first is 0.32 MB, where the old v3.0 one (`3dforweb.glb`, now removed) was 1.15 MB; a remix's model loads when it is picked, and the guide takes its LED panel from the official one.
- **/build has a fourth case, mbchars' horizontal desktop one, in the switch and in the 3D view.** Its card is its README's facts and the author's photos, and the switch is four tabs, two by two on a phone. `tools/case-models/mbchars.py` puts its model together from the remix's STLs, since no assembled file exists, and checks it against the README; in it the panel lies the long way (`ledTurn` in `placement.json`), the official knob is printed white (`knobLook`), and the orange is the one in the author's 3MF, `#F46B16` (`pla_orange`). The model is 0.38 MB and loads when the case is picked; the other three rebuild byte for byte. The case is 41 cm across, so on a desktop's tall view it is drawn smaller, by as little as keeps it clear of the arrows; on a phone all four cases, and on a desktop the three upright ones, are still drawn at the one scale. The build map's pin for the same build goes by the maker's name now, mbchars (it was extraplojo).
- **The 3D view on /build has arrows and a name.** On the Build tab, arrows either side of the device step through the cases, round and back to the first, as the build map's arrows step through builds; the switch, the card and `?case=` follow. Over the device is whose case it is, the name on its tab (Official, Besoiobiy, SimonePDA, mbchars), in the build map's label.
- **The preview's case is white, not beige, and the rest of it looks more like the photos.** The printed parts are a neutral white PLA (`#f4f4f2`, matte), where the guide's warm white had come along; the case front measures b\* +0.4 and L\* 89.8 where it was +1.7 and 82.5. The frame goes through the shoulder of the Neutral tone curve, which leaves what is under its knee as lit and rolls off what is brighter with its channels together, so a bright light no longer clips red and green first and leaves the white yellow. The light is made on the page from light panels, as on the guide's stage, instead of an environment map fetched from a CDN at run time. The LED panel is a grid of round LEDs on a black mask where it was solid blocks on grey (each LED becomes its whole cell once it is under about three pixels across, as on a phone), its sides and back are the panel's black frame, and what glowed before (the whites, and the bright yellows and cyans of a pattern from the editor) glows as before. The official knob is the printed knob's own profile drawn round, where it was a 32-sided prism. SimonePDA's clear acrylic is clear: the canvas is see-through, and every partly covered pixel used to reach the page far too bright, which drew the sheet frosted white; its three feet, which no photo shows on it, now come out only with the parts and on Assemble. Every case is drawn at one scale, so the LED panel is the same size in all three (the remixes drew it 3.5 to 4% smaller). The three models were rebuilt with `tools/case-models/build.sh`; the official one grows by 0.8 KB.

### Docs
Expand Down
Binary file added web/public/builds/zuperzuche/cat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/builds/zuperzuche/frog-pond.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/builds/zuperzuche/fuji.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions web/src/components/3d/HeroScene.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,37 @@
transition-delay: 0.25s;
}

/* Whose case is on screen, over the device on the Build tab: the build map's
name label (GlobeViewer.module.css, .maker) at the top of the frame, the
name alone on a highlight that hugs it. */

.title {
position: absolute;
top: 34px;
left: 0;
right: 0;
z-index: 10;
text-align: center;
pointer-events: none;
}

.titleName {
padding: 3px 11px;
background: rgba(13, 13, 13, 0.72);
color: var(--cream);
font-size: 44px;
font-weight: 500;
letter-spacing: -0.025em;
line-height: 1.28;
white-space: nowrap;
}

/* The hint (globals.css, .viewer-hint: 40px from the top) gives the top of
the frame to the title and sits under it. */
:global(.viewer-hint).hintUnderTitle {
top: 108px;
}

/* The arrows either side of the device on the Build tab, stepping through the
cases: the Inside globe's arrows (GlobeViewer.module.css), at the middle of
the frame, where the device is. */
Expand Down Expand Up @@ -73,6 +104,22 @@
font-size: 11px;
}

/* The viewer is a strip across the top of the screen here: the title and
the hint close up, clear of the device, at the build map's phone size. */
.title {
top: 14px;
}

.titleName {
padding: 2px 7px;
font-size: 21px;
line-height: 1.24;
}

:global(.viewer-hint).hintUnderTitle {
top: 50px;
}

.arrow svg {
width: 44px;
height: 44px;
Expand Down
17 changes: 15 additions & 2 deletions web/src/components/3d/HeroScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import patterns from './patterns';
import { useAppStore } from '@/store/useAppStore';
import { LedMatrixTexture } from './LedMatrixTexture';
import { LOGICAL_KNOB_TO_WEB_KNOB, knobUnitsPerTurn, webKnobRange } from '@/lib/pattern/controls';
import { BUILD_CASES, stepCase, writeCaseToUrl, type CaseId } from '@/components/sections/build-cases-data';
import { BUILD_CASES, findCase, stepCase, writeCaseToUrl, type CaseId } from '@/components/sections/build-cases-data';
import { captureEvent } from '@/lib/posthogEvents';
import { CASE_MODELS, DRACO_DECODER, type CaseFinish } from './caseModels';
import { preloadCaseModel } from './preloadCaseModel';
Expand Down Expand Up @@ -543,12 +543,25 @@ export default function HeroScene() {
captureEvent('build_case_selected', { case_id: id, interaction: 'click', surface: 'product_preview' });
};

// Over the device on the Build tab, whose case it is, in the build map's
// label: the name on its tab of the panel's switch, and nothing else. It
// changes with the pick, as the tab does, ahead of a model still loading
// (the note under the device says so), and reads Official for a case whose
// model did not load, since that is the one drawn.
const titled = homeTab === 'build' ? findCase(wanted) : undefined;

return (
<div id="three-canvas" style={{ width: '100%', height: '100%', position: 'relative' }}>

{titled && (
<div className={styles.title}>
<span className={styles.titleName}>{titled.tab}</span>
</div>
)}

{/* 조작 안내 문구 (최초 1회 조작 시 서서히 사라짐) */}
<div
className="viewer-hint"
className={`viewer-hint${titled ? ` ${styles.hintUnderTitle}` : ''}`}
style={{
position: 'absolute',
left: '0', width: '100%', textAlign: 'center',
Expand Down
17 changes: 17 additions & 0 deletions web/src/components/sections/InsideGlobe/builds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,23 @@ export const builds: Build[] = [
{ src: '/builds/mbchars/inside.jpg', alt: "Inside mbchars's case: the board in the orange control box beside the panel, with brass inserts at the corners" },
],
},
{
id: 'russia-zuperzuche',
title: 'The first print',
category: 'builds',
slug: 'zuperzuche-russia',
kind: 'build',
location: { lat: 55.7558, lng: 37.6173, label: 'Moscow, Russia' },
maker: 'ZuperZuche',
date: '2026-10',
description:
'Found by accident, with the PCBs ordered ten minutes later. A month on, the only thing missing was the case, so the maker bought a 3D printer and made it the first thing printed.',
images: [
{ src: '/builds/zuperzuche/fuji.jpg', alt: "ZuperZuche's white horizontal Patternflow in front of a modular synthesizer, with a pixel-art Mount Fuji at sunset on the panel" },
{ src: '/builds/zuperzuche/frog-pond.jpg', alt: "A pixel-art frog pond on the panel of ZuperZuche's Patternflow: lily pads and small flowers on teal water, seen from above" },
{ src: '/builds/zuperzuche/cat.jpg', alt: "A cat plush in the foreground, with ZuperZuche's Patternflow behind it glowing red, purple and blue" },
],
},
];

// One order for the text list and the globe's previous/next controls.
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/sections/InsideGlobe/groups.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ describe('the count beside the list', () => {
expect(named('Leeds, UK')).toBe('United Kingdom');
expect(named('United Kingdom')).toBe('United Kingdom');
expect(named('San Francisco, USA')).toBe('USA');
expect(countCountries(builds)).toBe(11);
expect(countCountries(builds)).toBe(12);
});
});

Expand Down
Loading