@vibes.diy/prompts 14.3.78 → 14.3.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms/image-gen.md CHANGED
@@ -145,23 +145,26 @@ When each item's substance is its words — a generated joke, a short verse, a p
145
145
 
146
146
  ```jsx
147
147
  import { ImgGen, useImgGen } from "use-vibes";
148
+ import { Button, RowCard, Text } from "@vibes.diy/pouf";
148
149
 
149
150
  function PunCard({ pun, database }) {
150
151
  const [wanted, setWanted] = React.useState(false);
151
152
  const { document: saved } = useImgGen({ _id: pun._id, database });
152
153
  const hasPicture = Object.keys(saved?._files ?? {}).length > 0;
153
154
  return (
154
- <li>
155
- <p>{pun.text}</p>
155
+ <RowCard>
156
+ <Text>{pun.text}</Text>
156
157
  {wanted || hasPicture ? (
157
158
  <figure>
158
159
  <ImgGen _id={pun._id} database={database} prompt={`A cheerful cartoon of this pun: ${pun.text}`} />
159
160
  {hasPicture ? null : <figcaption>Making a picture…</figcaption>}
160
161
  </figure>
161
162
  ) : (
162
- <button onClick={() => setWanted(true)}>Picture this</button>
163
+ <Button type="button" size="sm" variant="quiet" onClick={() => setWanted(true)}>
164
+ Picture this
165
+ </Button>
163
166
  )}
164
- </li>
167
+ </RowCard>
165
168
  );
166
169
  }
167
170
  ```
@@ -236,9 +239,44 @@ The platform predicate `ctx.isImgGenVersionAppend` accepts exactly one legitimat
236
239
 
237
240
  ## Choosing a Model
238
241
 
239
- Override the model per component: `<ImgGen prompt="An astronaut riding a horse" model="openai/gpt-5-image-mini" />`
242
+ Every `<ImgGen>` draws with the app's default image model. `listModels({ kind: "image" })` (from `call-ai`, beside `callAI`) returns the image models this app can use, the app's own choice first, and each entry's `id` goes straight into the `model` prop: `<ImgGen prompt="An astronaut riding a horse" model={m.id} />`. The entry with `isDefault: true` is the one ImgGen uses when `model` is left out; with `images` present the default is the platform's image-edit model instead, so img2img leaves `model` out (see above). When the app's maker saved their own provider key, it covers the image model they chose in settings; other models run on the person's credits. The callAI skill's "Choosing a model" section lists every field.
240
243
 
