@scenography/plugin-artwork 0.2.0

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.
@@ -0,0 +1,87 @@
1
+ import { eachPropIs, existsProp, fail, isFiniteNumber, isIntegerNumber, isMeasure, isNonNegativeNumber, isObject, isOneOf, isPositiveNumber, isString, isVec2, isVec3, propIs, validate, when } from "@scenography/scene";
2
+ import { allEdgeNames } from "@scenography/plugin-wall/data";
3
+ //#region src/data/artwork.ts
4
+ /** Default visibility when a label/card doesn't specify its own `fields`. */
5
+ const DEFAULT_ARTWORK_FIELDS = [
6
+ "title",
7
+ "technique",
8
+ "dimensions",
9
+ "availability"
10
+ ];
11
+ //#endregion
12
+ //#region src/data/validate.ts
13
+ const AVAILABILITY = new Set([
14
+ "for-sale",
15
+ "sold",
16
+ "not-for-sale"
17
+ ]);
18
+ const ARTWORK_FIELDS = new Set([
19
+ "title",
20
+ "author",
21
+ "year",
22
+ "technique",
23
+ "dimensions",
24
+ "availability"
25
+ ]);
26
+ const LABEL_SIDES = new Set([
27
+ "below",
28
+ "left",
29
+ "right"
30
+ ]);
31
+ const LOCATION_TYPES = new Set(["wall", "floating"]);
32
+ const locationOfType = (type) => (artwork) => isObject(artwork.location) && artwork.location.type === type;
33
+ /**
34
+ * Validate one artwork. `edgeNames` is the set of wall-edge names in the scene,
35
+ * used to check `location.edge` references. Returns every problem found, in
36
+ * order, with paths relative to the artwork (the caller scopes them).
37
+ */
38
+ const validateArtwork = (artwork, edgeNames) => validate([
39
+ existsProp("id", "Artwork is missing an id."),
40
+ existsProp("src", "Artwork is missing a src."),
41
+ existsProp("size", "Artwork is missing a size."),
42
+ propIs("size", isVec2, "size must be [width, height]."),
43
+ propIs("depth", isNonNegativeNumber, "depth must be a non-negative number."),
44
+ propIs("year", isIntegerNumber, "year must be an integer."),
45
+ propIs("href", isString, "href must be a string."),
46
+ propIs("availability", isOneOf(AVAILABILITY), "availability must be 'for-sale', 'sold' or 'not-for-sale'."),
47
+ propIs("infoRadius", isPositiveNumber, "infoRadius must be a positive number."),
48
+ propIs("label.side", isOneOf(LABEL_SIDES), "side must be 'below', 'left' or 'right'."),
49
+ propIs("label.fields", Array.isArray, "fields must be an array."),
50
+ eachPropIs("label.fields", isOneOf(ARTWORK_FIELDS), (field) => `unknown field "${String(field)}".`),
51
+ propIs("label.scale", isPositiveNumber, "scale must be a positive number."),
52
+ propIs("label.offset", isVec2, "offset must be [x, y]."),
53
+ propIs("frame.width", isFiniteNumber, "width must be a finite number."),
54
+ propIs("frame.depth", isFiniteNumber, "depth must be a finite number."),
55
+ when((a) => a.frame?.texture !== void 0, existsProp("frame.texture.src", "frame texture is missing a src.")),
56
+ propIs("frame.passepartout.width", isFiniteNumber, "passepartout width must be a finite number."),
57
+ existsProp("location", "Artwork is missing a location."),
58
+ propIs("location", isObject, "location must be an object."),
59
+ when(locationOfType("wall"), existsProp("location.edge"), propIs("location.edge", (edge) => isString(edge) && edgeNames.has(edge), (edge) => `references unknown edge "${String(edge)}".`), existsProp("location.position"), propIs("location.position", isMeasure, "position must be a number or a percentage string."), propIs("location.height", isMeasure, "height must be a number or a percentage string.")),
60
+ when(locationOfType("floating"), existsProp("location.point"), propIs("location.point", isVec3, "point must be [x, y, z].")),
61
+ when((a) => isObject(a.location) && !isOneOf(LOCATION_TYPES)(a.location.type), fail("location.type", "unknown location type."))
62
+ ])(artwork);
63
+ /**
64
+ * Validate an artwork against its scene: resolves the set of valid wall-edge
65
+ * names from the scene's walls (the wall plugin's data half) so `location.edge`
66
+ * references are checked. Floating pieces don't need any wall.
67
+ */
68
+ const validateArtworkInScene = (artwork, ctx) => {
69
+ const walls = ctx.scene.walls ?? [];
70
+ return validateArtwork(artwork, new Set(allEdgeNames(walls)));
71
+ };
72
+ //#endregion
73
+ //#region src/data/plugin.ts
74
+ /**
75
+ * The data half of the artwork plugin: owns the `artworks` scene key (a
76
+ * collection) and validates each piece — including that a wall-hung piece
77
+ * references a real wall edge. Pure — no three.js.
78
+ */
79
+ const artworkData = {
80
+ kind: "artworks",
81
+ cardinality: "many",
82
+ validate: (artwork, ctx) => validateArtworkInScene(artwork, ctx)
83
+ };
84
+ //#endregion
85
+ export { DEFAULT_ARTWORK_FIELDS as i, validateArtwork as n, validateArtworkInScene as r, artworkData as t };
86
+
87
+ //# sourceMappingURL=data-BsI0PbuK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-BsI0PbuK.mjs","names":[],"sources":["../src/data/artwork.ts","../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import type { Measure, SurfaceTexture, Vec2, Vec3 } from \"@scenography/scene\";\n\n/**\n * Where an artwork is placed. Extensible via `type`; today a piece either hangs\n * on a named wall edge or floats freely. Future element types (floor, ceiling…)\n * add new variants here.\n */\nexport type ArtworkLocation =\n | {\n type: \"wall\";\n /** Name of the `NamedEdge` to hang on. */\n edge: string;\n /** Position along the edge: a number is units from the edge start, `'50%'` is a fraction. */\n position: Measure;\n /**\n * Vertical placement: a number is an absolute world Y, `'50%'` is a\n * fraction of the wall height. Omitted defaults to the camera's eye height.\n */\n height?: Measure;\n /**\n * Which face of the wall the piece shows on, relative to the edge\n * direction. `'right'` (default) is the right-hand side when walking the\n * edge from its start vertex to its end vertex.\n */\n side?: \"right\" | \"left\";\n }\n | {\n type: \"floating\";\n /** World position `[x, y, z]`. */\n point: Vec3;\n /** Y-axis rotation in radians. Defaults to `0`. */\n yaw?: number;\n };\n\n/**\n * A passepartout (mat) sitting between the frame and the image: a flat border\n * that insets the image. Only meaningful when the artwork has a `frame`.\n */\nexport interface Passepartout {\n /** Mat border width around the image, in world units. Defaults to `6`. */\n width?: number;\n /** Mat color (CSS). Defaults to an off-white. */\n color?: string;\n}\n\n/**\n * A frame around an artwork. By default it's four flat bars rendered with a\n * solid `color` or a `texture` (e.g. wood). For an ornate, sculpted frame, point\n * `model` at an external glTF/GLB instead. A `passepartout` mat can sit between\n * the frame and the image.\n */\nexport interface Frame {\n /** Border width (how wide the frame face is), in world units. Defaults to `5`. */\n width?: number;\n /** How far the frame stands proud of the artwork face, in world units. Defaults to `3`. */\n depth?: number;\n /** Frame color (CSS). Defaults to a dark tone. Ignored when `texture` or `model` is set. */\n color?: string;\n /** Surface texture (e.g. wood); takes visual precedence over `color`. */\n texture?: SurfaceTexture;\n /** External glTF/GLB model for an ornate frame; replaces the generated bars. */\n model?: string;\n /** Passepartout mat between the frame and the image. */\n passepartout?: Passepartout;\n}\n\n/**\n * Commercial status of a piece. Gallery convention: a colored dot signals\n * availability. The dot's color is presentation (decided by the renderer), not\n * data — this is the state it derives from.\n */\nexport type Availability = \"for-sale\" | \"sold\" | \"not-for-sale\";\n\n/**\n * The fields a label/info card can show, identified by name. A `fields` array\n * toggles which are visible; the render order is fixed (it is not the array\n * order): title, then author/year, then technique + dimensions on one line, with\n * availability shown as a corner dot. Shared by the physical label (engine) and\n * the overlay info card.\n */\nexport type ArtworkField = \"title\" | \"author\" | \"year\" | \"technique\" | \"dimensions\" | \"availability\";\n\n/** Default visibility when a label/card doesn't specify its own `fields`. */\nexport const DEFAULT_ARTWORK_FIELDS: readonly ArtworkField[] = [\n \"title\",\n \"technique\",\n \"dimensions\",\n \"availability\",\n];\n\n/**\n * A physical wall label (cartela) materialized next to the piece. Its presence\n * is the opt-in; the engine renders it from the artwork's own data.\n */\nexport interface ArtworkLabel {\n /** Where the label sits relative to the piece. Defaults to `'below'`. */\n side?: \"below\" | \"left\" | \"right\";\n /** Which fields to show. Defaults to `DEFAULT_ARTWORK_FIELDS`. */\n fields?: ArtworkField[];\n /**\n * Size multiplier over the real physical size (24pt title etc). Defaults to\n * `1` (true to life), but a real cartela is only a few cm tall — tiny and hard\n * to read/click at gallery scale — so bump this (e.g. `3`) for legibility.\n */\n scale?: number;\n /**\n * Fine-tune the placement with an `[x, y]` shift in world units, added on top\n * of the `side` position (the frame is already cleared). `x` runs along the\n * wall (right is positive), `y` is vertical (up is positive).\n */\n offset?: Vec2;\n}\n\n/** A framed image placed somewhere in the scene. */\nexport interface Artwork {\n id: string;\n location: ArtworkLocation;\n /** Image URL/path to display. */\n src: string;\n /** `[width, height]` of the artwork, in world units. */\n size: Vec2;\n /**\n * Box depth (volume) in world units: the piece is a box with the image on the\n * front and white sides/back. Defaults to `2`.\n */\n depth?: number;\n /** Optional frame around the artwork. */\n frame?: Frame;\n title?: string;\n technique?: string;\n /** Author / artist name. */\n author?: string;\n /** Year the piece was made. */\n year?: number;\n /**\n * Real-world dimensions for display, e.g. `'92 × 73 cm'`. Distinct from `size`,\n * which is in world units.\n */\n dimensions?: string;\n /** Commercial status; drives the availability dot. */\n availability?: Availability;\n /** Long-form caption / catalog entry, shown in the expanded info panel. */\n description?: string;\n /**\n * External URL for the piece (a catalog page, or a marketplace listing when\n * it's for sale). Surfaced as a link in the expanded info panel.\n */\n href?: string;\n /** When present, a physical wall label is materialized next to the piece. */\n label?: ArtworkLabel;\n /**\n * Max distance (world units) from which the piece can be clicked to open its\n * info panel. A gate so far-away pieces aren't interactive. Absent = no gate.\n */\n infoRadius?: number;\n}\n\ndeclare module \"@scenography/scene\" {\n interface Scene {\n /** Framed images placed in the space — provided by `@scenography/plugin-artwork`. */\n artworks?: Artwork[];\n }\n}\n","import {\n eachPropIs,\n existsProp,\n fail,\n isFiniteNumber,\n isIntegerNumber,\n isMeasure,\n isNonNegativeNumber,\n isObject,\n isOneOf,\n isPositiveNumber,\n isString,\n isVec2,\n isVec3,\n propIs,\n validate,\n when,\n type SceneContext,\n type ValidationResult,\n} from \"@scenography/scene\";\nimport { allEdgeNames, type Wall } from \"@scenography/plugin-wall/data\";\n\nimport type { Artwork, ArtworkField, Availability } from \"./artwork\";\n\nconst AVAILABILITY: ReadonlySet<Availability> = new Set<Availability>([\n \"for-sale\",\n \"sold\",\n \"not-for-sale\",\n]);\nconst ARTWORK_FIELDS: ReadonlySet<ArtworkField> = new Set<ArtworkField>([\n \"title\",\n \"author\",\n \"year\",\n \"technique\",\n \"dimensions\",\n \"availability\",\n]);\nconst LABEL_SIDES: ReadonlySet<string> = new Set([\"below\", \"left\", \"right\"]);\nconst LOCATION_TYPES: ReadonlySet<string> = new Set([\"wall\", \"floating\"]);\n\nconst locationOfType =\n (type: string) =>\n (artwork: Artwork): boolean =>\n isObject(artwork.location) && artwork.location.type === type;\n\n/**\n * Validate one artwork. `edgeNames` is the set of wall-edge names in the scene,\n * used to check `location.edge` references. Returns every problem found, in\n * order, with paths relative to the artwork (the caller scopes them).\n */\nexport const validateArtwork = (artwork: Artwork, edgeNames: Set<string>): ValidationResult =>\n validate<Artwork>([\n existsProp(\"id\", \"Artwork is missing an id.\"),\n existsProp(\"src\", \"Artwork is missing a src.\"),\n existsProp(\"size\", \"Artwork is missing a size.\"),\n propIs(\"size\", isVec2, \"size must be [width, height].\"),\n propIs(\"depth\", isNonNegativeNumber, \"depth must be a non-negative number.\"),\n propIs(\"year\", isIntegerNumber, \"year must be an integer.\"),\n propIs(\"href\", isString, \"href must be a string.\"),\n propIs(\n \"availability\",\n isOneOf(AVAILABILITY),\n \"availability must be 'for-sale', 'sold' or 'not-for-sale'.\",\n ),\n propIs(\"infoRadius\", isPositiveNumber, \"infoRadius must be a positive number.\"),\n propIs(\"label.side\", isOneOf(LABEL_SIDES), \"side must be 'below', 'left' or 'right'.\"),\n propIs(\"label.fields\", Array.isArray, \"fields must be an array.\"),\n eachPropIs(\"label.fields\", isOneOf(ARTWORK_FIELDS), (field) => `unknown field \"${String(field)}\".`),\n propIs(\"label.scale\", isPositiveNumber, \"scale must be a positive number.\"),\n propIs(\"label.offset\", isVec2, \"offset must be [x, y].\"),\n propIs(\"frame.width\", isFiniteNumber, \"width must be a finite number.\"),\n propIs(\"frame.depth\", isFiniteNumber, \"depth must be a finite number.\"),\n when(\n (a: Artwork) => a.frame?.texture !== undefined,\n existsProp(\"frame.texture.src\", \"frame texture is missing a src.\"),\n ),\n propIs(\n \"frame.passepartout.width\",\n isFiniteNumber,\n \"passepartout width must be a finite number.\",\n ),\n existsProp(\"location\", \"Artwork is missing a location.\"),\n propIs(\"location\", isObject, \"location must be an object.\"),\n when(\n locationOfType(\"wall\"),\n existsProp(\"location.edge\"),\n propIs(\n \"location.edge\",\n (edge) => isString(edge) && edgeNames.has(edge),\n (edge) => `references unknown edge \"${String(edge)}\".`,\n ),\n existsProp(\"location.position\"),\n propIs(\"location.position\", isMeasure, \"position must be a number or a percentage string.\"),\n propIs(\"location.height\", isMeasure, \"height must be a number or a percentage string.\"),\n ),\n when(\n locationOfType(\"floating\"),\n existsProp(\"location.point\"),\n propIs(\"location.point\", isVec3, \"point must be [x, y, z].\"),\n ),\n when(\n (a: Artwork) => isObject(a.location) && !isOneOf(LOCATION_TYPES)(a.location.type),\n fail(\"location.type\", \"unknown location type.\"),\n ),\n ])(artwork);\n\n/**\n * Validate an artwork against its scene: resolves the set of valid wall-edge\n * names from the scene's walls (the wall plugin's data half) so `location.edge`\n * references are checked. Floating pieces don't need any wall.\n */\nexport const validateArtworkInScene = (artwork: Artwork, ctx: SceneContext): ValidationResult => {\n const walls = (ctx.scene as { walls?: Wall[] }).walls ?? [];\n return validateArtwork(artwork, new Set(allEdgeNames(walls)));\n};\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateArtworkInScene } from \"./validate\";\nimport type { Artwork } from \"./artwork\";\n\n/**\n * The data half of the artwork plugin: owns the `artworks` scene key (a\n * collection) and validates each piece — including that a wall-hung piece\n * references a real wall edge. Pure — no three.js.\n */\nexport const artworkData: DataPlugin<Artwork> = {\n kind: \"artworks\",\n cardinality: \"many\",\n validate: (artwork, ctx) => validateArtworkInScene(artwork, ctx),\n};\n"],"mappings":";;;;AAmFA,MAAa,yBAAkD;CAC7D;CACA;CACA;CACA;AACF;;;AChEA,MAAM,eAA0C,IAAI,IAAkB;CACpE;CACA;CACA;AACF,CAAC;AACD,MAAM,iBAA4C,IAAI,IAAkB;CACtE;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,cAAmC,IAAI,IAAI;CAAC;CAAS;CAAQ;AAAO,CAAC;AAC3E,MAAM,iBAAsC,IAAI,IAAI,CAAC,QAAQ,UAAU,CAAC;AAExE,MAAM,kBACH,UACA,YACC,SAAS,QAAQ,QAAQ,KAAK,QAAQ,SAAS,SAAS;;;;;;AAO5D,MAAa,mBAAmB,SAAkB,cAChD,SAAkB;CAChB,WAAW,MAAM,2BAA2B;CAC5C,WAAW,OAAO,2BAA2B;CAC7C,WAAW,QAAQ,4BAA4B;CAC/C,OAAO,QAAQ,QAAQ,+BAA+B;CACtD,OAAO,SAAS,qBAAqB,sCAAsC;CAC3E,OAAO,QAAQ,iBAAiB,0BAA0B;CAC1D,OAAO,QAAQ,UAAU,wBAAwB;CACjD,OACE,gBACA,QAAQ,YAAY,GACpB,4DACF;CACA,OAAO,cAAc,kBAAkB,uCAAuC;CAC9E,OAAO,cAAc,QAAQ,WAAW,GAAG,0CAA0C;CACrF,OAAO,gBAAgB,MAAM,SAAS,0BAA0B;CAChE,WAAW,gBAAgB,QAAQ,cAAc,IAAI,UAAU,kBAAkB,OAAO,KAAK,EAAE,GAAG;CAClG,OAAO,eAAe,kBAAkB,kCAAkC;CAC1E,OAAO,gBAAgB,QAAQ,wBAAwB;CACvD,OAAO,eAAe,gBAAgB,gCAAgC;CACtE,OAAO,eAAe,gBAAgB,gCAAgC;CACtE,MACG,MAAe,EAAE,OAAO,YAAY,KAAA,GACrC,WAAW,qBAAqB,iCAAiC,CACnE;CACA,OACE,4BACA,gBACA,6CACF;CACA,WAAW,YAAY,gCAAgC;CACvD,OAAO,YAAY,UAAU,6BAA6B;CAC1D,KACE,eAAe,MAAM,GACrB,WAAW,eAAe,GAC1B,OACE,kBACC,SAAS,SAAS,IAAI,KAAK,UAAU,IAAI,IAAI,IAC7C,SAAS,4BAA4B,OAAO,IAAI,EAAE,GACrD,GACA,WAAW,mBAAmB,GAC9B,OAAO,qBAAqB,WAAW,mDAAmD,GAC1F,OAAO,mBAAmB,WAAW,iDAAiD,CACxF;CACA,KACE,eAAe,UAAU,GACzB,WAAW,gBAAgB,GAC3B,OAAO,kBAAkB,QAAQ,0BAA0B,CAC7D;CACA,MACG,MAAe,SAAS,EAAE,QAAQ,KAAK,CAAC,QAAQ,cAAc,CAAC,CAAC,EAAE,SAAS,IAAI,GAChF,KAAK,iBAAiB,wBAAwB,CAChD;AACF,CAAC,CAAC,CAAC,OAAO;;;;;;AAOZ,MAAa,0BAA0B,SAAkB,QAAwC;CAC/F,MAAM,QAAS,IAAI,MAA6B,SAAS,CAAC;CAC1D,OAAO,gBAAgB,SAAS,IAAI,IAAI,aAAa,KAAK,CAAC,CAAC;AAC9D;;;;;;;;ACxGA,MAAa,cAAmC;CAC9C,MAAM;CACN,aAAa;CACb,WAAW,SAAS,QAAQ,uBAAuB,SAAS,GAAG;AACjE"}
@@ -0,0 +1,110 @@
1
+ let _scenography_scene = require("@scenography/scene");
2
+ let _scenography_plugin_wall_data = require("@scenography/plugin-wall/data");
3
+ //#region src/data/artwork.ts
4
+ /** Default visibility when a label/card doesn't specify its own `fields`. */
5
+ const DEFAULT_ARTWORK_FIELDS = [
6
+ "title",
7
+ "technique",
8
+ "dimensions",
9
+ "availability"
10
+ ];
11
+ //#endregion
12
+ //#region src/data/validate.ts
13
+ const AVAILABILITY = new Set([
14
+ "for-sale",
15
+ "sold",
16
+ "not-for-sale"
17
+ ]);
18
+ const ARTWORK_FIELDS = new Set([
19
+ "title",
20
+ "author",
21
+ "year",
22
+ "technique",
23
+ "dimensions",
24
+ "availability"
25
+ ]);
26
+ const LABEL_SIDES = new Set([
27
+ "below",
28
+ "left",
29
+ "right"
30
+ ]);
31
+ const LOCATION_TYPES = new Set(["wall", "floating"]);
32
+ const locationOfType = (type) => (artwork) => (0, _scenography_scene.isObject)(artwork.location) && artwork.location.type === type;
33
+ /**
34
+ * Validate one artwork. `edgeNames` is the set of wall-edge names in the scene,
35
+ * used to check `location.edge` references. Returns every problem found, in
36
+ * order, with paths relative to the artwork (the caller scopes them).
37
+ */
38
+ const validateArtwork = (artwork, edgeNames) => (0, _scenography_scene.validate)([
39
+ (0, _scenography_scene.existsProp)("id", "Artwork is missing an id."),
40
+ (0, _scenography_scene.existsProp)("src", "Artwork is missing a src."),
41
+ (0, _scenography_scene.existsProp)("size", "Artwork is missing a size."),
42
+ (0, _scenography_scene.propIs)("size", _scenography_scene.isVec2, "size must be [width, height]."),
43
+ (0, _scenography_scene.propIs)("depth", _scenography_scene.isNonNegativeNumber, "depth must be a non-negative number."),
44
+ (0, _scenography_scene.propIs)("year", _scenography_scene.isIntegerNumber, "year must be an integer."),
45
+ (0, _scenography_scene.propIs)("href", _scenography_scene.isString, "href must be a string."),
46
+ (0, _scenography_scene.propIs)("availability", (0, _scenography_scene.isOneOf)(AVAILABILITY), "availability must be 'for-sale', 'sold' or 'not-for-sale'."),
47
+ (0, _scenography_scene.propIs)("infoRadius", _scenography_scene.isPositiveNumber, "infoRadius must be a positive number."),
48
+ (0, _scenography_scene.propIs)("label.side", (0, _scenography_scene.isOneOf)(LABEL_SIDES), "side must be 'below', 'left' or 'right'."),
49
+ (0, _scenography_scene.propIs)("label.fields", Array.isArray, "fields must be an array."),
50
+ (0, _scenography_scene.eachPropIs)("label.fields", (0, _scenography_scene.isOneOf)(ARTWORK_FIELDS), (field) => `unknown field "${String(field)}".`),
51
+ (0, _scenography_scene.propIs)("label.scale", _scenography_scene.isPositiveNumber, "scale must be a positive number."),
52
+ (0, _scenography_scene.propIs)("label.offset", _scenography_scene.isVec2, "offset must be [x, y]."),
53
+ (0, _scenography_scene.propIs)("frame.width", _scenography_scene.isFiniteNumber, "width must be a finite number."),
54
+ (0, _scenography_scene.propIs)("frame.depth", _scenography_scene.isFiniteNumber, "depth must be a finite number."),
55
+ (0, _scenography_scene.when)((a) => a.frame?.texture !== void 0, (0, _scenography_scene.existsProp)("frame.texture.src", "frame texture is missing a src.")),
56
+ (0, _scenography_scene.propIs)("frame.passepartout.width", _scenography_scene.isFiniteNumber, "passepartout width must be a finite number."),
57
+ (0, _scenography_scene.existsProp)("location", "Artwork is missing a location."),
58
+ (0, _scenography_scene.propIs)("location", _scenography_scene.isObject, "location must be an object."),
59
+ (0, _scenography_scene.when)(locationOfType("wall"), (0, _scenography_scene.existsProp)("location.edge"), (0, _scenography_scene.propIs)("location.edge", (edge) => (0, _scenography_scene.isString)(edge) && edgeNames.has(edge), (edge) => `references unknown edge "${String(edge)}".`), (0, _scenography_scene.existsProp)("location.position"), (0, _scenography_scene.propIs)("location.position", _scenography_scene.isMeasure, "position must be a number or a percentage string."), (0, _scenography_scene.propIs)("location.height", _scenography_scene.isMeasure, "height must be a number or a percentage string.")),
60
+ (0, _scenography_scene.when)(locationOfType("floating"), (0, _scenography_scene.existsProp)("location.point"), (0, _scenography_scene.propIs)("location.point", _scenography_scene.isVec3, "point must be [x, y, z].")),
61
+ (0, _scenography_scene.when)((a) => (0, _scenography_scene.isObject)(a.location) && !(0, _scenography_scene.isOneOf)(LOCATION_TYPES)(a.location.type), (0, _scenography_scene.fail)("location.type", "unknown location type."))
62
+ ])(artwork);
63
+ /**
64
+ * Validate an artwork against its scene: resolves the set of valid wall-edge
65
+ * names from the scene's walls (the wall plugin's data half) so `location.edge`
66
+ * references are checked. Floating pieces don't need any wall.
67
+ */
68
+ const validateArtworkInScene = (artwork, ctx) => {
69
+ const walls = ctx.scene.walls ?? [];
70
+ return validateArtwork(artwork, new Set((0, _scenography_plugin_wall_data.allEdgeNames)(walls)));
71
+ };
72
+ //#endregion
73
+ //#region src/data/plugin.ts
74
+ /**
75
+ * The data half of the artwork plugin: owns the `artworks` scene key (a
76
+ * collection) and validates each piece — including that a wall-hung piece
77
+ * references a real wall edge. Pure — no three.js.
78
+ */
79
+ const artworkData = {
80
+ kind: "artworks",
81
+ cardinality: "many",
82
+ validate: (artwork, ctx) => validateArtworkInScene(artwork, ctx)
83
+ };
84
+ //#endregion
85
+ Object.defineProperty(exports, "DEFAULT_ARTWORK_FIELDS", {
86
+ enumerable: true,
87
+ get: function() {
88
+ return DEFAULT_ARTWORK_FIELDS;
89
+ }
90
+ });
91
+ Object.defineProperty(exports, "artworkData", {
92
+ enumerable: true,
93
+ get: function() {
94
+ return artworkData;
95
+ }
96
+ });
97
+ Object.defineProperty(exports, "validateArtwork", {
98
+ enumerable: true,
99
+ get: function() {
100
+ return validateArtwork;
101
+ }
102
+ });
103
+ Object.defineProperty(exports, "validateArtworkInScene", {
104
+ enumerable: true,
105
+ get: function() {
106
+ return validateArtworkInScene;
107
+ }
108
+ });
109
+
110
+ //# sourceMappingURL=data-WcCPqQ_k.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-WcCPqQ_k.cjs","names":["isVec2","isNonNegativeNumber","isIntegerNumber","isString","isPositiveNumber","isFiniteNumber","isObject","isMeasure","isVec3"],"sources":["../src/data/artwork.ts","../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import type { Measure, SurfaceTexture, Vec2, Vec3 } from \"@scenography/scene\";\n\n/**\n * Where an artwork is placed. Extensible via `type`; today a piece either hangs\n * on a named wall edge or floats freely. Future element types (floor, ceiling…)\n * add new variants here.\n */\nexport type ArtworkLocation =\n | {\n type: \"wall\";\n /** Name of the `NamedEdge` to hang on. */\n edge: string;\n /** Position along the edge: a number is units from the edge start, `'50%'` is a fraction. */\n position: Measure;\n /**\n * Vertical placement: a number is an absolute world Y, `'50%'` is a\n * fraction of the wall height. Omitted defaults to the camera's eye height.\n */\n height?: Measure;\n /**\n * Which face of the wall the piece shows on, relative to the edge\n * direction. `'right'` (default) is the right-hand side when walking the\n * edge from its start vertex to its end vertex.\n */\n side?: \"right\" | \"left\";\n }\n | {\n type: \"floating\";\n /** World position `[x, y, z]`. */\n point: Vec3;\n /** Y-axis rotation in radians. Defaults to `0`. */\n yaw?: number;\n };\n\n/**\n * A passepartout (mat) sitting between the frame and the image: a flat border\n * that insets the image. Only meaningful when the artwork has a `frame`.\n */\nexport interface Passepartout {\n /** Mat border width around the image, in world units. Defaults to `6`. */\n width?: number;\n /** Mat color (CSS). Defaults to an off-white. */\n color?: string;\n}\n\n/**\n * A frame around an artwork. By default it's four flat bars rendered with a\n * solid `color` or a `texture` (e.g. wood). For an ornate, sculpted frame, point\n * `model` at an external glTF/GLB instead. A `passepartout` mat can sit between\n * the frame and the image.\n */\nexport interface Frame {\n /** Border width (how wide the frame face is), in world units. Defaults to `5`. */\n width?: number;\n /** How far the frame stands proud of the artwork face, in world units. Defaults to `3`. */\n depth?: number;\n /** Frame color (CSS). Defaults to a dark tone. Ignored when `texture` or `model` is set. */\n color?: string;\n /** Surface texture (e.g. wood); takes visual precedence over `color`. */\n texture?: SurfaceTexture;\n /** External glTF/GLB model for an ornate frame; replaces the generated bars. */\n model?: string;\n /** Passepartout mat between the frame and the image. */\n passepartout?: Passepartout;\n}\n\n/**\n * Commercial status of a piece. Gallery convention: a colored dot signals\n * availability. The dot's color is presentation (decided by the renderer), not\n * data — this is the state it derives from.\n */\nexport type Availability = \"for-sale\" | \"sold\" | \"not-for-sale\";\n\n/**\n * The fields a label/info card can show, identified by name. A `fields` array\n * toggles which are visible; the render order is fixed (it is not the array\n * order): title, then author/year, then technique + dimensions on one line, with\n * availability shown as a corner dot. Shared by the physical label (engine) and\n * the overlay info card.\n */\nexport type ArtworkField = \"title\" | \"author\" | \"year\" | \"technique\" | \"dimensions\" | \"availability\";\n\n/** Default visibility when a label/card doesn't specify its own `fields`. */\nexport const DEFAULT_ARTWORK_FIELDS: readonly ArtworkField[] = [\n \"title\",\n \"technique\",\n \"dimensions\",\n \"availability\",\n];\n\n/**\n * A physical wall label (cartela) materialized next to the piece. Its presence\n * is the opt-in; the engine renders it from the artwork's own data.\n */\nexport interface ArtworkLabel {\n /** Where the label sits relative to the piece. Defaults to `'below'`. */\n side?: \"below\" | \"left\" | \"right\";\n /** Which fields to show. Defaults to `DEFAULT_ARTWORK_FIELDS`. */\n fields?: ArtworkField[];\n /**\n * Size multiplier over the real physical size (24pt title etc). Defaults to\n * `1` (true to life), but a real cartela is only a few cm tall — tiny and hard\n * to read/click at gallery scale — so bump this (e.g. `3`) for legibility.\n */\n scale?: number;\n /**\n * Fine-tune the placement with an `[x, y]` shift in world units, added on top\n * of the `side` position (the frame is already cleared). `x` runs along the\n * wall (right is positive), `y` is vertical (up is positive).\n */\n offset?: Vec2;\n}\n\n/** A framed image placed somewhere in the scene. */\nexport interface Artwork {\n id: string;\n location: ArtworkLocation;\n /** Image URL/path to display. */\n src: string;\n /** `[width, height]` of the artwork, in world units. */\n size: Vec2;\n /**\n * Box depth (volume) in world units: the piece is a box with the image on the\n * front and white sides/back. Defaults to `2`.\n */\n depth?: number;\n /** Optional frame around the artwork. */\n frame?: Frame;\n title?: string;\n technique?: string;\n /** Author / artist name. */\n author?: string;\n /** Year the piece was made. */\n year?: number;\n /**\n * Real-world dimensions for display, e.g. `'92 × 73 cm'`. Distinct from `size`,\n * which is in world units.\n */\n dimensions?: string;\n /** Commercial status; drives the availability dot. */\n availability?: Availability;\n /** Long-form caption / catalog entry, shown in the expanded info panel. */\n description?: string;\n /**\n * External URL for the piece (a catalog page, or a marketplace listing when\n * it's for sale). Surfaced as a link in the expanded info panel.\n */\n href?: string;\n /** When present, a physical wall label is materialized next to the piece. */\n label?: ArtworkLabel;\n /**\n * Max distance (world units) from which the piece can be clicked to open its\n * info panel. A gate so far-away pieces aren't interactive. Absent = no gate.\n */\n infoRadius?: number;\n}\n\ndeclare module \"@scenography/scene\" {\n interface Scene {\n /** Framed images placed in the space — provided by `@scenography/plugin-artwork`. */\n artworks?: Artwork[];\n }\n}\n","import {\n eachPropIs,\n existsProp,\n fail,\n isFiniteNumber,\n isIntegerNumber,\n isMeasure,\n isNonNegativeNumber,\n isObject,\n isOneOf,\n isPositiveNumber,\n isString,\n isVec2,\n isVec3,\n propIs,\n validate,\n when,\n type SceneContext,\n type ValidationResult,\n} from \"@scenography/scene\";\nimport { allEdgeNames, type Wall } from \"@scenography/plugin-wall/data\";\n\nimport type { Artwork, ArtworkField, Availability } from \"./artwork\";\n\nconst AVAILABILITY: ReadonlySet<Availability> = new Set<Availability>([\n \"for-sale\",\n \"sold\",\n \"not-for-sale\",\n]);\nconst ARTWORK_FIELDS: ReadonlySet<ArtworkField> = new Set<ArtworkField>([\n \"title\",\n \"author\",\n \"year\",\n \"technique\",\n \"dimensions\",\n \"availability\",\n]);\nconst LABEL_SIDES: ReadonlySet<string> = new Set([\"below\", \"left\", \"right\"]);\nconst LOCATION_TYPES: ReadonlySet<string> = new Set([\"wall\", \"floating\"]);\n\nconst locationOfType =\n (type: string) =>\n (artwork: Artwork): boolean =>\n isObject(artwork.location) && artwork.location.type === type;\n\n/**\n * Validate one artwork. `edgeNames` is the set of wall-edge names in the scene,\n * used to check `location.edge` references. Returns every problem found, in\n * order, with paths relative to the artwork (the caller scopes them).\n */\nexport const validateArtwork = (artwork: Artwork, edgeNames: Set<string>): ValidationResult =>\n validate<Artwork>([\n existsProp(\"id\", \"Artwork is missing an id.\"),\n existsProp(\"src\", \"Artwork is missing a src.\"),\n existsProp(\"size\", \"Artwork is missing a size.\"),\n propIs(\"size\", isVec2, \"size must be [width, height].\"),\n propIs(\"depth\", isNonNegativeNumber, \"depth must be a non-negative number.\"),\n propIs(\"year\", isIntegerNumber, \"year must be an integer.\"),\n propIs(\"href\", isString, \"href must be a string.\"),\n propIs(\n \"availability\",\n isOneOf(AVAILABILITY),\n \"availability must be 'for-sale', 'sold' or 'not-for-sale'.\",\n ),\n propIs(\"infoRadius\", isPositiveNumber, \"infoRadius must be a positive number.\"),\n propIs(\"label.side\", isOneOf(LABEL_SIDES), \"side must be 'below', 'left' or 'right'.\"),\n propIs(\"label.fields\", Array.isArray, \"fields must be an array.\"),\n eachPropIs(\"label.fields\", isOneOf(ARTWORK_FIELDS), (field) => `unknown field \"${String(field)}\".`),\n propIs(\"label.scale\", isPositiveNumber, \"scale must be a positive number.\"),\n propIs(\"label.offset\", isVec2, \"offset must be [x, y].\"),\n propIs(\"frame.width\", isFiniteNumber, \"width must be a finite number.\"),\n propIs(\"frame.depth\", isFiniteNumber, \"depth must be a finite number.\"),\n when(\n (a: Artwork) => a.frame?.texture !== undefined,\n existsProp(\"frame.texture.src\", \"frame texture is missing a src.\"),\n ),\n propIs(\n \"frame.passepartout.width\",\n isFiniteNumber,\n \"passepartout width must be a finite number.\",\n ),\n existsProp(\"location\", \"Artwork is missing a location.\"),\n propIs(\"location\", isObject, \"location must be an object.\"),\n when(\n locationOfType(\"wall\"),\n existsProp(\"location.edge\"),\n propIs(\n \"location.edge\",\n (edge) => isString(edge) && edgeNames.has(edge),\n (edge) => `references unknown edge \"${String(edge)}\".`,\n ),\n existsProp(\"location.position\"),\n propIs(\"location.position\", isMeasure, \"position must be a number or a percentage string.\"),\n propIs(\"location.height\", isMeasure, \"height must be a number or a percentage string.\"),\n ),\n when(\n locationOfType(\"floating\"),\n existsProp(\"location.point\"),\n propIs(\"location.point\", isVec3, \"point must be [x, y, z].\"),\n ),\n when(\n (a: Artwork) => isObject(a.location) && !isOneOf(LOCATION_TYPES)(a.location.type),\n fail(\"location.type\", \"unknown location type.\"),\n ),\n ])(artwork);\n\n/**\n * Validate an artwork against its scene: resolves the set of valid wall-edge\n * names from the scene's walls (the wall plugin's data half) so `location.edge`\n * references are checked. Floating pieces don't need any wall.\n */\nexport const validateArtworkInScene = (artwork: Artwork, ctx: SceneContext): ValidationResult => {\n const walls = (ctx.scene as { walls?: Wall[] }).walls ?? [];\n return validateArtwork(artwork, new Set(allEdgeNames(walls)));\n};\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateArtworkInScene } from \"./validate\";\nimport type { Artwork } from \"./artwork\";\n\n/**\n * The data half of the artwork plugin: owns the `artworks` scene key (a\n * collection) and validates each piece — including that a wall-hung piece\n * references a real wall edge. Pure — no three.js.\n */\nexport const artworkData: DataPlugin<Artwork> = {\n kind: \"artworks\",\n cardinality: \"many\",\n validate: (artwork, ctx) => validateArtworkInScene(artwork, ctx),\n};\n"],"mappings":";;;;AAmFA,MAAa,yBAAkD;CAC7D;CACA;CACA;CACA;AACF;;;AChEA,MAAM,eAA0C,IAAI,IAAkB;CACpE;CACA;CACA;AACF,CAAC;AACD,MAAM,iBAA4C,IAAI,IAAkB;CACtE;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,cAAmC,IAAI,IAAI;CAAC;CAAS;CAAQ;AAAO,CAAC;AAC3E,MAAM,iBAAsC,IAAI,IAAI,CAAC,QAAQ,UAAU,CAAC;AAExE,MAAM,kBACH,UACA,aAAA,GAAA,mBAAA,SAAA,CACU,QAAQ,QAAQ,KAAK,QAAQ,SAAS,SAAS;;;;;;AAO5D,MAAa,mBAAmB,SAAkB,eAAA,GAAA,mBAAA,SAAA,CAC9B;oCACL,MAAM,2BAA2B;oCACjC,OAAO,2BAA2B;oCAClC,QAAQ,4BAA4B;gCACxC,QAAQA,mBAAAA,QAAQ,+BAA+B;gCAC/C,SAASC,mBAAAA,qBAAqB,sCAAsC;gCACpE,QAAQC,mBAAAA,iBAAiB,0BAA0B;gCACnD,QAAQC,mBAAAA,UAAU,wBAAwB;gCAE/C,iBAAA,GAAA,mBAAA,QAAA,CACQ,YAAY,GACpB,4DACF;gCACO,cAAcC,mBAAAA,kBAAkB,uCAAuC;gCACvE,eAAA,GAAA,mBAAA,QAAA,CAAsB,WAAW,GAAG,0CAA0C;gCAC9E,gBAAgB,MAAM,SAAS,0BAA0B;oCACrD,iBAAA,GAAA,mBAAA,QAAA,CAAwB,cAAc,IAAI,UAAU,kBAAkB,OAAO,KAAK,EAAE,GAAG;gCAC3F,eAAeA,mBAAAA,kBAAkB,kCAAkC;gCACnE,gBAAgBJ,mBAAAA,QAAQ,wBAAwB;gCAChD,eAAeK,mBAAAA,gBAAgB,gCAAgC;gCAC/D,eAAeA,mBAAAA,gBAAgB,gCAAgC;+BAEnE,MAAe,EAAE,OAAO,YAAY,KAAA,IAAA,GAAA,mBAAA,WAAA,CAC1B,qBAAqB,iCAAiC,CACnE;gCAEE,4BACAA,mBAAAA,gBACA,6CACF;oCACW,YAAY,gCAAgC;gCAChD,YAAYC,mBAAAA,UAAU,6BAA6B;8BAExD,eAAe,MAAM,IAAA,GAAA,mBAAA,WAAA,CACV,eAAe,IAAA,GAAA,mBAAA,OAAA,CAExB,kBACC,UAAA,GAAA,mBAAA,SAAA,CAAkB,IAAI,KAAK,UAAU,IAAI,IAAI,IAC7C,SAAS,4BAA4B,OAAO,IAAI,EAAE,GACrD,IAAA,GAAA,mBAAA,WAAA,CACW,mBAAmB,IAAA,GAAA,mBAAA,OAAA,CACvB,qBAAqBC,mBAAAA,WAAW,mDAAmD,IAAA,GAAA,mBAAA,OAAA,CACnF,mBAAmBA,mBAAAA,WAAW,iDAAiD,CACxF;8BAEE,eAAe,UAAU,IAAA,GAAA,mBAAA,WAAA,CACd,gBAAgB,IAAA,GAAA,mBAAA,OAAA,CACpB,kBAAkBC,mBAAAA,QAAQ,0BAA0B,CAC7D;+BAEG,OAAA,GAAA,mBAAA,SAAA,CAAwB,EAAE,QAAQ,KAAK,EAAA,GAAA,mBAAA,QAAA,CAAS,cAAc,CAAC,CAAC,EAAE,SAAS,IAAI,IAAA,GAAA,mBAAA,KAAA,CAC3E,iBAAiB,wBAAwB,CAChD;AACF,CAAC,CAAC,CAAC,OAAO;;;;;;AAOZ,MAAa,0BAA0B,SAAkB,QAAwC;CAC/F,MAAM,QAAS,IAAI,MAA6B,SAAS,CAAC;CAC1D,OAAO,gBAAgB,SAAS,IAAI,KAAA,GAAA,8BAAA,aAAA,CAAiB,KAAK,CAAC,CAAC;AAC9D;;;;;;;;ACxGA,MAAa,cAAmC;CAC9C,MAAM;CACN,aAAa;CACb,WAAW,SAAS,QAAQ,uBAAuB,SAAS,GAAG;AACjE"}
package/dist/data.cjs ADDED
@@ -0,0 +1,6 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_data = require("./data-WcCPqQ_k.cjs");
3
+ exports.DEFAULT_ARTWORK_FIELDS = require_data.DEFAULT_ARTWORK_FIELDS;
4
+ exports.artworkData = require_data.artworkData;
5
+ exports.validateArtwork = require_data.validateArtwork;
6
+ exports.validateArtworkInScene = require_data.validateArtworkInScene;
@@ -0,0 +1,2 @@
1
+ import { a as ArtworkField, c as Availability, d as Passepartout, i as Artwork, l as DEFAULT_ARTWORK_FIELDS, n as validateArtwork, o as ArtworkLabel, r as validateArtworkInScene, s as ArtworkLocation, t as artworkData, u as Frame } from "./index-XUkegdM8.cjs";
2
+ export { type Artwork, type ArtworkField, type ArtworkLabel, type ArtworkLocation, type Availability, DEFAULT_ARTWORK_FIELDS, type Frame, type Passepartout, artworkData, validateArtwork, validateArtworkInScene };
@@ -0,0 +1,2 @@
1
+ import { a as ArtworkField, c as Availability, d as Passepartout, i as Artwork, l as DEFAULT_ARTWORK_FIELDS, n as validateArtwork, o as ArtworkLabel, r as validateArtworkInScene, s as ArtworkLocation, t as artworkData, u as Frame } from "./index-XUkegdM8.mjs";
2
+ export { type Artwork, type ArtworkField, type ArtworkLabel, type ArtworkLocation, type Availability, DEFAULT_ARTWORK_FIELDS, type Frame, type Passepartout, artworkData, validateArtwork, validateArtworkInScene };
package/dist/data.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { i as DEFAULT_ARTWORK_FIELDS, n as validateArtwork, r as validateArtworkInScene, t as artworkData } from "./data-BsI0PbuK.mjs";
2
+ export { DEFAULT_ARTWORK_FIELDS, artworkData, validateArtwork, validateArtworkInScene };
@@ -0,0 +1,170 @@
1
+ import { DataPlugin, Measure, SceneContext, SurfaceTexture, ValidationResult, Vec2, Vec3 } from "@scenography/scene";
2
+
3
+ //#region src/data/artwork.d.ts
4
+ /**
5
+ * Where an artwork is placed. Extensible via `type`; today a piece either hangs
6
+ * on a named wall edge or floats freely. Future element types (floor, ceiling…)
7
+ * add new variants here.
8
+ */
9
+ type ArtworkLocation = {
10
+ type: "wall"; /** Name of the `NamedEdge` to hang on. */
11
+ edge: string; /** Position along the edge: a number is units from the edge start, `'50%'` is a fraction. */
12
+ position: Measure;
13
+ /**
14
+ * Vertical placement: a number is an absolute world Y, `'50%'` is a
15
+ * fraction of the wall height. Omitted defaults to the camera's eye height.
16
+ */
17
+ height?: Measure;
18
+ /**
19
+ * Which face of the wall the piece shows on, relative to the edge
20
+ * direction. `'right'` (default) is the right-hand side when walking the
21
+ * edge from its start vertex to its end vertex.
22
+ */
23
+ side?: "right" | "left";
24
+ } | {
25
+ type: "floating"; /** World position `[x, y, z]`. */
26
+ point: Vec3; /** Y-axis rotation in radians. Defaults to `0`. */
27
+ yaw?: number;
28
+ };
29
+ /**
30
+ * A passepartout (mat) sitting between the frame and the image: a flat border
31
+ * that insets the image. Only meaningful when the artwork has a `frame`.
32
+ */
33
+ interface Passepartout {
34
+ /** Mat border width around the image, in world units. Defaults to `6`. */
35
+ width?: number;
36
+ /** Mat color (CSS). Defaults to an off-white. */
37
+ color?: string;
38
+ }
39
+ /**
40
+ * A frame around an artwork. By default it's four flat bars rendered with a
41
+ * solid `color` or a `texture` (e.g. wood). For an ornate, sculpted frame, point
42
+ * `model` at an external glTF/GLB instead. A `passepartout` mat can sit between
43
+ * the frame and the image.
44
+ */
45
+ interface Frame {
46
+ /** Border width (how wide the frame face is), in world units. Defaults to `5`. */
47
+ width?: number;
48
+ /** How far the frame stands proud of the artwork face, in world units. Defaults to `3`. */
49
+ depth?: number;
50
+ /** Frame color (CSS). Defaults to a dark tone. Ignored when `texture` or `model` is set. */
51
+ color?: string;
52
+ /** Surface texture (e.g. wood); takes visual precedence over `color`. */
53
+ texture?: SurfaceTexture;
54
+ /** External glTF/GLB model for an ornate frame; replaces the generated bars. */
55
+ model?: string;
56
+ /** Passepartout mat between the frame and the image. */
57
+ passepartout?: Passepartout;
58
+ }
59
+ /**
60
+ * Commercial status of a piece. Gallery convention: a colored dot signals
61
+ * availability. The dot's color is presentation (decided by the renderer), not
62
+ * data — this is the state it derives from.
63
+ */
64
+ type Availability = "for-sale" | "sold" | "not-for-sale";
65
+ /**
66
+ * The fields a label/info card can show, identified by name. A `fields` array
67
+ * toggles which are visible; the render order is fixed (it is not the array
68
+ * order): title, then author/year, then technique + dimensions on one line, with
69
+ * availability shown as a corner dot. Shared by the physical label (engine) and
70
+ * the overlay info card.
71
+ */
72
+ type ArtworkField = "title" | "author" | "year" | "technique" | "dimensions" | "availability";
73
+ /** Default visibility when a label/card doesn't specify its own `fields`. */
74
+ declare const DEFAULT_ARTWORK_FIELDS: readonly ArtworkField[];
75
+ /**
76
+ * A physical wall label (cartela) materialized next to the piece. Its presence
77
+ * is the opt-in; the engine renders it from the artwork's own data.
78
+ */
79
+ interface ArtworkLabel {
80
+ /** Where the label sits relative to the piece. Defaults to `'below'`. */
81
+ side?: "below" | "left" | "right";
82
+ /** Which fields to show. Defaults to `DEFAULT_ARTWORK_FIELDS`. */
83
+ fields?: ArtworkField[];
84
+ /**
85
+ * Size multiplier over the real physical size (24pt title etc). Defaults to
86
+ * `1` (true to life), but a real cartela is only a few cm tall — tiny and hard
87
+ * to read/click at gallery scale — so bump this (e.g. `3`) for legibility.
88
+ */
89
+ scale?: number;
90
+ /**
91
+ * Fine-tune the placement with an `[x, y]` shift in world units, added on top
92
+ * of the `side` position (the frame is already cleared). `x` runs along the
93
+ * wall (right is positive), `y` is vertical (up is positive).
94
+ */
95
+ offset?: Vec2;
96
+ }
97
+ /** A framed image placed somewhere in the scene. */
98
+ interface Artwork {
99
+ id: string;
100
+ location: ArtworkLocation;
101
+ /** Image URL/path to display. */
102
+ src: string;
103
+ /** `[width, height]` of the artwork, in world units. */
104
+ size: Vec2;
105
+ /**
106
+ * Box depth (volume) in world units: the piece is a box with the image on the
107
+ * front and white sides/back. Defaults to `2`.
108
+ */
109
+ depth?: number;
110
+ /** Optional frame around the artwork. */
111
+ frame?: Frame;
112
+ title?: string;
113
+ technique?: string;
114
+ /** Author / artist name. */
115
+ author?: string;
116
+ /** Year the piece was made. */
117
+ year?: number;
118
+ /**
119
+ * Real-world dimensions for display, e.g. `'92 × 73 cm'`. Distinct from `size`,
120
+ * which is in world units.
121
+ */
122
+ dimensions?: string;
123
+ /** Commercial status; drives the availability dot. */
124
+ availability?: Availability;
125
+ /** Long-form caption / catalog entry, shown in the expanded info panel. */
126
+ description?: string;
127
+ /**
128
+ * External URL for the piece (a catalog page, or a marketplace listing when
129
+ * it's for sale). Surfaced as a link in the expanded info panel.
130
+ */
131
+ href?: string;
132
+ /** When present, a physical wall label is materialized next to the piece. */
133
+ label?: ArtworkLabel;
134
+ /**
135
+ * Max distance (world units) from which the piece can be clicked to open its
136
+ * info panel. A gate so far-away pieces aren't interactive. Absent = no gate.
137
+ */
138
+ infoRadius?: number;
139
+ }
140
+ declare module "@scenography/scene" {
141
+ interface Scene {
142
+ /** Framed images placed in the space — provided by `@scenography/plugin-artwork`. */
143
+ artworks?: Artwork[];
144
+ }
145
+ }
146
+ //#endregion
147
+ //#region src/data/validate.d.ts
148
+ /**
149
+ * Validate one artwork. `edgeNames` is the set of wall-edge names in the scene,
150
+ * used to check `location.edge` references. Returns every problem found, in
151
+ * order, with paths relative to the artwork (the caller scopes them).
152
+ */
153
+ declare const validateArtwork: (artwork: Artwork, edgeNames: Set<string>) => ValidationResult;
154
+ /**
155
+ * Validate an artwork against its scene: resolves the set of valid wall-edge
156
+ * names from the scene's walls (the wall plugin's data half) so `location.edge`
157
+ * references are checked. Floating pieces don't need any wall.
158
+ */
159
+ declare const validateArtworkInScene: (artwork: Artwork, ctx: SceneContext) => ValidationResult;
160
+ //#endregion
161
+ //#region src/data/plugin.d.ts
162
+ /**
163
+ * The data half of the artwork plugin: owns the `artworks` scene key (a
164
+ * collection) and validates each piece — including that a wall-hung piece
165
+ * references a real wall edge. Pure — no three.js.
166
+ */
167
+ declare const artworkData: DataPlugin<Artwork>;
168
+ //#endregion
169
+ export { ArtworkField as a, Availability as c, Passepartout as d, Artwork as i, DEFAULT_ARTWORK_FIELDS as l, validateArtwork as n, ArtworkLabel as o, validateArtworkInScene as r, ArtworkLocation as s, artworkData as t, Frame as u };
170
+ //# sourceMappingURL=index-XUkegdM8.d.cts.map
@@ -0,0 +1,170 @@
1
+ import { DataPlugin, Measure, SceneContext, SurfaceTexture, ValidationResult, Vec2, Vec3 } from "@scenography/scene";
2
+
3
+ //#region src/data/artwork.d.ts
4
+ /**
5
+ * Where an artwork is placed. Extensible via `type`; today a piece either hangs
6
+ * on a named wall edge or floats freely. Future element types (floor, ceiling…)
7
+ * add new variants here.
8
+ */
9
+ type ArtworkLocation = {
10
+ type: "wall"; /** Name of the `NamedEdge` to hang on. */
11
+ edge: string; /** Position along the edge: a number is units from the edge start, `'50%'` is a fraction. */
12
+ position: Measure;
13
+ /**
14
+ * Vertical placement: a number is an absolute world Y, `'50%'` is a
15
+ * fraction of the wall height. Omitted defaults to the camera's eye height.
16
+ */
17
+ height?: Measure;
18
+ /**
19
+ * Which face of the wall the piece shows on, relative to the edge
20
+ * direction. `'right'` (default) is the right-hand side when walking the
21
+ * edge from its start vertex to its end vertex.
22
+ */
23
+ side?: "right" | "left";
24
+ } | {
25
+ type: "floating"; /** World position `[x, y, z]`. */
26
+ point: Vec3; /** Y-axis rotation in radians. Defaults to `0`. */
27
+ yaw?: number;
28
+ };
29
+ /**
30
+ * A passepartout (mat) sitting between the frame and the image: a flat border
31
+ * that insets the image. Only meaningful when the artwork has a `frame`.
32
+ */
33
+ interface Passepartout {
34
+ /** Mat border width around the image, in world units. Defaults to `6`. */
35
+ width?: number;
36
+ /** Mat color (CSS). Defaults to an off-white. */
37
+ color?: string;
38
+ }
39
+ /**
40
+ * A frame around an artwork. By default it's four flat bars rendered with a
41
+ * solid `color` or a `texture` (e.g. wood). For an ornate, sculpted frame, point
42
+ * `model` at an external glTF/GLB instead. A `passepartout` mat can sit between
43
+ * the frame and the image.
44
+ */
45
+ interface Frame {
46
+ /** Border width (how wide the frame face is), in world units. Defaults to `5`. */
47
+ width?: number;
48
+ /** How far the frame stands proud of the artwork face, in world units. Defaults to `3`. */
49
+ depth?: number;
50
+ /** Frame color (CSS). Defaults to a dark tone. Ignored when `texture` or `model` is set. */
51
+ color?: string;
52
+ /** Surface texture (e.g. wood); takes visual precedence over `color`. */
53
+ texture?: SurfaceTexture;
54
+ /** External glTF/GLB model for an ornate frame; replaces the generated bars. */
55
+ model?: string;
56
+ /** Passepartout mat between the frame and the image. */
57
+ passepartout?: Passepartout;
58
+ }
59
+ /**
60
+ * Commercial status of a piece. Gallery convention: a colored dot signals
61
+ * availability. The dot's color is presentation (decided by the renderer), not
62
+ * data — this is the state it derives from.
63
+ */
64
+ type Availability = "for-sale" | "sold" | "not-for-sale";
65
+ /**
66
+ * The fields a label/info card can show, identified by name. A `fields` array
67
+ * toggles which are visible; the render order is fixed (it is not the array
68
+ * order): title, then author/year, then technique + dimensions on one line, with
69
+ * availability shown as a corner dot. Shared by the physical label (engine) and
70
+ * the overlay info card.
71
+ */
72
+ type ArtworkField = "title" | "author" | "year" | "technique" | "dimensions" | "availability";
73
+ /** Default visibility when a label/card doesn't specify its own `fields`. */
74
+ declare const DEFAULT_ARTWORK_FIELDS: readonly ArtworkField[];
75
+ /**
76
+ * A physical wall label (cartela) materialized next to the piece. Its presence
77
+ * is the opt-in; the engine renders it from the artwork's own data.
78
+ */
79
+ interface ArtworkLabel {
80
+ /** Where the label sits relative to the piece. Defaults to `'below'`. */
81
+ side?: "below" | "left" | "right";
82
+ /** Which fields to show. Defaults to `DEFAULT_ARTWORK_FIELDS`. */
83
+ fields?: ArtworkField[];
84
+ /**
85
+ * Size multiplier over the real physical size (24pt title etc). Defaults to
86
+ * `1` (true to life), but a real cartela is only a few cm tall — tiny and hard
87
+ * to read/click at gallery scale — so bump this (e.g. `3`) for legibility.
88
+ */
89
+ scale?: number;
90
+ /**
91
+ * Fine-tune the placement with an `[x, y]` shift in world units, added on top
92
+ * of the `side` position (the frame is already cleared). `x` runs along the
93
+ * wall (right is positive), `y` is vertical (up is positive).
94
+ */
95
+ offset?: Vec2;
96
+ }
97
+ /** A framed image placed somewhere in the scene. */
98
+ interface Artwork {
99
+ id: string;
100
+ location: ArtworkLocation;
101
+ /** Image URL/path to display. */
102
+ src: string;
103
+ /** `[width, height]` of the artwork, in world units. */
104
+ size: Vec2;
105
+ /**
106
+ * Box depth (volume) in world units: the piece is a box with the image on the
107
+ * front and white sides/back. Defaults to `2`.
108
+ */
109
+ depth?: number;
110
+ /** Optional frame around the artwork. */
111
+ frame?: Frame;
112
+ title?: string;
113
+ technique?: string;
114
+ /** Author / artist name. */
115
+ author?: string;
116
+ /** Year the piece was made. */
117
+ year?: number;
118
+ /**
119
+ * Real-world dimensions for display, e.g. `'92 × 73 cm'`. Distinct from `size`,
120
+ * which is in world units.
121
+ */
122
+ dimensions?: string;
123
+ /** Commercial status; drives the availability dot. */
124
+ availability?: Availability;
125
+ /** Long-form caption / catalog entry, shown in the expanded info panel. */
126
+ description?: string;
127
+ /**
128
+ * External URL for the piece (a catalog page, or a marketplace listing when
129
+ * it's for sale). Surfaced as a link in the expanded info panel.
130
+ */
131
+ href?: string;
132
+ /** When present, a physical wall label is materialized next to the piece. */
133
+ label?: ArtworkLabel;
134
+ /**
135
+ * Max distance (world units) from which the piece can be clicked to open its
136
+ * info panel. A gate so far-away pieces aren't interactive. Absent = no gate.
137
+ */
138
+ infoRadius?: number;
139
+ }
140
+ declare module "@scenography/scene" {
141
+ interface Scene {
142
+ /** Framed images placed in the space — provided by `@scenography/plugin-artwork`. */
143
+ artworks?: Artwork[];
144
+ }
145
+ }
146
+ //#endregion
147
+ //#region src/data/validate.d.ts
148
+ /**
149
+ * Validate one artwork. `edgeNames` is the set of wall-edge names in the scene,
150
+ * used to check `location.edge` references. Returns every problem found, in
151
+ * order, with paths relative to the artwork (the caller scopes them).
152
+ */
153
+ declare const validateArtwork: (artwork: Artwork, edgeNames: Set<string>) => ValidationResult;
154
+ /**
155
+ * Validate an artwork against its scene: resolves the set of valid wall-edge
156
+ * names from the scene's walls (the wall plugin's data half) so `location.edge`
157
+ * references are checked. Floating pieces don't need any wall.
158
+ */
159
+ declare const validateArtworkInScene: (artwork: Artwork, ctx: SceneContext) => ValidationResult;
160
+ //#endregion
161
+ //#region src/data/plugin.d.ts
162
+ /**
163
+ * The data half of the artwork plugin: owns the `artworks` scene key (a
164
+ * collection) and validates each piece — including that a wall-hung piece
165
+ * references a real wall edge. Pure — no three.js.
166
+ */
167
+ declare const artworkData: DataPlugin<Artwork>;
168
+ //#endregion
169
+ export { ArtworkField as a, Availability as c, Passepartout as d, Artwork as i, DEFAULT_ARTWORK_FIELDS as l, validateArtwork as n, ArtworkLabel as o, validateArtworkInScene as r, ArtworkLocation as s, artworkData as t, Frame as u };
170
+ //# sourceMappingURL=index-XUkegdM8.d.mts.map