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 />);