index.jsx
5.1 kB · jsx · 103 lines
1import { useMemo, useState } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { mount, Page, Row, Slider, Btn, Stats, Stat } from '../../lib/app.jsx';4import { Grid, Markup } from '../../lib/draw.jsx';5import { useSeeds } from '../../lib/select.jsx';6import { useQuery, share } from '../../lib/query.js';78const m = await ready();9const colors = () => [ink.blue, ink.orange, ink.yellow, ink.green, ink.pink];10const COUNTS = m.counting_sequence(4).join(', ');11const BASE3 = m.baseq_sequence(3, 2).join(', ');12const WORLD = { 2: JSON.parse(m.universe(2)), 3: JSON.parse(m.universe(3)) };13const FIRST = { 2: '7', 3: '23' };14const BUDGET = 60000;1516const tint = (design) => colors()[design.degree % 5];1718const drawn = (seed, designs) => designs[m.random_between(seed, [0], [designs.length - 1])[0]].code;1920function thumb(design, dimension) {21 const label = `design ${design.code}, ${design.anf}`;22 if (dimension === 2) return <Grid grid={m.two_grid(design.code, 3, 2, 0, 2)} on={tint(design)} className="" role="img" aria-label={label} />;23 try {24 return <Markup svg={m.hex_svg(design.code, 3, 1, 2, 'iso', 6)} role="img" aria-label={label} />;25 } catch {26 return <Markup svg='<svg viewBox="0 0 1 1"></svg>' role="img" aria-label={label} />;27 }28}2930function grown(design, dimension, level) {31 const label = `${design.name} grown to level ${level}`;32 if (dimension === 2) return <Grid grid={m.two_grid(design.code, 3, level, 0, 2)} on={tint(design)} style={{ maxWidth: 486 }} role="img" aria-label={label} />;33 try {34 return <Markup svg={m.hex_svg(design.code, 3, level, 2, 'iso', level === 3 ? 2 : 8)} role="img" aria-label={label} />;35 } catch (error) {36 return <div>{String(error.message ?? error)}</div>;37 }38}3940function App() {41 const s = useSeeds();42 const [pick, set] = useQuery({ d: 2 });43 const dimension = pick.d === 3 ? 3 : 2;44 const world = WORLD[dimension];45 const [level, setLevel] = useState(3);46 const [code, setCode] = useState(() => (s.get() ? drawn(s.get(), world.designs) : FIRST[dimension]));47 const design = world.designs.find((one) => one.code === code) ?? world.designs[0];48 const top = m.level_cap(3, dimension, BUDGET);49 const grow = Math.min(level, top);50 const cards = useMemo(() => world.designs.map((one) => [one, thumb(one, dimension)]), [dimension]);5152 const swap = (d) => {53 set({ d });54 setCode(FIRST[d]);55 setLevel(Math.min(level, m.level_cap(3, d, BUDGET)));56 };5758 const controls = (59 <Row>60 <div className="tabs" role="group" aria-label="Dimension">61 <Btn on={dimension === 2} onClick={() => swap(2)}>plane</Btn>62 <Btn on={dimension === 3} onClick={() => swap(3)}>cube</Btn>63 </div>64 <Btn onClick={() => setCode(drawn(s.next(), world.designs))}>Randomize</Btn>65 <Slider label="level" value={grow} min={1} max={top} onChange={setLevel} />66 {dimension === 3 && <a href={`../sponge${share({ code: design.code, number: 3, base: 2 })}`}>open in the sponge</a>}67 </Row>68 );6970 return (71 <Page crumb="universe" title="The universe of designs is a finite gallery" controls={controls}72 sub={<>A code is a bitmask over the corners of a hypercube. Rotations and reflections fold the codes into orbits, and one design per orbit is all there is: <span className="num">{COUNTS}</span> distinct designs in dimensions 1 to 4. Click one to grow it.</>}73 foot={<>The distinct counts are Burnside averages over the hyperoctahedral group; the gallery enumerates the orbits outright and the two agree. In base 3 the same count runs <span className="num">{BASE3}</span> for dimensions 1 and 2. The two moves this gallery enumerates, a rule on the corners and that rule substituted into itself, are written up in <a href="/research/core/">the core</a>.</>}>74 <p className="num dim">{world.distinct} designs from {world.total} codes</p>75 <div className="cards" role="group" aria-label="Designs">76 {cards.map(([one, art]) => (77 <div key={one.code} role="button" tabIndex={0} aria-pressed={one.code === design.code}78 className={one.code === design.code ? 'card on' : 'card'}79 onClick={() => setCode(one.code)}80 onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setCode(one.code); } }}>81 {art}82 <div className="code">{one.code}</div>83 <div className="name">{one.anf}</div>84 </div>85 ))}86 </div>87 <div className="panel" style={{ marginTop: 22 }}>88 <h2><span className="num">{design.name}</span></h2>89 {grown(design, dimension, grow)}90 <Stats>91 <Stat label="orbit">{design.orbit}</Stat>92 <Stat label="degree">{design.degree}</Stat>93 <Stat label="normal form">{design.anf}</Stat>94 <Stat label="filled">{m.fills(design.code, 3, dimension, grow, 2)} of {m.grid_total(3, dimension, grow)}</Stat>95 <Stat label="fill ratio">{m.ratio(design.code, 3, dimension, grow, 2).toFixed(4)}</Stat>96 <Stat label="dimension">{m.dimension(design.code, 3, dimension, 2).toFixed(4)}</Stat>97 </Stats>98 </div>99 </Page>100 );101}102103mount(<App />);