Line data Source code
1 : import * as React from 'react';
2 :
3 : import * as baseStyles from '../../ui/foundations/banner_page.module.css';
4 :
5 : import { useFlavorWobbleBrand } from '../../agent/wobble.js';
6 : import { DASHQL_BRAND, DBCOM_BRAND, OMEN_BRAND } from '@ankoh/dashql-ui/wobble/wobble_morph';
7 : import { CLOUD_DENSITIES, DEFAULT_LANDING_BACKDROP as DEFAULTS, type BackdropStats } from '@ankoh/dashql-ui/landing_backdrop/landing_backdrop';
8 : import { Button, ButtonSize, ButtonVariant } from '../../ui/foundations/button.js';
9 : import { SegmentedControl, SegmentedControlSize } from '../../ui/foundations/segmented_control.js';
10 : import { LandingBackdrop } from './landing_backdrop.js';
11 : import * as styles from './landing_backdrop_page.module.css';
12 :
13 0 : const BRANDS = [
14 0 : { label: 'DashQL', brand: DASHQL_BRAND },
15 0 : { label: 'Database.com', brand: DBCOM_BRAND },
16 0 : { label: 'Omen', brand: OMEN_BRAND },
17 0 : ];
18 :
19 : /// The sphere of dots that turns into the brand mark, with knobs to tune it
20 0 : function LandingBackdropPage(): React.ReactElement {
21 0 : const flavorBrand = useFlavorWobbleBrand();
22 0 : const [brand, setBrand] = React.useState(() => Math.max(0, BRANDS.findIndex(entry => entry.brand === flavorBrand)));
23 0 : const [period, setPeriod] = React.useState(DEFAULTS.period);
24 0 : const [contrast, setContrast] = React.useState(DEFAULTS.contrast);
25 0 : const [density, setDensity] = React.useState(0);
26 0 : const [particles, setParticles] = React.useState(DEFAULTS.particles);
27 0 : const [ringFps, setRingFps] = React.useState(DEFAULTS.ringFps);
28 0 : const [ringInk, setRingInk] = React.useState(DEFAULTS.ringInk);
29 0 : const [outerWobble, setOuterWobble] = React.useState(DEFAULTS.outerWobble);
30 0 : const [gap, setGap] = React.useState(DEFAULTS.gap);
31 0 : const [fly, setFly] = React.useState(DEFAULTS.fly);
32 0 : const [lightSeconds, setLightSeconds] = React.useState(DEFAULTS.lightSeconds);
33 0 : const [glow, setGlow] = React.useState(DEFAULTS.glow);
34 0 : const [logoDensity, setLogoDensity] = React.useState(DEFAULTS.density);
35 0 : const [sphereDensity, setSphereDensity] = React.useState(DEFAULTS.sphereDensity);
36 0 : const [ringDensity, setRingDensity] = React.useState(DEFAULTS.ringDensity);
37 0 : const [twinkleSeconds, setTwinkleSeconds] = React.useState(DEFAULTS.twinkleSeconds);
38 0 : const [wobble, setWobble] = React.useState(DEFAULTS.wobble);
39 0 : const [markWobble, setMarkWobble] = React.useState(DEFAULTS.markWobble);
40 0 : const [scale, setScale] = React.useState(DEFAULTS.scale);
41 0 : const [eyes, setEyes] = React.useState<boolean>(DEFAULTS.eyes);
42 0 : const [stats, setStats] = React.useState<BackdropStats | null>(null);
43 0 : const [motion, setMotion] = React.useState(() => !(globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false));
44 0 : const [controlsShown, setControlsShown] = React.useState(true);
45 :
46 0 : return (
47 0 : <div className={`${baseStyles.page} ${styles.page}`}>
48 0 : <LandingBackdrop brand={BRANDS[brand].brand} period={period} contrast={contrast} motion={motion} dots={CLOUD_DENSITIES[density]} wobble={wobble} markWobble={markWobble} scale={scale} eyes={eyes} particles={particles} ringFps={ringFps} ringInk={ringInk} outerWobble={outerWobble} gap={gap} fly={fly} lightSeconds={lightSeconds} glow={glow} density={logoDensity} sphereDensity={sphereDensity} ringDensity={ringDensity} twinkleSeconds={twinkleSeconds} onStats={setStats} />
49 0 : {controlsShown
50 : ? (
51 0 : <div className={styles.controls}>
52 0 : <h1 className={styles.title}>Landing Backdrop</h1>
53 0 : <label className={styles.knob}>
54 0 : <span className={styles.control_label}>Cycle</span>
55 0 : <input id="landing-backdrop-cycle" type="range" min={6} max={60} step={1} value={period}
56 0 : onChange={e => setPeriod(Number(e.target.value))} />
57 0 : <span className={styles.knob_value}>{period}s</span>
58 0 : </label>
59 0 : <label className={styles.knob}>
60 0 : <span className={styles.control_label}>Size</span>
61 0 : <input id="landing-backdrop-size" type="range" min={0.3} max={1.2} step={0.01} value={scale}
62 0 : onChange={e => setScale(Number(e.target.value))} />
63 0 : <span className={styles.knob_value}>{Math.round(scale * 100)}%</span>
64 0 : </label>
65 0 : <label className={styles.knob}>
66 0 : <span className={styles.control_label}>Wobble</span>
67 0 : <input id="landing-backdrop-wobble" type="range" min={0} max={0.2} step={0.005} value={wobble}
68 0 : onChange={e => setWobble(Number(e.target.value))} />
69 0 : <span className={styles.knob_value}>{wobble.toFixed(3)}</span>
70 0 : </label>
71 0 : <label className={styles.knob}>
72 0 : <span className={styles.control_label}>Logo</span>
73 0 : <input id="landing-backdrop-mark-wobble" type="range" min={0} max={0.12} step={0.005} value={markWobble}
74 0 : onChange={e => setMarkWobble(Number(e.target.value))} />
75 0 : <span className={styles.knob_value}>{markWobble.toFixed(3)}</span>
76 0 : </label>
77 0 : <label className={styles.knob}>
78 0 : <span className={styles.control_label}>Light</span>
79 0 : <input id="landing-backdrop-light" type="range" min={2} max={30} step={0.5} value={lightSeconds}
80 0 : onChange={e => setLightSeconds(Number(e.target.value))} />
81 0 : <span className={styles.knob_value}>{lightSeconds.toFixed(1)}s</span>
82 0 : </label>
83 0 : <label className={styles.knob}>
84 0 : <span className={styles.control_label}>Glow</span>
85 0 : <input id="landing-backdrop-glow" type="range" min={0} max={2} step={0.05} value={glow}
86 0 : onChange={e => setGlow(Number(e.target.value))} />
87 0 : <span className={styles.knob_value}>{glow.toFixed(2)}</span>
88 0 : </label>
89 0 : <label className={styles.knob}>
90 0 : <span className={styles.control_label}>Logo dots</span>
91 0 : <input id="landing-backdrop-density" type="range" min={0.05} max={1} step={0.01} value={logoDensity}
92 0 : onChange={e => setLogoDensity(Number(e.target.value))} />
93 0 : <span className={styles.knob_value}>{Math.round(logoDensity * 100)}%</span>
94 0 : </label>
95 0 : <label className={styles.knob}>
96 0 : <span className={styles.control_label}>Sphere</span>
97 0 : <input id="landing-backdrop-sphere-density" type="range" min={0.05} max={1} step={0.01} value={sphereDensity}
98 0 : onChange={e => setSphereDensity(Number(e.target.value))} />
99 0 : <span className={styles.knob_value}>{Math.round(sphereDensity * 100)}%</span>
100 0 : </label>
101 0 : <label className={styles.knob}>
102 0 : <span className={styles.control_label}>Ring</span>
103 0 : <input id="landing-backdrop-ring-density" type="range" min={0.05} max={1} step={0.01} value={ringDensity}
104 0 : onChange={e => setRingDensity(Number(e.target.value))} />
105 0 : <span className={styles.knob_value}>{Math.round(ringDensity * 100)}%</span>
106 0 : </label>
107 0 : <label className={styles.knob}>
108 0 : <span className={styles.control_label}>Twinkle</span>
109 0 : <input id="landing-backdrop-twinkle" type="range" min={0.5} max={15} step={0.1} value={twinkleSeconds}
110 0 : onChange={e => setTwinkleSeconds(Number(e.target.value))} />
111 0 : <span className={styles.knob_value}>{twinkleSeconds.toFixed(1)}s</span>
112 0 : </label>
113 0 : <label className={styles.knob}>
114 0 : <span className={styles.control_label}>Contrast</span>
115 0 : <input id="landing-backdrop-contrast" type="range" min={0.25} max={3} step={0.05} value={contrast}
116 0 : onChange={e => setContrast(Number(e.target.value))} />
117 0 : <span className={styles.knob_value}>{contrast.toFixed(2)}</span>
118 0 : </label>
119 0 : <div className={styles.control_row}>
120 0 : <span className={styles.control_label}>Dots</span>
121 0 : <SegmentedControl aria-label="Dots" size={SegmentedControlSize.Small} onChange={setDensity}>
122 0 : {CLOUD_DENSITIES.map((count, i) => (
123 0 : <SegmentedControl.Button key={count} selected={density === i}>{`${count / 1000}k`}</SegmentedControl.Button>
124 0 : ))}
125 0 : </SegmentedControl>
126 0 : <span className={styles.knob_value}>{stats == null ? '' : `${stats.fps} fps`}</span>
127 0 : </div>
128 0 : <label className={styles.knob}>
129 0 : <span className={styles.control_label}>Particles</span>
130 0 : <input id="landing-backdrop-particles" type="range" min={0} max={10000} step={100} value={particles}
131 0 : onChange={e => setParticles(Number(e.target.value))} />
132 0 : <span className={styles.knob_value}>{particles}</span>
133 0 : </label>
134 0 : <label className={styles.knob}>
135 0 : <span className={styles.control_label}>Ring ink</span>
136 0 : <input id="landing-backdrop-ring-ink" type="range" min={0.2} max={3} step={0.05} value={ringInk}
137 0 : onChange={e => setRingInk(Number(e.target.value))} />
138 0 : <span className={styles.knob_value}>{ringInk.toFixed(2)}</span>
139 0 : </label>
140 0 : <label className={styles.knob}>
141 0 : <span className={styles.control_label}>Ring fps</span>
142 0 : <input id="landing-backdrop-ring-fps" type="range" min={5} max={60} step={1} value={ringFps}
143 0 : onChange={e => setRingFps(Number(e.target.value))} />
144 0 : <span className={styles.knob_value}>{ringFps}</span>
145 0 : </label>
146 0 : <label className={styles.knob}>
147 0 : <span className={styles.control_label}>Fly</span>
148 0 : <input id="landing-backdrop-fly" type="range" min={0.2} max={4} step={0.05} value={fly}
149 0 : onChange={e => setFly(Number(e.target.value))} />
150 0 : <span className={styles.knob_value}>{fly.toFixed(2)}s</span>
151 0 : </label>
152 0 : <label className={styles.knob}>
153 0 : <span className={styles.control_label}>Gap</span>
154 0 : <input id="landing-backdrop-gap" type="range" min={1.05} max={3} step={0.01} value={gap}
155 0 : onChange={e => setGap(Number(e.target.value))} />
156 0 : <span className={styles.knob_value}>{gap.toFixed(2)}</span>
157 0 : </label>
158 0 : <label className={styles.knob}>
159 0 : <span className={styles.control_label}>Outer</span>
160 0 : <input id="landing-backdrop-outer-wobble" type="range" min={0} max={0.3} step={0.005} value={outerWobble}
161 0 : onChange={e => setOuterWobble(Number(e.target.value))} />
162 0 : <span className={styles.knob_value}>{outerWobble.toFixed(3)}</span>
163 0 : </label>
164 0 : <div className={styles.renderer} data-testid="backdrop-renderer">{stats?.renderer}</div>
165 0 : <div className={styles.control_row}>
166 0 : <span className={styles.control_label}>Eyes</span>
167 0 : <SegmentedControl aria-label="Eyes" size={SegmentedControlSize.Small} onChange={index => setEyes(index === 0)}>
168 0 : <SegmentedControl.Button selected={eyes}>Shown</SegmentedControl.Button>
169 0 : <SegmentedControl.Button selected={!eyes}>Hidden</SegmentedControl.Button>
170 0 : </SegmentedControl>
171 0 : </div>
172 0 : <div className={styles.control_row}>
173 0 : <span className={styles.control_label}>Brand</span>
174 0 : <SegmentedControl aria-label="Brand" size={SegmentedControlSize.Small} onChange={setBrand}>
175 0 : {BRANDS.map((entry, i) => (
176 0 : <SegmentedControl.Button key={entry.label} selected={brand === i}>{entry.label}</SegmentedControl.Button>
177 0 : ))}
178 0 : </SegmentedControl>
179 0 : </div>
180 0 : <div className={styles.control_row}>
181 0 : <span className={styles.control_label}>Motion</span>
182 0 : <SegmentedControl aria-label="Motion" size={SegmentedControlSize.Small} onChange={index => setMotion(index === 0)}>
183 0 : <SegmentedControl.Button selected={motion}>On</SegmentedControl.Button>
184 0 : <SegmentedControl.Button selected={!motion}>Still</SegmentedControl.Button>
185 0 : </SegmentedControl>
186 0 : </div>
187 0 : <Button size={ButtonSize.Small} variant={ButtonVariant.Default} onClick={() => setControlsShown(false)}>
188 : Hide controls
189 0 : </Button>
190 0 : </div>
191 : )
192 : : (
193 0 : <div className={styles.controls_collapsed}>
194 0 : <Button size={ButtonSize.Small} variant={ButtonVariant.Default} onClick={() => setControlsShown(true)}>
195 : Show controls
196 0 : </Button>
197 0 : </div>
198 : )}
199 0 : </div>
200 : );
201 0 : }
202 :
203 : export default LandingBackdropPage;
|