241
- Model ids follow the `provider/model-name` form from the platform's model catalog. Unknown ids surface as an error in the component's error UI.
244
+ To compare image models, call `listModels({ kind: "image" })` and render one `<ImgGen>` per entry with `model={m.id}`. Every picture shares one prompt, each card is labelled with `m.name`, and each `_id` pairs the prompt with the model so a reload shows the saved pictures:
245
+
246
+ ### `App.jsx`
247
+ ```jsx
248
+ import React, { useEffect, useState } from "react";
249
+ import { ImgGen } from "use-vibes";
250
+ import { listModels } from "call-ai";
251
+ import { Button, Card, Field, Grid, Input, Stack, Text } from "@vibes.diy/pouf";
252
+
253
+ export default function App() {
254
+ const [models, setModels] = useState([]);
255
+ const [message, setMessage] = useState("");
256
+ const [draft, setDraft] = useState("a penguin riding a bicycle");
257
+ const [prompt, setPrompt] = useState("a penguin riding a bicycle");
258
+ useEffect(() => {
259
+ listModels({ kind: "image" })
260
+ .then(setModels)
261
+ .catch((error) => setMessage(error.message));
262
+ }, []);
263
+ return (
264
+ <Stack>
265
+ <Field label="Picture">{(id) => <Input id={id} value={draft} onChange={setDraft} />}</Field>
266
+ <Button onClick={() => setPrompt(draft)} disabled={draft.trim().length === 0}>Draw it with every model</Button>
267
+ <Text>{message}</Text>
268
+ <Grid cols={3}>
269
+ {models.map((m) => (
270
+ <Card key={m.id}>
271
+ <ImgGen _id={`audition:${prompt}:${m.id}`} prompt={prompt} model={m.id} alt={`${prompt}, drawn by ${m.name}`} />
272
+ <Text>{m.isDefault ? `${m.name} · this app's default` : m.name}</Text>
273
+ </Card>
274
+ ))}
275
+ </Grid>
276
+ </Stack>
277
+ );
278
+ }
279
+ ```
242
280
 
243
281
  #### Props
244
282
 
@@ -248,4 +286,4 @@ Model ids follow the `provider/model-name` form from the platform's model catalo
248
286
  - `database`: Fireproof db name or instance (default `"ImgGen"`)
249
287
  - `className`, `alt`, `style`: standard image styling
250
288
  - `showControls`: toggle regenerate + version nav (default `true`)
251
- - `model`: override the image-gen model for this component
289
+ - `model`: an `id` from `listModels({ kind: "image" })` to draw with instead of the app's default
package/llms/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { backendConfig } from "./backend.js";
2
2
  export { calendarConfig } from "./calendar.js";
3
+ export { boardGamesConfig } from "./board-games.js";
3
4
  export { callaiConfig } from "./callai.js";
4
5
  export { fireproofConfig } from "./fireproof.js";
5
6
  export { imageGenConfig } from "./image-gen.js";
@@ -46,5 +47,5 @@ export { poufLandingConfig } from "./pouf-landing.js";
46
47
  export { poufCalendarConfig } from "./pouf-calendar.js";
47
48
  export { poufTestimonialsConfig } from "./pouf-testimonials.js";
48
49
  export type { LlmConfig } from "./types.js";
49
- export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
50
+ export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
50
51
  export declare const FAST_SKILLS: readonly ["callai", "image-gen", "fireproof", "three-js", "d3", "webxr", "calendar", "spotify"];
package/llms/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { backendConfig } from "./backend.js";
2
2
  import { calendarConfig } from "./calendar.js";
3
+ import { boardGamesConfig } from "./board-games.js";
3
4
  import { callaiConfig } from "./callai.js";
4
5
  import { fireproofConfig } from "./fireproof.js";
5
6
  import { imageGenConfig } from "./image-gen.js";
@@ -47,6 +48,7 @@ import { poufCalendarConfig } from "./pouf-calendar.js";
47
48
  import { poufTestimonialsConfig } from "./pouf-testimonials.js";
48
49
  export { backendConfig } from "./backend.js";
49
50
  export { calendarConfig } from "./calendar.js";
51
+ export { boardGamesConfig } from "./board-games.js";
50
52
  export { callaiConfig } from "./callai.js";
51
53
  export { fireproofConfig } from "./fireproof.js";
52
54
  export { imageGenConfig } from "./image-gen.js";
@@ -107,6 +109,7 @@ export const allConfigs = [
107
109
  createVibeConfig,
108
110
  backendConfig,
109
111
  calendarConfig,
112
+ boardGamesConfig,
110
113
  accessConfig,
111
114
  connectionsConfig,
112
115
  youtubeConfig,
package/llms/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"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"}
package/llms/p5.md CHANGED
@@ -24,7 +24,7 @@ the instance so remounts do not leak a second draw loop.
24
24
  ```jsx
25
25
  import React from "react";
26
26
  import p5 from "https://esm.sh/p5@1.11.13";
27
- import { Button } from "@vibes.diy/look";
27
+ import { Button, Row, Stack } from "@vibes.diy/pouf";
28
28
 
29
29
  const TAU = Math.PI * 2;
30
30
 
