@promptframe/component-kit 0.1.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.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @promptframe/component-kit
2
+
3
+ Stable helper package for Remotion marketplace component authors.
4
+
5
+ This package is intentionally small. It exposes standard stamps, preview constraints, and deterministic timing helpers that external/private components can depend on without importing renderer internals.
6
+
7
+ ## Public Entrypoints
8
+
9
+ ```ts
10
+ import { getComponentStandardStamp } from '@promptframe/component-kit';
11
+ import { COMPONENT_PREVIEW_CONSTRAINTS } from '@promptframe/component-kit/preview';
12
+ import { createDurationTimeline } from '@promptframe/component-kit/timing';
13
+ ```
14
+
15
+ The package does not replace server-side admission. Uploads must still pass build admission, deterministic security review, manifest/schema/policy validation, evidence indexing, artifact mirror, and `get_component_schema` before Director can use the component.
16
+
17
+ ## Authoring Rules
18
+
19
+ - Do not import `@remotion-media/renderer` or repo-internal paths from component packages.
20
+ - Keep Remotion/React as the component project's own dependencies; `component-kit` declares them as optional peers only for future helper compatibility.
21
+ - Treat `getComponentStandardStamp()` as a local development stamp. The platform API and server admission remain the final SSOT.
22
+ - Use `component-market validate --online` before upload and `component-market upload` for admission.
23
+
24
+ ## Publishing Status
25
+
26
+ The package is public-release ready. The confirmed public coordinates are:
27
+
28
+ - npm package: `@promptframe/component-kit`
29
+ - npm org/scope: `@promptframe`
30
+ - release repository: `https://github.com/ty-teams/promptframe-component-kit`
31
+ - license: MIT
32
+
33
+ First publish still requires the npm account's OTP/2FA path and npm Trusted Publisher configuration; do not add an `NPM_TOKEN` secret.
34
+
35
+ Marketplace/private components themselves do not publish to npm. They still move through platform CLI upload, build admission, security review, schema/policy/receipt generation, evidence indexing, artifact mirror, and resolver-backed preview/render.
@@ -0,0 +1,4 @@
1
+ export * from './preview.js';
2
+ export * from './standard.js';
3
+ export * from './timing.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './preview.js';
2
+ export * from './standard.js';
3
+ export * from './timing.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface ComponentPreviewConstraints {
2
+ maxDurationFrames: number;
3
+ maxWidth: number;
4
+ maxHeight: number;
5
+ maxFps: number;
6
+ }
7
+ export declare const COMPONENT_PREVIEW_CONSTRAINTS: ComponentPreviewConstraints;
8
+ export interface ComponentPreviewPropsEnvelope<TProps extends Record<string, unknown> = Record<string, unknown>> {
9
+ durationFrames: number;
10
+ fps: 30;
11
+ width: number;
12
+ height: number;
13
+ props: TProps;
14
+ }
15
+ export declare function assertPreviewWithinConstraints(preview: ComponentPreviewPropsEnvelope, constraints?: ComponentPreviewConstraints): void;
16
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,2BAA2B;IAC1C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,6BAA6B,EAAE,2BAK3C,CAAC;AAEF,MAAM,WAAW,6BAA6B,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7G,cAAc,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,EAAE,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,6BAA6B,EACtC,WAAW,GAAE,2BAA2D,GACvE,IAAI,CAON"}
@@ -0,0 +1,20 @@
1
+ export const COMPONENT_PREVIEW_CONSTRAINTS = {
2
+ maxDurationFrames: 180,
3
+ maxWidth: 1280,
4
+ maxHeight: 720,
5
+ maxFps: 30,
6
+ };
7
+ export function assertPreviewWithinConstraints(preview, constraints = COMPONENT_PREVIEW_CONSTRAINTS) {
8
+ const errors = [];
9
+ if (preview.durationFrames > constraints.maxDurationFrames)
10
+ errors.push(`durationFrames must be <= ${constraints.maxDurationFrames}`);
11
+ if (preview.width > constraints.maxWidth)
12
+ errors.push(`width must be <= ${constraints.maxWidth}`);
13
+ if (preview.height > constraints.maxHeight)
14
+ errors.push(`height must be <= ${constraints.maxHeight}`);
15
+ if (preview.fps > constraints.maxFps)
16
+ errors.push(`fps must be <= ${constraints.maxFps}`);
17
+ if (errors.length > 0)
18
+ throw new Error(errors.join('; '));
19
+ }
20
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,6BAA6B,GAAgC;IACxE,iBAAiB,EAAE,GAAG;IACtB,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,EAAE;CACX,CAAC;AAUF,MAAM,UAAU,8BAA8B,CAC5C,OAAsC,EACtC,cAA2C,6BAA6B;IAExE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,OAAO,CAAC,cAAc,GAAG,WAAW,CAAC,iBAAiB;QAAE,MAAM,CAAC,IAAI,CAAC,6BAA6B,WAAW,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACtI,IAAI,OAAO,CAAC,KAAK,GAAG,WAAW,CAAC,QAAQ;QAAE,MAAM,CAAC,IAAI,CAAC,oBAAoB,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;IAClG,IAAI,OAAO,CAAC,MAAM,GAAG,WAAW,CAAC,SAAS;QAAE,MAAM,CAAC,IAAI,CAAC,qBAAqB,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC;IACtG,IAAI,OAAO,CAAC,GAAG,GAAG,WAAW,CAAC,MAAM;QAAE,MAAM,CAAC,IAAI,CAAC,kBAAkB,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1F,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare const COMPONENT_STANDARD_VERSION: "component-standard.v0.1.0";
2
+ export declare const COMPONENT_STANDARD_SOURCE_HASH: "sha256:8c1e01c36155b4b646981064d24df9bd8cda501fd9cd9da93e5b62f40db22d52";
3
+ export declare const COMPONENT_STANDARD_SOURCE_PATH: "docs/requirements/REQ-073/component-standard.md";
4
+ export declare const COMPONENT_KIT_VERSION: "0.1.0";
5
+ export declare const COMPONENT_MANIFEST_SCHEMA_VERSION: "component-manifest.v0.1.0";
6
+ export declare const COMPONENT_REVIEW_CHECKLIST_VERSION: "component-review-checklist.v0.1.0";
7
+ export declare function getComponentStandardStamp(): {
8
+ sourceVersion: "component-standard.v0.1.0";
9
+ sourceHash: "sha256:8c1e01c36155b4b646981064d24df9bd8cda501fd9cd9da93e5b62f40db22d52";
10
+ sourcePath: "docs/requirements/REQ-073/component-standard.md";
11
+ componentKitVersion: "0.1.0";
12
+ manifestSchemaVersion: "component-manifest.v0.1.0";
13
+ reviewChecklistVersion: "component-review-checklist.v0.1.0";
14
+ };
15
+ //# sourceMappingURL=standard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard.d.ts","sourceRoot":"","sources":["../src/standard.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,EAAG,2BAAoC,CAAC;AAC/E,eAAO,MAAM,8BAA8B,EAAG,yEAAkF,CAAC;AACjI,eAAO,MAAM,8BAA8B,EAAG,iDAA0D,CAAC;AACzG,eAAO,MAAM,qBAAqB,EAAG,OAAgB,CAAC;AACtD,eAAO,MAAM,iCAAiC,EAAG,2BAAoC,CAAC;AACtF,eAAO,MAAM,kCAAkC,EAAG,mCAA4C,CAAC;AAE/F,wBAAgB,yBAAyB;;;;;;;EASxC"}
@@ -0,0 +1,17 @@
1
+ export const COMPONENT_STANDARD_VERSION = 'component-standard.v0.1.0';
2
+ export const COMPONENT_STANDARD_SOURCE_HASH = 'sha256:8c1e01c36155b4b646981064d24df9bd8cda501fd9cd9da93e5b62f40db22d52';
3
+ export const COMPONENT_STANDARD_SOURCE_PATH = 'docs/requirements/REQ-073/component-standard.md';
4
+ export const COMPONENT_KIT_VERSION = '0.1.0';
5
+ export const COMPONENT_MANIFEST_SCHEMA_VERSION = 'component-manifest.v0.1.0';
6
+ export const COMPONENT_REVIEW_CHECKLIST_VERSION = 'component-review-checklist.v0.1.0';
7
+ export function getComponentStandardStamp() {
8
+ return {
9
+ sourceVersion: COMPONENT_STANDARD_VERSION,
10
+ sourceHash: COMPONENT_STANDARD_SOURCE_HASH,
11
+ sourcePath: COMPONENT_STANDARD_SOURCE_PATH,
12
+ componentKitVersion: COMPONENT_KIT_VERSION,
13
+ manifestSchemaVersion: COMPONENT_MANIFEST_SCHEMA_VERSION,
14
+ reviewChecklistVersion: COMPONENT_REVIEW_CHECKLIST_VERSION,
15
+ };
16
+ }
17
+ //# sourceMappingURL=standard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standard.js","sourceRoot":"","sources":["../src/standard.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG,2BAAoC,CAAC;AAC/E,MAAM,CAAC,MAAM,8BAA8B,GAAG,yEAAkF,CAAC;AACjI,MAAM,CAAC,MAAM,8BAA8B,GAAG,iDAA0D,CAAC;AACzG,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAgB,CAAC;AACtD,MAAM,CAAC,MAAM,iCAAiC,GAAG,2BAAoC,CAAC;AACtF,MAAM,CAAC,MAAM,kCAAkC,GAAG,mCAA4C,CAAC;AAE/F,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,aAAa,EAAE,0BAA0B;QACzC,UAAU,EAAE,8BAA8B;QAC1C,UAAU,EAAE,8BAA8B;QAC1C,mBAAmB,EAAE,qBAAqB;QAC1C,qBAAqB,EAAE,iCAAiC;QACxD,sBAAsB,EAAE,kCAAkC;KAC3D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface DurationTimeline {
2
+ actualDuration: number;
3
+ designedDuration: number;
4
+ actionDuration: number;
5
+ scale: number;
6
+ holdFrames: number;
7
+ at(frame: number): number;
8
+ frame(value: number): number;
9
+ range(start: number, end: number): [number, number];
10
+ stagger(baseDelay: number, index: number, baseStagger: number): number;
11
+ scaledFps(actualFps: number): number;
12
+ }
13
+ export interface CreateDurationTimelineParams {
14
+ actualDuration: number;
15
+ designedDuration: number;
16
+ minScale?: number;
17
+ }
18
+ export declare function createDurationTimeline(params: CreateDurationTimelineParams): DurationTimeline;
19
+ export interface ScaledSpringTiming {
20
+ frame: number;
21
+ fps: number;
22
+ }
23
+ export declare function getScaledSpringTiming(frame: number, fps: number, delay: number, timeline: DurationTimeline): ScaledSpringTiming;
24
+ //# sourceMappingURL=timing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpD,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IACvE,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,4BAA4B;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,4BAA4B,GAAG,gBAAgB,CA2B7F;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,gBAAgB,GACzB,kBAAkB,CAKpB"}
package/dist/timing.js ADDED
@@ -0,0 +1,39 @@
1
+ export function createDurationTimeline(params) {
2
+ const actualDuration = positiveInteger(params.actualDuration, 'actualDuration');
3
+ const designedDuration = positiveInteger(params.designedDuration, 'designedDuration');
4
+ const minScale = params.minScale ?? 0.3;
5
+ if (minScale <= 0 || minScale > 1)
6
+ throw new Error('minScale must be > 0 and <= 1');
7
+ const rawScale = Math.min(actualDuration, designedDuration) / designedDuration;
8
+ const scale = Math.max(minScale, rawScale);
9
+ const actionDuration = Math.round(designedDuration * scale);
10
+ const holdFrames = Math.max(0, actualDuration - actionDuration);
11
+ const at = (frame) => Math.round(frame * scale);
12
+ const range = (start, end) => [at(start), at(end)];
13
+ const stagger = (baseDelay, index, baseStagger) => at(baseDelay + index * baseStagger);
14
+ const scaledFps = (actualFps) => scale < 1 ? actualFps / scale : actualFps;
15
+ return {
16
+ actualDuration,
17
+ designedDuration,
18
+ actionDuration,
19
+ scale,
20
+ holdFrames,
21
+ at,
22
+ frame: at,
23
+ range,
24
+ stagger,
25
+ scaledFps,
26
+ };
27
+ }
28
+ export function getScaledSpringTiming(frame, fps, delay, timeline) {
29
+ return {
30
+ frame: Math.max(0, frame - timeline.at(delay)),
31
+ fps: timeline.scaledFps(fps),
32
+ };
33
+ }
34
+ function positiveInteger(value, label) {
35
+ if (!Number.isInteger(value) || value <= 0)
36
+ throw new Error(`${label} must be a positive integer`);
37
+ return value;
38
+ }
39
+ //# sourceMappingURL=timing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing.js","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,sBAAsB,CAAC,MAAoC;IACzE,MAAM,cAAc,GAAG,eAAe,CAAC,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAChF,MAAM,gBAAgB,GAAG,eAAe,CAAC,MAAM,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,GAAG,CAAC;IACxC,IAAI,QAAQ,IAAI,CAAC,IAAI,QAAQ,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAEpF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3C,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,KAAK,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,GAAG,cAAc,CAAC,CAAC;IAChE,MAAM,EAAE,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,GAAW,EAAoB,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrF,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAE,KAAa,EAAE,WAAmB,EAAE,EAAE,CAAC,EAAE,CAAC,SAAS,GAAG,KAAK,GAAG,WAAW,CAAC,CAAC;IAC/G,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAEnF,OAAO;QACL,cAAc;QACd,gBAAgB;QAChB,cAAc;QACd,KAAK;QACL,UAAU;QACV,EAAE;QACF,KAAK,EAAE,EAAE;QACT,KAAK;QACL,OAAO;QACP,SAAS;KACV,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,qBAAqB,CACnC,KAAa,EACb,GAAW,EACX,KAAa,EACb,QAA0B;IAE1B,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QAC9C,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC;KAC7B,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,KAAa,EAAE,KAAa;IACnD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,6BAA6B,CAAC,CAAC;IACnG,OAAO,KAAK,CAAC;AACf,CAAC"}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@promptframe/component-kit",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ },
18
+ "./standard": {
19
+ "import": "./dist/standard.js",
20
+ "types": "./dist/standard.d.ts"
21
+ },
22
+ "./preview": {
23
+ "import": "./dist/preview.js",
24
+ "types": "./dist/preview.d.ts"
25
+ },
26
+ "./timing": {
27
+ "import": "./dist/timing.js",
28
+ "types": "./dist/timing.d.ts"
29
+ },
30
+ "./package.json": {
31
+ "default": "./package.json"
32
+ }
33
+ },
34
+ "publishConfig": {
35
+ "registry": "https://registry.npmjs.org/",
36
+ "access": "public",
37
+ "provenance": true
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/ty-teams/promptframe-component-kit.git",
42
+ "directory": "packages/component-kit"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/ty-teams/promptframe-component-kit/issues"
46
+ },
47
+ "homepage": "https://github.com/ty-teams/promptframe-component-kit#readme",
48
+ "scripts": {
49
+ "build": "tsc",
50
+ "lint": "tsc --noEmit",
51
+ "test": "node --import tsx --test test/*.test.ts",
52
+ "clean": "rm -rf dist"
53
+ },
54
+ "dependencies": {},
55
+ "peerDependencies": {
56
+ "react": "^19.0.0",
57
+ "remotion": "^4.0.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "react": {
61
+ "optional": true
62
+ },
63
+ "remotion": {
64
+ "optional": true
65
+ }
66
+ },
67
+ "devDependencies": {
68
+ "tsx": "^4.19.0",
69
+ "typescript": "~5.7.0"
70
+ }
71
+ }