@@ -191,16 +191,14 @@ export default function App() {
191
191
 
192
192
  return (
193
193
  <div className="min-h-screen bg-background text-foreground font-sans p-6">
194
- <div
195
- ref={hostRef}
196
- className="w-full h-[70vh] overflow-hidden rounded-lg border border-border shadow-md touch-none"
197
- />
198
- <Button
199
- className="mt-3"
200
- onClick={() => setParams((prev) => ({ ...prev, seed: Math.floor(Math.random() * 100000) }))}
201
- >
202
- Reseed
203
- </Button>
194
+ <Stack gap={3}>
195
+ <div ref={hostRef} className="w-full aspect-video overflow-hidden rounded-lg border border-border shadow-md touch-none" />
196
+ <Row>
197
+ <Button type="button" onClick={() => setParams((prev) => ({ ...prev, seed: Math.floor(Math.random() * 100000) }))}>
198
+ Reseed
199
+ </Button>
200
+ </Row>
201
+ </Stack>
204
202
  </div>
205
203
  );
206
204
  }
package/llms/spotify.md CHANGED
@@ -306,6 +306,7 @@ function artistRow(a) {
306
306
  ### `App.jsx`
307
307
  ```jsx app=spotify-browser
308
308
  import React, { useState } from "react";
309
+ import { Button, Field, Input, Row } from "@vibes.diy/pouf";
309
310
 
310
311
  const SETUP_STEPS = [
311
312
  "Open the Spotify developer dashboard and log in.",
@@ -333,8 +334,10 @@ export default function App() {
333
334
 
334
335
  return (
335
336
  <div>
336
- <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search Spotify" />
337
- <button onClick={search}>Search</button>
337
+ <Row gap={2} align="top">
338
+ <Field label="Search">{(id, describedBy) => <Input id={id} describedBy={describedBy} value={query} onChange={setQuery} placeholder="Search Spotify" />}</Field>
339
+ <Button type="button" onClick={search}>Search</Button>
340
+ </Row>
338
341
 
339
342
  {problem?.reason === "missing-key" && (
340
343
  <section>
package/llms/three-js.md CHANGED
@@ -1213,7 +1213,7 @@ Structure your component in this order:
1213
1213
  import React, { useState, useEffect, useRef, useCallback } from "react";
1214
1214
  import { useFireproof } from "use-fireproof";
1215
1215
  import * as THREE from "three";
1216
- import { Card, CardContent, CardHeader, CardTitle } from "@vibes.diy/look";
1216
+ import { Card, Heading, Stack, Text } from "@vibes.diy/pouf";
1217
1217
 
1218
1218
  export default function SkyGlider() {
1219
1219
  const { database, useLiveQuery } = useFireproof("skyGliderScores");
@@ -1613,31 +1613,34 @@ export default function SkyGlider() {
1613
1613
  return (
1614
1614
  <div className="relative h-screen w-full bg-background text-foreground font-sans">
1615
1615
  <canvas ref={canvasRef} className="absolute inset-0" />
1616
- <Card className="absolute top-4 left-4">
1617
- <CardHeader>
1618
- <CardTitle>Sky Glider</CardTitle>
1619
- </CardHeader>
1620
- <CardContent>
1621
- <p>Score: {currentScore}</p>
1622
- <p className="mt-2 text-muted-foreground">WASD/Arrows: Fly, Space: Thrust</p>
1623
- </CardContent>
1624
- </Card>
1625
- {scoreData.length > 0 && (
1626
- <Card className="absolute top-4 right-4">
1627
- <CardHeader>
1628
- <CardTitle>High Scores</CardTitle>
1629
- </CardHeader>
1630
- <CardContent>
1631
- {scoreData
1632
- .sort((a, b) => b.value - a.value)
1633
- .slice(0, 3)
1634
- .map((score, i) => (
1635
- <div key={score._id}>
1636
- #{i + 1}: {score.value}
1637
- </div>
1638
- ))}
1639
- </CardContent>
1616
+ {/* A pouf Card takes no className: a plain wrapper carries its placement. */}
1617
+ <div className="absolute top-4 left-4">
1618
+ <Card>
1619
+ <Stack gap={2}>
1620
+ <Heading level={2}>Sky Glider</Heading>
1621
+ <Text num>Score: {currentScore}</Text>
1622
+ <Text size="sm" muted>
1623
+ WASD/Arrows: Fly, Space: Thrust
1624
+ </Text>
1625
+ </Stack>
1640
1626
  </Card>
1627
+ </div>
1628
+ {scoreData.length > 0 && (
1629
+ <div className="absolute top-4 right-4">
1630
+ <Card>
1631
+ <Stack gap={2}>
1632
+ <Heading level={2}>High Scores</Heading>
1633
+ {scoreData
1634
+ .sort((a, b) => b.value - a.value)
1635
+ .slice(0, 3)
1636
+ .map((score, i) => (
1637
+ <Text key={score._id} num>
1638
+ #{i + 1}: {score.value}
1639
+ </Text>
1640
+ ))}
1641
+ </Stack>
1642
+ </Card>
1643
+ </div>
1641
1644
  )}
1642
1645
  </div>
1643
1646
  );
@@ -1654,8 +1657,8 @@ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
1654
1657
  import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
1655
1658
  import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
1656
1659
  import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js";
1657
- import { Button, Card, CardContent, CardHeader, CardTitle, Input, Label, Separator } from "@vibes.diy/look";
1658
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@vibes.diy/look/overlay";
1660
+ import { Button, Card, Field, Heading, Input, RowCard, Stack, Text } from "@vibes.diy/pouf";
1661
+ import { Checkbox, Select, Separator, Slider } from "@vibes.diy/pouf/interactive";
1659
1662
 
1660
1663
  export default function HalftoneArtStudio() {
1661
1664
  const { database, useLiveQuery } = useFireproof("halftoneStudio");
@@ -2001,154 +2004,148 @@ export default function HalftoneArtStudio() {
2001
2004
  const shapeName = ["", "Dot", "Ellipse", "Line", "Square"][parameters.shape] || "Dot";
2002
2005
  const blendModeName = ["", "Linear", "Multiply", "Add", "Lighter", "Darker"][parameters.blendingMode] || "Linear";
2003
2006
  const actionNames = {
2004
- "before-randomize": "🎲 Before Random",
2005
- randomized: "✨ Randomized",
2006
- manual: "✏️ Manual Edit",
2007
+ "before-randomize": "Before Random",
2008
+ randomized: "Randomized",
2009
+ manual: "Manual Edit",
2007
2010
  };
2008
2011
 
2009
2012
  return (
2010
2013
  <div className="relative h-screen w-full overflow-hidden bg-background text-foreground font-sans">
2011
2014
  <canvas ref={canvasRef} className="absolute inset-0" />
2012
2015
 
2013
- {/* One control panel. The look's `Card` IS the panel — its `className`
2014
- carries placement only; the cushion, corner and ink are its own. */}
2015
- <Card className="absolute inset-y-4 left-4 w-80 overflow-y-auto">
2016
- <CardHeader>
2017
- <CardTitle>RGB Halftone Studio</CardTitle>
2018
- </CardHeader>
2019
- <CardContent>
2020
- <Button className="w-full" onClick={generateRandomScene} disabled={isGenerating}>
2021
- {isGenerating ? "Generating…" : "🎲 Random Art"}
2022
- </Button>
2023
- <Button variant="secondary" className="w-full" onClick={() => setShowParameters(!showParameters)}>
2024
- {showParameters ? "🔼 Hide Controls" : "🔽 Show Controls"}
2025
- </Button>
2026
-
2027
- {showParameters && (
2028
- <div className="space-y-4">
2029
- <Separator />
2030
-
2031
- {/* A chooser is `Select`, and its props do the styling. */}
2032
- <div className="space-y-2">
2033
- <Label htmlFor="shape">Shape: {shapeName}</Label>
2034
- <Select value={String(parameters.shape)} onValueChange={(value) => setParameters((prev) => ({ ...prev, shape: parseInt(value) }))}>
2035
- <SelectTrigger id="shape" className="w-full">
2036
- <SelectValue />
2037
- </SelectTrigger>
2038
- <SelectContent>
2039
- <SelectItem value="1">Dot</SelectItem>
2040
- <SelectItem value="2">Ellipse</SelectItem>
2041
- <SelectItem value="3">Line</SelectItem>
2042
- <SelectItem value="4">Square</SelectItem>
2043
- </SelectContent>
2044
- </Select>
2045
- </div>
2046
-
2047
- {/* A range slider and a checkbox are NOT in the package, so they
2048
- stay plain elements you style by hand. Blend amount and the
2049
- per-channel R/G/B rotations are this same Label + range pair,
2050
- one per uniform. */}
2051
- <div className="space-y-2">
2052
- <Label htmlFor="size">Size: {parameters.radius.toFixed(1)}</Label>
2053
- <input
2054
- id="size"
2055
- type="range"
2056
- min="1"
2057
- max="25"
2058
- step="0.5"
2059
- value={parameters.radius}
2060
- onChange={(e) => setParameters((prev) => ({ ...prev, radius: parseFloat(e.target.value) }))}
2061
- className="w-full"
2016
+ {/* One control panel. The pouf `Card` IS the panel — it takes no className,
2017
+ so a plain wrapper carries its placement; the cushion, corner and ink are its own. */}
2018
+ <div className="absolute inset-y-4 left-4 w-80 overflow-y-auto">
2019
+ <Card>
2020
+ <Stack gap={3}>
2021
+ <Heading level={2}>RGB Halftone Studio</Heading>
2022
+ <Button block loading={isGenerating} onClick={generateRandomScene}>
2023
+ {isGenerating ? "Generating…" : "Random Art"}
2024
+ </Button>
2025
+ <Button block variant="quiet" aria-expanded={showParameters} onClick={() => setShowParameters(!showParameters)}>
2026
+ {showParameters ? "Hide Controls" : "Show Controls"}
2027
+ </Button>
2028
+
2029
+ {showParameters && (
2030
+ <Stack gap={4}>
2031
+ <Separator />
2032
+
2033
+ {/* A chooser is `Select`; its options are data. */}
2034
+ <Field label={`Shape: ${shapeName}`}>
2035
+ {(id, describedBy) => (
2036
+ <Select
2037
+ id={id}
2038
+ describedBy={describedBy}
2039
+ value={String(parameters.shape)}
2040
+ onChange={(value) => setParameters((prev) => ({ ...prev, shape: parseInt(value) }))}
2041
+ options={[
2042
+ { value: "1", label: "Dot" },
2043
+ { value: "2", label: "Ellipse" },
2044
+ { value: "3", label: "Line" },
2045
+ { value: "4", label: "Square" },
2046
+ ]}
2047
+ />
2048
+ )}
2049
+ </Field>
2050
+
2051
+ {/* A range is `Slider`, which hands over an array of values. Blend amount
2052
+ and the per-channel R/G/B rotations are this same labelled Slider,
2053
+ one per uniform. */}
2054
+ <Stack gap={2}>
2055
+ <Text size="sm">Size: {parameters.radius.toFixed(1)}</Text>
2056
+ <Slider
2057
+ label="Size"
2058
+ min={1}
2059
+ max={25}
2060
+ step={0.5}
2061
+ value={[parameters.radius]}
2062
+ onChange={([radius]) => setParameters((prev) => ({ ...prev, radius }))}
2063
+ />
2064
+ </Stack>
2065
+
2066
+ <Stack gap={2}>
2067
+ <Text size="sm">Scatter: {(parameters.scatter * 100).toFixed(0)}%</Text>
2068
+ <Slider
2069
+ label="Scatter"
2070
+ min={0}
2071
+ max={1}
2072
+ step={0.01}
2073
+ value={[parameters.scatter]}
2074
+ onChange={([scatter]) => setParameters((prev) => ({ ...prev, scatter }))}
2075
+ />
2076
+ </Stack>
2077
+
2078
+ <Field label={`Blend Mode: ${blendModeName}`}>
2079
+ {(id, describedBy) => (
2080
+ <Select
2081
+ id={id}
2082
+ describedBy={describedBy}
2083
+ value={String(parameters.blendingMode)}
2084
+ onChange={(value) => setParameters((prev) => ({ ...prev, blendingMode: parseInt(value) }))}
2085
+ options={[
2086
+ { value: "1", label: "Linear" },
2087
+ { value: "2", label: "Multiply" },
2088
+ { value: "3", label: "Add" },
2089
+ { value: "4", label: "Lighter" },
2090
+ { value: "5", label: "Darker" },
2091
+ ]}
2092
+ />
2093
+ )}
2094
+ </Field>
2095
+
2096
+ <Checkbox
2097
+ label="Greyscale"
2098
+ checked={parameters.greyscale}
2099
+ onChange={(checked) => setParameters((prev) => ({ ...prev, greyscale: checked === true }))}
2062
2100
  />
2063
- </div>
2064
-
2065
- <div className="space-y-2">
2066
- <Label htmlFor="scatter">Scatter: {(parameters.scatter * 100).toFixed(0)}%</Label>
2067
- <input
2068
- id="scatter"
2069
- type="range"
2070
- min="0"
2071
- max="1"
2072
- step="0.01"
2073
- value={parameters.scatter}
2074
- onChange={(e) => setParameters((prev) => ({ ...prev, scatter: parseFloat(e.target.value) }))}
2075
- className="w-full"
2101
+ <Checkbox
2102
+ label="Disable Effect"
2103
+ checked={parameters.disable}
2104
+ onChange={(checked) => setParameters((prev) => ({ ...prev, disable: checked === true }))}
2076
2105
  />
2077
- </div>
2078
-
2079
- <div className="space-y-2">
2080
- <Label htmlFor="blend-mode">Blend Mode: {blendModeName}</Label>
2081
- <Select
2082
- value={String(parameters.blendingMode)}
2083
- onValueChange={(value) => setParameters((prev) => ({ ...prev, blendingMode: parseInt(value) }))}
2084
- >
2085
- <SelectTrigger id="blend-mode" className="w-full">
2086
- <SelectValue />
2087
- </SelectTrigger>
2088
- <SelectContent>
2089
- <SelectItem value="1">Linear</SelectItem>
2090
- <SelectItem value="2">Multiply</SelectItem>
2091
- <SelectItem value="3">Add</SelectItem>
2092
- <SelectItem value="4">Lighter</SelectItem>
2093
- <SelectItem value="5">Darker</SelectItem>
2094
- </SelectContent>
2095
- </Select>
2096
- </div>
2097
-
2098
- <div className="flex items-center gap-2">
2099
- <input id="greyscale" type="checkbox" checked={parameters.greyscale} onChange={(e) => setParameters((prev) => ({ ...prev, greyscale: e.target.checked }))} />
2100
- <Label htmlFor="greyscale">Greyscale</Label>
2101
- </div>
2102
- <div className="flex items-center gap-2">
2103
- <input id="disable" type="checkbox" checked={parameters.disable} onChange={(e) => setParameters((prev) => ({ ...prev, disable: e.target.checked }))} />
2104
- <Label htmlFor="disable">Disable Effect</Label>
2105
- </div>
2106
-
2107
- <div className="space-y-2">
2108
- <Label htmlFor="preset-name">Preset name</Label>
2109
- <Input id="preset-name" placeholder="Preset name…" value={presetName} onChange={(e) => setPresetName(e.target.value)} className="w-full" />
2110
- <Button variant="secondary" className="w-full" onClick={savePreset} disabled={!presetName.trim()}>
2111
- 💾 Save Preset
2106
+
2107
+ <Field label="Preset name">
2108
+ {(id, describedBy) => (
2109
+ <Input id={id} describedBy={describedBy} placeholder="Preset name…" value={presetName} onChange={setPresetName} />
2110
+ )}
2111
+ </Field>
2112
+ <Button block variant="quiet" onClick={savePreset} disabled={!presetName.trim()}>
2113
+ Save Preset
2112
2114
  </Button>
2113
- </div>
2114
-
2115
- {/* A row that selects something is a `Button`, and which one is
2116
- chosen is a `variant` — never a colour `className`. */}
2117
- {presets.length > 0 && (
2118
- <div className="space-y-2">
2119
- <h4 className="font-bold">💾 Saved Presets</h4>
2120
- {presets
2121
- .sort((a, b) => b.timestamp - a.timestamp)
2122
- .map((preset) => (
2123
- <Button
2124
- key={preset._id}
2125
- variant={currentPreset?._id === preset._id ? "default" : "secondary"}
2126
- className="w-full justify-start"
2127
- onClick={() => loadPreset(preset)}
2128
- >
2129
- {preset.name} · {["", "Dot", "Ellipse", "Line", "Square"][preset.parameters.shape]}
2130
- </Button>
2131
- ))}
2132
- </div>
2133
- )}
2134
-
2135
- {parameterHistory.length > 0 && (
2136
- <div className="space-y-2">
2137
- <h4 className="font-bold">📜 Parameter History</h4>
2138
- {parameterHistory
2139
- .sort((a, b) => b.timestamp - a.timestamp)
2140
- .slice(0, 10)
2141
- .map((state) => (
2142
- <Button key={state._id} variant="outline" className="w-full justify-start" onClick={() => loadParameterState(state)}>
2143
- {actionNames[state.action] || "⚙️ Unknown"} · {new Date(state.timestamp).toLocaleTimeString()}
2144
- </Button>
2145
- ))}
2146
- </div>
2147
- )}
2148
- </div>
2149
- )}
2150
- </CardContent>
2151
- </Card>
2115
+
2116
+ {/* A row that selects something is a `RowCard`, and which one is
2117
+ chosen is its `selected` prop. */}
2118
+ {presets.length > 0 && (
2119
+ <Stack gap={2}>
2120
+ <Heading level={3}>Saved Presets</Heading>
2121
+ {presets
2122
+ .sort((a, b) => b.timestamp - a.timestamp)
2123
+ .map((preset) => (
2124
+ <RowCard key={preset._id} selected={currentPreset?._id === preset._id} onClick={() => loadPreset(preset)}>
2125
+ {preset.name} · {["", "Dot", "Ellipse", "Line", "Square"][preset.parameters.shape]}
2126
+ </RowCard>
2127
+ ))}
2128
+ </Stack>
2129
+ )}
2130
+
2131
+ {parameterHistory.length > 0 && (
2132
+ <Stack gap={2}>
2133
+ <Heading level={3}>Parameter History</Heading>
2134
+ {parameterHistory
2135
+ .sort((a, b) => b.timestamp - a.timestamp)
2136
+ .slice(0, 10)
2137
+ .map((state) => (
2138
+ <RowCard key={state._id} onClick={() => loadParameterState(state)}>
2139
+ {actionNames[state.action] || "Unknown"} · {new Date(state.timestamp).toLocaleTimeString()}
2140
+ </RowCard>
2141
+ ))}
2142
+ </Stack>
2143
+ )}
2144
+ </Stack>
2145
+ )}
2146
+ </Stack>
2147
+ </Card>
2148
+ </div>
2152
2149
  </div>
2153
2150
  );
2154
2151
  }