@chuzi/shared 1.3.37 → 1.3.39

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.
@@ -1,18 +1,18 @@
1
1
  // src/realms/cosmos/appearance.ts
2
2
  var DEFAULT_CONSTELLATION_APPEARANCE = {
3
- titleFontSize: 15,
4
- titleOpacity: 0.48,
5
- titleColor: "#e8f0ff",
6
- titleLetterSpacing: 4,
7
- titleYOffset: 14,
8
- titleDistanceFactor: 18,
3
+ titleFontSize: 34,
4
+ titleOpacity: 0.82,
5
+ titleColor: "#c8dce8",
6
+ titleLetterSpacing: 6,
7
+ titleYOffset: 8,
8
+ titleDistanceFactor: 12,
9
9
  htmlZIndexRange: [8, 0],
10
10
  previewWidth: 148,
11
11
  previewHeight: 96,
12
12
  previewOffsetY: 2.4,
13
13
  labelFontSize: 13,
14
14
  labelLetterSpacing: 1.2,
15
- labelGap: 14,
15
+ labelGap: 10,
16
16
  controlsGridHeight: 92,
17
17
  billboardDistanceFactor: 10,
18
18
  cameraDefaultOffset: [0, 4, 12],
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/realms/cosmos/appearance.ts","../../../src/realms/cosmos/index.ts"],"names":[],"mappings":";AAqCO,IAAM,gCAAA,GAA4D;AAAA,EACvE,aAAA,EAAe,EAAA;AAAA,EACf,YAAA,EAAc,IAAA;AAAA,EACd,UAAA,EAAY,SAAA;AAAA,EACZ,kBAAA,EAAoB,CAAA;AAAA,EACpB,YAAA,EAAc,EAAA;AAAA,EACd,mBAAA,EAAqB,EAAA;AAAA,EACrB,eAAA,EAAiB,CAAC,CAAA,EAAG,CAAC,CAAA;AAAA,EAEtB,YAAA,EAAc,GAAA;AAAA,EACd,aAAA,EAAe,EAAA;AAAA,EACf,cAAA,EAAgB,GAAA;AAAA,EAChB,aAAA,EAAe,EAAA;AAAA,EACf,kBAAA,EAAoB,GAAA;AAAA,EACpB,QAAA,EAAU,EAAA;AAAA,EACV,kBAAA,EAAoB,EAAA;AAAA,EACpB,uBAAA,EAAyB,EAAA;AAAA,EAEzB,mBAAA,EAAqB,CAAC,CAAA,EAAG,CAAA,EAAG,EAAE,CAAA;AAAA,EAC9B,kBAAA,EAAoB,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,EAC5B,eAAA,EAAiB,KAAA;AAAA,EACjB,sBAAA,EAAwB;AAC1B;AAEO,SAAS,6BACd,SAAA,EACyB;AACzB,EAAA,OAAO,EAAE,GAAG,gCAAA,EAAkC,GAAG,SAAA,EAAU;AAC7D;;;AC7CA,IAAM,gBAAA,GAA2C;AAAA,EAC/C,KAAA,EAAO,EAAA;AAAA,EACP,QAAA,EAAU,GAAA;AAAA,EACV,MAAA,EAAQ,CAAA;AAAA,EACR,MAAA,EAAQ,EAAA;AAAA,EACR,OAAA,EAAS,GAAA;AAAA,EACT,KAAA,EAAO,GAAA;AAAA,EACP,WAAA,EAAa,GAAA;AAAA,EACb,SAAA,EAAW;AACb,CAAA;AAEA,IAAM,QAAA,GAAmC;AAAA,EACvC,MAAA,EAAQ,EAAA;AAAA,EACR,IAAA,EAAM,EAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,MAAA,EAAQ,GAAA;AAAA,EACR,KAAA,EAAO,GAAA;AAAA,EACP,OAAA,EAAS,GAAA;AAAA,EACT,UAAA,EAAY;AACd,CAAA;AAEA,SAAS,QAAQ,CAAA,EAAmB;AAClC,EAAA,OAAO,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,CAAA;AACnC;AAEA,SAAS,sBAAsB,IAAA,EAA6B;AAE1D,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,kBAAkB,CAAA;AACnD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,CAAA,GAAI,OAAO,CAAA;AAElC,EAAA,OAAO,OAAA,CAAQ,MAAM,CAAC,CAAA;AACxB;AAEA,SAAS,YAAY,IAAA,EAA6B;AAGhD,EAAA,MAAM,MAAA,GAAS,KAAK,YAAA,IAAgB,CAAA;AACpC,EAAA,OAAO,QAAQ,IAAA,GAAO,IAAA,CAAK,MAAM,CAAA,GAAI,MAAM,IAAI,CAAC,CAAA;AAClD;AAEA,SAAS,UAAU,IAAA,EAA6B;AAC9C,EAAA,MAAM,QAAA,GAAA,CAAY,KAAK,KAAA,IAAS,EAAA,EAAI,aAAY,CAAE,OAAA,CAAQ,WAAW,EAAE,CAAA;AACvE,EAAA,MAAM,OAAA,GAAU,EAAA;AAChB,EAAA,MAAM,OAAA,GAAU,SAAS,OAAO,CAAA;AAChC,EAAA,MAAM,WAAA,GAAc,iBAAiB,QAAQ,CAAA;AAC7C,EAAA,IAAI,OAAA,KAAY,QAAW,OAAO,OAAA;AAClC,EAAA,IAAI,WAAA,KAAgB,QAAW,OAAO,WAAA;AACtC,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,YAAY,IAAA,EAAgC;AAGnD,EAAA,OAAO,IAAA,CAAK,YAAY,SAAA,GAAY,KAAA;AACtC;AAEO,IAAM,aAAA,GAA6B,CAAC,IAAA,MAAU;AAAA,EACnD,QAAA,EAAU,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA;AAAA,EAClB,KAAA,EAAO,YAAY,IAAI,CAAA;AAAA,EACvB,GAAA,EAAK,UAAU,IAAI,CAAA;AAAA,EACnB,SAAA,EAAW,sBAAsB,IAAI,CAAA;AAAA,EACrC,KAAA,EAAO,YAAY,IAAI,CAAA;AAAA,EACvB,QAAA,EAAU;AAAA,IACR,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,YAAY,IAAA,CAAK,kBAAA;AAAA,IACjB,OAAO,IAAA,CAAK;AAAA;AAEhB,CAAA;AAEO,IAAM,YAAA,GAA6B;AAAA,EACxC,kBAAA,EAAoB,EAAA;AAAA,EACpB,aAAA,EAAe,IAAA;AAAA,EACf,WAAA,EAAa,GAAA;AAAA,EACb,gBAAA,EAAkB,GAAA;AAAA,EAClB,cAAA,EAAgB;AAClB;AAQO,IAAM,WAAA,GAA4B;AAAA;AAAA;AAAA,EAGvC,WAAA,EAAa,+BAAA;AAAA,EACb,UAAA,EAAY,gCAAA;AAAA,EACZ,YAAA,EAAc,2BAAA;AAAA,EACd,UAAA,EAAY;AACd","file":"index.js","sourcesContent":["/**\n * Constellation presentation tokens — consumed by the cosmos realm camera,\n * star billboards, and ghost title typography. Admins can override via\n * saved experience / appearance templates.\n */\nexport interface ConstellationAppearance {\n /** Rainbow arc title above the constellation (SVG px). */\n titleFontSize: number;\n titleOpacity: number;\n titleColor: string;\n titleLetterSpacing: number;\n /** World-space lift above constellation center. */\n titleYOffset: number;\n /** Html distance factor for the arc title billboard. */\n titleDistanceFactor: number;\n /** DOM z-index ceiling for constellation Html overlays (keep below editor). */\n htmlZIndexRange: [number, number];\n\n /** Per-star preview card + label. */\n previewWidth: number;\n previewHeight: number;\n previewOffsetY: number;\n labelFontSize: number;\n labelLetterSpacing: number;\n labelGap: number;\n /** Reserved space below preview for the 2×2 HUD control grid (px). */\n controlsGridHeight: number;\n billboardDistanceFactor: number;\n\n /** Camera — floating-in-space slide between stars. */\n cameraDefaultOffset: [number, number, number];\n cameraTargetOffset: [number, number, number];\n /** 0–1 lerp factor per frame while gliding (lower = smoother / pressurized). */\n cameraSlideLerp: number;\n cameraArrivalThreshold: number;\n}\n\nexport const DEFAULT_CONSTELLATION_APPEARANCE: ConstellationAppearance = {\n titleFontSize: 15,\n titleOpacity: 0.48,\n titleColor: \"#e8f0ff\",\n titleLetterSpacing: 4,\n titleYOffset: 14,\n titleDistanceFactor: 18,\n htmlZIndexRange: [8, 0],\n\n previewWidth: 148,\n previewHeight: 96,\n previewOffsetY: 2.4,\n labelFontSize: 13,\n labelLetterSpacing: 1.2,\n labelGap: 14,\n controlsGridHeight: 92,\n billboardDistanceFactor: 10,\n\n cameraDefaultOffset: [0, 4, 12],\n cameraTargetOffset: [0, 0, 0],\n cameraSlideLerp: 0.035,\n cameraArrivalThreshold: 0.08,\n};\n\nexport function mergeConstellationAppearance(\n overrides?: Partial<ConstellationAppearance>,\n): ConstellationAppearance {\n return { ...DEFAULT_CONSTELLATION_APPEARANCE, ...overrides };\n}\n","import type { StoryListItem } from \"../../types/index.js\";\nimport type { AtomMapping, AtomState, AudioPalette, MotionTokens } from \"../index.js\";\n\n/**\n * COSMOS realm — pure-data layer. The 3D components (World, Star, NavRig,\n * EngageTransition) live in a follow-up package once the JSX build is\n * wired up; this file owns the realm's mapping and tuning constants so\n * they can be consumed today by any non-3D surface (catalog list, search,\n * preview cards, sound design tooling).\n *\n * Mapping rationale:\n * runtime → scale (longer film = bigger star)\n * popularity → intensity (more watches = brighter)\n * mood → hue (warm/cool palette by tone)\n * genre → spectral hint (small offset on top of mood, reads as\n * \"stellar class\" — drama is yellow-G,\n * thriller is blue-O, romance is red-M).\n * state → orbit-ring rendering (handled by Atom component).\n */\n\nconst GENRE_HUE_OFFSET: Record<string, number> = {\n drama: 50,\n thriller: 220,\n horror: 0,\n comedy: 35,\n romance: 340,\n scifi: 200,\n documentary: 180,\n animation: 280,\n};\n\nconst MOOD_HUE: Record<string, number> = {\n bright: 50,\n warm: 25,\n bittersweet: 290,\n somber: 230,\n tense: 210,\n playful: 110,\n melancholy: 250,\n};\n\nfunction clamp01(n: number): number {\n return Math.max(0, Math.min(1, n));\n}\n\nfunction popularityToIntensity(film: StoryListItem): number {\n // Log-compress: a 100x more-watched film should not be 100x brighter.\n const watches = Math.max(0, film.watch_starts_count);\n const log = Math.log10(1 + watches);\n // Rough cap at ~6 (1M watches saturates the scale).\n return clamp01(log / 6);\n}\n\nfunction deriveScale(film: StoryListItem): number {\n // We don't have runtime in StoryListItem yet; proxy with scenes_count.\n // Caps the starfield from going visually noisy.\n const scenes = film.scenes_count ?? 1;\n return clamp01(0.25 + Math.log10(1 + scenes) / 4);\n}\n\nfunction deriveHue(film: StoryListItem): number {\n const genreKey = (film.genre ?? \"\").toLowerCase().replace(/[^a-z]/g, \"\");\n const moodKey = \"\";\n const moodHue = MOOD_HUE[moodKey];\n const genreOffset = GENRE_HUE_OFFSET[genreKey];\n if (moodHue !== undefined) return moodHue;\n if (genreOffset !== undefined) return genreOffset;\n return 210;\n}\n\nfunction deriveState(film: StoryListItem): AtomState {\n // Without per-user progress threaded through, default; the consuming\n // app will overlay state from CatalogResponse.meta.progress.\n return film.published ? \"default\" : \"new\";\n}\n\nexport const cosmosMapping: AtomMapping = (film) => ({\n position: [0, 0, 0], // assigned by the realm's spatial layouter\n scale: deriveScale(film),\n hue: deriveHue(film),\n intensity: popularityToIntensity(film),\n state: deriveState(film),\n metadata: {\n title: film.title,\n popularity: film.watch_starts_count,\n genre: film.genre,\n },\n});\n\nexport const cosmosMotion: MotionTokens = {\n flightAcceleration: 14,\n flightDamping: 0.92,\n focusEaseMs: 380,\n engageDurationMs: 900,\n backDurationMs: 900,\n};\n\nexport {\n DEFAULT_CONSTELLATION_APPEARANCE,\n mergeConstellationAppearance,\n} from \"./appearance.js\";\nexport type { ConstellationAppearance } from \"./appearance.js\";\n\nexport const cosmosAudio: AudioPalette = {\n // Asset paths are resolved by the host app's asset bundler; chuzi-shared\n // only declares the contract. Replace with CDN URLs at integration time.\n ambientLoop: \"audio/cosmos/ambient-deep.ogg\",\n focusChime: \"audio/cosmos/focus-shimmer.ogg\",\n engageImpact: \"audio/cosmos/dolly-in.ogg\",\n backWhoosh: \"audio/cosmos/dolly-out.ogg\",\n};\n"]}
1
+ {"version":3,"sources":["../../../src/realms/cosmos/appearance.ts","../../../src/realms/cosmos/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Constellation presentation tokens — consumed by the cosmos realm camera,\n * star billboards, and ghost title typography. Admins can override via\n * saved experience / appearance templates.\n */\nexport interface ConstellationAppearance {\n /** Rainbow arc title above the constellation (SVG px). */\n titleFontSize: number;\n titleOpacity: number;\n titleColor: string;\n titleLetterSpacing: number;\n /** World-space lift above constellation center. */\n titleYOffset: number;\n /** Html distance factor for the arc title billboard. */\n titleDistanceFactor: number;\n /** DOM z-index ceiling for constellation Html overlays (keep below editor). */\n htmlZIndexRange: [number, number];\n\n /** Per-star preview card + label. */\n previewWidth: number;\n previewHeight: number;\n previewOffsetY: number;\n labelFontSize: number;\n labelLetterSpacing: number;\n labelGap: number;\n /** Reserved space below preview for the 2×2 HUD control grid (px). */\n controlsGridHeight: number;\n billboardDistanceFactor: number;\n\n /** Camera — floating-in-space slide between stars. */\n cameraDefaultOffset: [number, number, number];\n cameraTargetOffset: [number, number, number];\n /** 0–1 lerp factor per frame while gliding (lower = smoother / pressurized). */\n cameraSlideLerp: number;\n cameraArrivalThreshold: number;\n}\n\nexport const DEFAULT_CONSTELLATION_APPEARANCE: ConstellationAppearance = {\n titleFontSize: 34,\n titleOpacity: 0.82,\n titleColor: \"#c8dce8\",\n titleLetterSpacing: 6,\n titleYOffset: 8,\n titleDistanceFactor: 12,\n htmlZIndexRange: [8, 0],\n\n previewWidth: 148,\n previewHeight: 96,\n previewOffsetY: 2.4,\n labelFontSize: 13,\n labelLetterSpacing: 1.2,\n labelGap: 10,\n controlsGridHeight: 92,\n billboardDistanceFactor: 10,\n\n cameraDefaultOffset: [0, 4, 12],\n cameraTargetOffset: [0, 0, 0],\n cameraSlideLerp: 0.035,\n cameraArrivalThreshold: 0.08,\n};\n\nexport function mergeConstellationAppearance(\n overrides?: Partial<ConstellationAppearance>,\n): ConstellationAppearance {\n return { ...DEFAULT_CONSTELLATION_APPEARANCE, ...overrides };\n}\n","import type { StoryListItem } from \"../../types/index.js\";\nimport type { AtomMapping, AtomState, AudioPalette, MotionTokens } from \"../index.js\";\n\n/**\n * COSMOS realm — pure-data layer. The 3D components (World, Star, NavRig,\n * EngageTransition) live in a follow-up package once the JSX build is\n * wired up; this file owns the realm's mapping and tuning constants so\n * they can be consumed today by any non-3D surface (catalog list, search,\n * preview cards, sound design tooling).\n *\n * Mapping rationale:\n * runtime → scale (longer film = bigger star)\n * popularity → intensity (more watches = brighter)\n * mood → hue (warm/cool palette by tone)\n * genre → spectral hint (small offset on top of mood, reads as\n * \"stellar class\" — drama is yellow-G,\n * thriller is blue-O, romance is red-M).\n * state → orbit-ring rendering (handled by Atom component).\n */\n\nconst GENRE_HUE_OFFSET: Record<string, number> = {\n drama: 50,\n thriller: 220,\n horror: 0,\n comedy: 35,\n romance: 340,\n scifi: 200,\n documentary: 180,\n animation: 280,\n};\n\nconst MOOD_HUE: Record<string, number> = {\n bright: 50,\n warm: 25,\n bittersweet: 290,\n somber: 230,\n tense: 210,\n playful: 110,\n melancholy: 250,\n};\n\nfunction clamp01(n: number): number {\n return Math.max(0, Math.min(1, n));\n}\n\nfunction popularityToIntensity(film: StoryListItem): number {\n // Log-compress: a 100x more-watched film should not be 100x brighter.\n const watches = Math.max(0, film.watch_starts_count);\n const log = Math.log10(1 + watches);\n // Rough cap at ~6 (1M watches saturates the scale).\n return clamp01(log / 6);\n}\n\nfunction deriveScale(film: StoryListItem): number {\n // We don't have runtime in StoryListItem yet; proxy with scenes_count.\n // Caps the starfield from going visually noisy.\n const scenes = film.scenes_count ?? 1;\n return clamp01(0.25 + Math.log10(1 + scenes) / 4);\n}\n\nfunction deriveHue(film: StoryListItem): number {\n const genreKey = (film.genre ?? \"\").toLowerCase().replace(/[^a-z]/g, \"\");\n const moodKey = \"\";\n const moodHue = MOOD_HUE[moodKey];\n const genreOffset = GENRE_HUE_OFFSET[genreKey];\n if (moodHue !== undefined) return moodHue;\n if (genreOffset !== undefined) return genreOffset;\n return 210;\n}\n\nfunction deriveState(film: StoryListItem): AtomState {\n // Without per-user progress threaded through, default; the consuming\n // app will overlay state from CatalogResponse.meta.progress.\n return film.published ? \"default\" : \"new\";\n}\n\nexport const cosmosMapping: AtomMapping = (film) => ({\n position: [0, 0, 0], // assigned by the realm's spatial layouter\n scale: deriveScale(film),\n hue: deriveHue(film),\n intensity: popularityToIntensity(film),\n state: deriveState(film),\n metadata: {\n title: film.title,\n popularity: film.watch_starts_count,\n genre: film.genre,\n },\n});\n\nexport const cosmosMotion: MotionTokens = {\n flightAcceleration: 14,\n flightDamping: 0.92,\n focusEaseMs: 380,\n engageDurationMs: 900,\n backDurationMs: 900,\n};\n\nexport {\n DEFAULT_CONSTELLATION_APPEARANCE,\n mergeConstellationAppearance,\n} from \"./appearance.js\";\nexport type { ConstellationAppearance } from \"./appearance.js\";\n\nexport const cosmosAudio: AudioPalette = {\n // Asset paths are resolved by the host app's asset bundler; chuzi-shared\n // only declares the contract. Replace with CDN URLs at integration time.\n ambientLoop: \"audio/cosmos/ambient-deep.ogg\",\n focusChime: \"audio/cosmos/focus-shimmer.ogg\",\n engageImpact: \"audio/cosmos/dolly-in.ogg\",\n backWhoosh: \"audio/cosmos/dolly-out.ogg\",\n};\n"]}
@@ -72,6 +72,67 @@ interface PublicDirectorProfile {
72
72
  realm: RealmId | null;
73
73
  stories_count: number;
74
74
  }
75
+ type CreditPool = "watch" | "create";
76
+ interface CreditBalance {
77
+ watch: number;
78
+ create: number;
79
+ }
80
+ interface CreditPack {
81
+ id: string;
82
+ pool: CreditPool;
83
+ name: string;
84
+ credits: number;
85
+ price_cents: number;
86
+ price: string;
87
+ }
88
+ interface PaymentMethod {
89
+ id: string;
90
+ type: string;
91
+ last_four: string;
92
+ label: string | null;
93
+ is_default: boolean;
94
+ created_at: string;
95
+ }
96
+ interface CreditBalanceResponse {
97
+ balance: CreditBalance;
98
+ }
99
+ interface UploadCostResponse {
100
+ file_size_bytes: number;
101
+ file_size_mb: number;
102
+ base_size_mb: number;
103
+ credits_needed: number;
104
+ balance: CreditBalance;
105
+ can_afford: boolean;
106
+ }
107
+ interface PurchaseCreditsRequest {
108
+ pack_id: string;
109
+ payment_method_id?: string;
110
+ }
111
+ interface PurchaseAmountRequest {
112
+ pool: CreditPool;
113
+ credits: number;
114
+ payment_method_id?: string;
115
+ }
116
+ interface PurchaseCreditsResponse {
117
+ message: string;
118
+ transaction_id?: string;
119
+ balance: CreditBalance;
120
+ }
121
+ interface StorePaymentMethodRequest {
122
+ type?: string;
123
+ last_four: string;
124
+ label?: string;
125
+ }
126
+ interface GrantCreditsRequest {
127
+ user_id: string;
128
+ pool: CreditPool;
129
+ amount: number;
130
+ note?: string;
131
+ }
132
+ interface GrantCreditsResponse {
133
+ message: string;
134
+ balance: CreditBalance;
135
+ }
75
136
  interface StoryListItem {
76
137
  id: string;
77
138
  title: string;
@@ -583,4 +644,4 @@ declare function computeSceneVisibility(sceneList: Pick<SceneListItem, "is_title
583
644
  endingSeen: boolean;
584
645
  }): SceneVisibility[];
585
646
 
586
- export { type AcceptGenerationRequest, type AcceptGenerationResponse, type AiGeneration, type AiGenerationShowResponse, type AiGenerationStatus, type BookmarkListItem, type BookmarkListResponse, type BookmarkResponse, type CatalogResponse, type ContentRating, type ContentRatingDefinition, type CreateSceneActionRequest, type CreateSceneRequest, type CreateStoryRequest, type EngagementResponse, type GenerateImageRequest, type GenerateImageResponse, type HistoryEntry, type LineType, type LocaleId, type MagicLinkRequest, type MagicLinkRequestResponse, type MagicLinkVerifyRequest, type MagicLinkVerifyResponse, type MediaItem, type MineResponse, type OidcExchangeRequest, type OidcExchangeResponse, type PaginatedLink, type PaginatedResponse, type PasswordLoginRequest, type PasswordLoginResponse, type PlayUrlResponse, type PopularChoice, type PublicDirectorProfile, type RealmConfigResponse, type RealmDefinition, type RealmId, type RegisterMediaRequest, type RegisterMediaResponse, type RejectGenerationRequest, type RejectGenerationResponse, type SaveBookmarkRequest, type SceneActionItem, type SceneActionPayload, type SceneChoice, type SceneListItem, type SceneMapEntry, type SceneMapResponse, type SceneNode, type SceneTextContent, type SceneVisibility, type SourceUrlResponse, type StateUpdate, type StoryListItem, type StoryPreview, type StoryProgress, type StoryStyleResponse, type TagListResponse, type TextLine, type TrackEngagementRequest, type TranscodeRequest, type TranscodeResponse, type TreeGraph, type TreeGraphEdge, type TreeGraphNode, type UpdateLocaleRequest, type UpdateLocaleResponse, type UpdateProfileRequest, type UpdateProfileResponse, type UpdateRealmRequest, type UpdateRealmResponse, type UpdateSceneActionRequest, type UpdateSceneRequest, type UpdateStoryRequest, type UploadUrlRequest, type UploadUrlResponse, type UserProfile, type UsernameAvailabilityResponse, type VisibilityCondition, type VisibilityRules, type WatchSnapshot, computeSceneVisibility };
647
+ export { type AcceptGenerationRequest, type AcceptGenerationResponse, type AiGeneration, type AiGenerationShowResponse, type AiGenerationStatus, type BookmarkListItem, type BookmarkListResponse, type BookmarkResponse, type CatalogResponse, type ContentRating, type ContentRatingDefinition, type CreateSceneActionRequest, type CreateSceneRequest, type CreateStoryRequest, type CreditBalance, type CreditBalanceResponse, type CreditPack, type CreditPool, type EngagementResponse, type GenerateImageRequest, type GenerateImageResponse, type GrantCreditsRequest, type GrantCreditsResponse, type HistoryEntry, type LineType, type LocaleId, type MagicLinkRequest, type MagicLinkRequestResponse, type MagicLinkVerifyRequest, type MagicLinkVerifyResponse, type MediaItem, type MineResponse, type OidcExchangeRequest, type OidcExchangeResponse, type PaginatedLink, type PaginatedResponse, type PasswordLoginRequest, type PasswordLoginResponse, type PaymentMethod, type PlayUrlResponse, type PopularChoice, type PublicDirectorProfile, type PurchaseAmountRequest, type PurchaseCreditsRequest, type PurchaseCreditsResponse, type RealmConfigResponse, type RealmDefinition, type RealmId, type RegisterMediaRequest, type RegisterMediaResponse, type RejectGenerationRequest, type RejectGenerationResponse, type SaveBookmarkRequest, type SceneActionItem, type SceneActionPayload, type SceneChoice, type SceneListItem, type SceneMapEntry, type SceneMapResponse, type SceneNode, type SceneTextContent, type SceneVisibility, type SourceUrlResponse, type StateUpdate, type StorePaymentMethodRequest, type StoryListItem, type StoryPreview, type StoryProgress, type StoryStyleResponse, type TagListResponse, type TextLine, type TrackEngagementRequest, type TranscodeRequest, type TranscodeResponse, type TreeGraph, type TreeGraphEdge, type TreeGraphNode, type UpdateLocaleRequest, type UpdateLocaleResponse, type UpdateProfileRequest, type UpdateProfileResponse, type UpdateRealmRequest, type UpdateRealmResponse, type UpdateSceneActionRequest, type UpdateSceneRequest, type UpdateStoryRequest, type UploadCostResponse, type UploadUrlRequest, type UploadUrlResponse, type UserProfile, type UsernameAvailabilityResponse, type VisibilityCondition, type VisibilityRules, type WatchSnapshot, computeSceneVisibility };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types/index.ts"],"names":[],"mappings":";AA8qBO,SAAS,sBAAA,CACd,WACA,IAAA,EACmB;AACnB,EAAA,IAAI,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,UAAA,EAAY;AACrC,IAAA,OAAO,SAAA,CAAU,IAAI,OAAO,EAAE,WAAW,IAAA,EAAM,MAAA,EAAQ,OAAM,CAAE,CAAA;AAAA,EACjE;AACA,EAAA,OAAO,SAAA,CAAU,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,IAC/B,WAAW,KAAA,CAAM,QAAA;AAAA,IACjB,MAAA,EAAQ,CAAC,KAAA,CAAM;AAAA,GACjB,CAAE,CAAA;AACJ","file":"index.js","sourcesContent":["// ── Auth ──\n\nexport interface MagicLinkRequest {\n email: string;\n username?: string;\n device_name?: string;\n}\n\nexport interface MagicLinkRequestResponse {\n message: string;\n}\n\nexport interface MagicLinkVerifyRequest {\n email: string;\n token: string;\n device_name?: string;\n}\n\nexport interface MagicLinkVerifyResponse {\n token?: string;\n user: UserProfile;\n}\n\nexport interface OidcExchangeRequest {\n code: string;\n code_verifier: string;\n redirect_uri: string;\n device_name?: string;\n}\n\nexport interface OidcExchangeResponse {\n token?: string;\n user: UserProfile;\n}\n\nexport interface PasswordLoginRequest {\n email: string;\n password: string;\n device_name?: string;\n}\n\nexport interface PasswordLoginResponse {\n token?: string;\n user: UserProfile;\n}\n\n// ── User ──\n\nexport interface UserProfile {\n id: string;\n name: string;\n username: string;\n email: string;\n realm: RealmId | null;\n needs_realm_choice: boolean;\n locale: LocaleId | null;\n avatar_url: string | null;\n is_admin: boolean;\n created_at: string;\n}\n\nexport interface UpdateRealmRequest {\n realm: RealmId;\n}\n\nexport interface UpdateRealmResponse {\n realm: RealmId;\n user: UserProfile;\n}\n\nexport interface UpdateProfileRequest {\n name?: string;\n username?: string;\n}\n\nexport interface UpdateProfileResponse {\n user: UserProfile;\n}\n\nexport interface UsernameAvailabilityResponse {\n username: string;\n available: boolean;\n suggestion?: string;\n}\n\nexport interface PublicDirectorProfile {\n id: string;\n name: string;\n avatar_url: string | null;\n realm: RealmId | null;\n stories_count: number;\n}\n\n// ── Catalog ──\n\nexport interface StoryListItem {\n id: string;\n title: string;\n description: string | null;\n genre: string | null;\n content_rating: ContentRating | null;\n published: boolean;\n published_version: number;\n watch_starts_count: number;\n choice_clicks_count: number;\n scenes_count: number;\n choices_count: number;\n tags: string[];\n creator: {\n id: string;\n name: string;\n } | null;\n created_at: string;\n updated_at: string;\n}\n\nexport interface StoryPreview {\n source: \"trailer\" | \"title_scene\" | \"none\";\n title: string;\n preview_url: string | null;\n media_type: string | null;\n is_ready: boolean;\n}\n\nexport interface PopularChoice {\n label: string;\n click_count: number;\n}\n\nexport interface StoryProgress {\n last_scene_id: string | null;\n bookmark_code: string | null;\n playback_seconds: number;\n watched_version: number;\n last_watched_at: string | null;\n}\n\nexport interface CatalogResponse {\n data: StoryListItem[];\n meta: {\n previews: Record<string, StoryPreview>;\n popular_choices: Record<string, PopularChoice[]>;\n creator_avatars: Record<string, string>;\n coverboxes: Record<string, string | null>;\n progress: Record<string, StoryProgress>;\n };\n}\n\nexport interface MineResponse {\n stories: StoryListItem[];\n data?: StoryListItem[];\n progress?: Record<string, StoryProgress>;\n meta: {\n coverboxes: Record<string, string | null>;\n };\n}\n\n// ── Watch / Scene Map ──\n\nexport interface SceneChoice {\n id: string;\n label: string;\n choice_type: string;\n choice_icon: string | null;\n choice_icon_media_id: string | null;\n choice_icon_url: string | null;\n reveal_mode: string;\n start_time_seconds: number | null;\n pause_for_choice: boolean;\n end_time_seconds: number | null;\n target_scene_id: string;\n x: number;\n y: number;\n w: number;\n h: number;\n arrow_rotation: number;\n arrow_scale: number;\n fade_in_ms?: number | null;\n fade_out_ms?: number | null;\n duration_ms?: number | null;\n visibility_rules: VisibilityRules | null;\n state_updates: StateUpdate[];\n}\n\nexport interface VisibilityRules {\n mode: \"all\" | \"any\";\n conditions: VisibilityCondition[];\n}\n\nexport interface VisibilityCondition {\n variable?: string;\n key?: string;\n operator?: string;\n value?: string;\n}\n\nexport interface StateUpdate {\n variable?: string;\n key?: string;\n action?: string;\n value?: string;\n}\n\nexport interface SceneNode {\n type: string;\n properties?: Record<string, unknown>;\n children?: SceneNode[];\n}\n\nexport type SceneActionPayload = Record<string, unknown> | unknown[];\n\nexport interface SceneMapEntry {\n scene_id: string;\n scene_title: string;\n color: string | null;\n is_title: boolean;\n media_title: string | null;\n media_type: string | null;\n media_url: string | null;\n stream_status: string | null;\n choice_style: string;\n choice_overlay_mode: string;\n goto_scene_id: string | null;\n choices: SceneChoice[];\n nodes: SceneNode[];\n}\n\nexport interface TreeGraphNode {\n id: string;\n title: string;\n is_title: boolean;\n is_end: boolean;\n level: number;\n index: number;\n x: number;\n y: number;\n color: string | null;\n}\n\nexport interface TreeGraphEdge {\n id: string;\n source: string;\n target: string;\n type: \"choice\" | \"go_to_scene\";\n}\n\nexport interface TreeGraph {\n nodes: TreeGraphNode[];\n edges: TreeGraphEdge[];\n meta: {\n root_id: string | null;\n level_count: number;\n };\n}\n\nexport interface WatchSnapshot {\n scene_id: string | null;\n state: Record<string, string>;\n history: HistoryEntry[];\n path: string[];\n visited_scene_ids?: string[];\n playback_seconds: number;\n}\n\nexport interface HistoryEntry {\n choice_id: string;\n scene_id: string;\n target_scene_id: string | null;\n at: string | null;\n path_index: number | null;\n}\n\nexport interface SceneMapResponse {\n story: {\n id: string;\n title: string;\n description: string | null;\n genre: string | null;\n content_rating: ContentRating | null;\n published_version: number;\n watch_starts_count: number;\n choice_clicks_count: number;\n };\n start_scene_id: string;\n scene_map: Record<string, SceneMapEntry>;\n tree_graph: TreeGraph;\n initial_snapshot: WatchSnapshot;\n initial_bookmark_code: string | null;\n}\n\n// ── Engagement ──\n\nexport interface TrackEngagementRequest {\n event: \"play_start\" | \"choice_click\";\n choice_id?: string;\n}\n\nexport interface EngagementResponse {\n watch_starts_count: number;\n choice_clicks_count: number;\n}\n\n// ── Bookmarks ──\n\nexport interface SaveBookmarkRequest {\n snapshot: WatchSnapshot;\n}\n\nexport interface BookmarkResponse {\n code: string;\n snapshot: WatchSnapshot;\n}\n\nexport interface BookmarkListItem {\n code: string;\n snapshot: WatchSnapshot;\n updated_at: string;\n}\n\nexport interface BookmarkListResponse {\n bookmarks: BookmarkListItem[];\n}\n\n// ── Pagination ──\n\nexport interface PaginatedLink {\n url: string | null;\n label: string;\n active: boolean;\n}\n\nexport interface PaginatedResponse<T> {\n data: T[];\n current_page: number;\n first_page_url: string;\n from: number | null;\n last_page: number;\n last_page_url: string;\n links: PaginatedLink[];\n next_page_url: string | null;\n path: string;\n per_page: number;\n prev_page_url: string | null;\n to: number | null;\n total: number;\n}\n\n// ── Scenes ──\n\nexport type LineType = \"text\" | \"sound\" | \"image\";\n\nexport interface TextLine {\n id: string;\n type?: LineType;\n media_id?: string;\n html: string;\n appear_at_ms: number;\n fade_in_ms: number;\n fade_out_ms: number;\n duration_ms: number;\n position_x?: number;\n position_y?: number;\n width_pct?: number;\n height_pct?: number;\n persist?: boolean;\n}\n\nexport interface SceneTextContent {\n html: string;\n lines?: TextLine[];\n}\n\nexport interface SceneListItem {\n id: string;\n story_id: string;\n title: string;\n order: number;\n is_title: boolean;\n is_end: boolean;\n color: string | null;\n media_id: string | null;\n alt_media_id: string | null;\n media_mode: \"text\" | \"imagery\" | \"film\" | null;\n text_content: SceneTextContent | null;\n text_style: {\n font_family?: string | null;\n text_color?: string | null;\n background_color?: string | null;\n } | null;\n goto_scene_id?: string | null;\n choice_end_time_seconds?: number | null;\n created_at: string;\n updated_at: string;\n}\n\nexport interface UpdateSceneRequest {\n title?: string;\n order?: number;\n media_id?: string | null;\n alt_media_id?: string | null;\n media_mode?: \"text\" | \"imagery\" | \"film\" | null;\n text_content?: SceneTextContent | null;\n text_style?: {\n font_family?: string | null;\n text_color?: string | null;\n background_color?: string | null;\n } | null;\n color?: string | null;\n is_title?: boolean;\n is_end?: boolean;\n goto_scene_id?: string | null;\n choice_style?: string | null;\n choice_overlay_mode?: string | null;\n choice_reveal_mode?: string | null;\n choice_start_time_seconds?: number | null;\n choice_pause_for_choice?: boolean;\n choice_end_time_seconds?: number | null;\n}\n\nexport interface CreateSceneRequest {\n story_id: string;\n title: string;\n order?: number;\n is_title?: boolean;\n is_end?: boolean;\n}\n\nexport interface SceneActionItem {\n id: string;\n scene_id: string;\n name: string;\n type: string | null;\n scene_choice_id: string | null;\n order: number;\n duration: number;\n properties: SceneActionPayload | null;\n subproperties: SceneActionPayload | null;\n created_at?: string;\n updated_at?: string;\n}\n\nexport interface CreateSceneActionRequest {\n scene_id: string;\n name: string;\n type?: string | null;\n order?: number;\n duration?: number;\n properties?: SceneActionPayload | null;\n subproperties?: SceneActionPayload | null;\n}\n\nexport interface UpdateSceneActionRequest {\n name?: string;\n type?: string | null;\n order?: number;\n duration?: number;\n properties?: SceneActionPayload;\n subproperties?: SceneActionPayload;\n}\n\n// ── AI Generation ──\n\nexport type AiGenerationStatus =\n | \"pending\"\n | \"generating\"\n | \"ready\"\n | \"failed\"\n | \"accepted\"\n | \"rejected\";\n\nexport interface GenerateImageRequest {\n story_id: string;\n scene_id?: string;\n prompt: string;\n mood?: string;\n visual_style?: string;\n rejection_feedback?: string;\n previous_generation_id?: string;\n}\n\nexport interface AiGeneration {\n id: string;\n story_id: string;\n scene_id: string | null;\n status: AiGenerationStatus;\n user_prompt: string;\n style_context: {\n mood?: string;\n visual_style?: string;\n subject_prompt: string;\n full_prompt: string;\n negative_prompt?: string;\n };\n preview_url?: string;\n rejection_feedback: string | null;\n media_id: string | null;\n attempt_number: number;\n credits_charged: number;\n created_at: string;\n updated_at: string;\n}\n\nexport interface GenerateImageResponse {\n generation: AiGeneration;\n balance: { watch: number; create: number };\n}\n\nexport interface AiGenerationShowResponse {\n generation: AiGeneration;\n}\n\nexport interface AcceptGenerationRequest {\n scene_id: string;\n}\n\nexport interface AcceptGenerationResponse {\n generation: AiGeneration;\n scene: SceneListItem;\n media: MediaItem;\n}\n\nexport interface RejectGenerationRequest {\n feedback: string;\n}\n\nexport interface RejectGenerationResponse {\n generation: AiGeneration;\n}\n\nexport interface StoryStyleResponse {\n has_style: boolean;\n style_context: AiGeneration[\"style_context\"] | null;\n}\n\n// ── Media ──\n\nexport interface MediaItem {\n id: string;\n user_id: string;\n s3_key: string;\n source_path: string;\n output_prefix: string | null;\n status: \"uploaded\" | \"processing\" | \"ready\" | \"error\";\n meta: Record<string, unknown>;\n order: number;\n created_at: string;\n updated_at: string;\n}\n\nexport interface UploadUrlRequest {\n filename: string;\n contentType: string;\n}\n\nexport interface UploadUrlResponse {\n key: string;\n uploadUrl: string;\n}\n\nexport interface RegisterMediaRequest {\n key: string;\n s3_key: string;\n filename: string;\n content_type?: string;\n title?: string;\n file_size?: number;\n}\n\nexport interface RegisterMediaResponse extends MediaItem {\n credits_charged: number;\n balance: number;\n}\n\nexport interface TranscodeRequest {\n media_id: string;\n}\n\nexport interface TranscodeResponse {\n message: string;\n job_id: string | null;\n manifest_path?: string;\n}\n\nexport interface PlayUrlResponse {\n play_url: string;\n status: string;\n}\n\nexport interface SourceUrlResponse {\n url: string;\n}\n\n// ── Story authoring ──\n\nexport interface CreateStoryRequest {\n title: string;\n description?: string | null;\n genre?: string | null;\n content_rating?: ContentRating | null;\n}\n\nexport interface UpdateStoryRequest {\n title?: string;\n description?: string | null;\n genre?: string | null;\n content_rating?: ContentRating | null;\n published?: boolean;\n tags?: string[];\n}\n\nexport interface TagListResponse {\n data: string[];\n}\n\n// ── Content Ratings ──\n\nexport type ContentRating = \"G\" | \"PG\" | \"PG-13\" | \"R\" | \"NC-17\";\n\nexport interface ContentRatingDefinition {\n id: ContentRating;\n label: string;\n description: string;\n}\n\n// ── Realm Config ──\n\nexport type RealmId = \"cosmos\" | \"wilds\";\n\n/** Supported UI locales — matches PHP `chuzi_realms.supported_locales`. */\nexport type LocaleId = \"en\" | \"es\" | \"fr\" | \"de\" | \"pt\";\n\nexport interface RealmDefinition {\n label: string;\n short_label: string;\n /** Canonical English lexicon (always present). */\n lexicon: Record<string, string>;\n /** Optional per-locale overrides; missing keys fall through to `lexicon`. */\n locales?: Partial<Record<LocaleId, Record<string, string>>>;\n}\n\nexport interface RealmConfigResponse {\n realms: Record<RealmId, RealmDefinition>;\n fallback_lexicon: Record<string, string>;\n fallback_locales?: Partial<Record<LocaleId, Record<string, string>>>;\n intro: {\n line1: string;\n line2: string;\n };\n intro_locales?: Partial<Record<LocaleId, { line1: string; line2: string }>>;\n profile: {\n title: string;\n current_prefix: string;\n switch_prompt: string;\n };\n profile_locales?: Partial<Record<LocaleId, { title: string; current_prefix: string; switch_prompt: string }>>;\n allowed_realm_ids: RealmId[];\n supported_locales: LocaleId[];\n locale_labels?: Partial<Record<LocaleId, string>>;\n}\n\nexport interface UpdateLocaleRequest {\n locale: LocaleId;\n}\n\nexport interface UpdateLocaleResponse {\n locale: LocaleId;\n}\n\n// ── Scene Visibility ──\n\nexport interface SceneVisibility {\n /** Whether the user can navigate to this scene-star. */\n navigable: boolean;\n /** Whether the star/edge should render at reduced opacity. */\n dimmed: boolean;\n}\n\n/**\n * Compute per-scene navigable/dimmed flags for a constellation.\n *\n * Rules:\n * - If `isCreator` is true, everything is navigable and bright.\n * - Title scenes are always navigable and bright.\n * - If the user has watched the ending (`endingSeen`), all scenes unlock.\n * - Otherwise non-title scenes are locked and dimmed.\n */\nexport function computeSceneVisibility(\n sceneList: Pick<SceneListItem, \"is_title\" | \"is_end\">[],\n opts: { isCreator: boolean; endingSeen: boolean },\n): SceneVisibility[] {\n if (opts.isCreator || opts.endingSeen) {\n return sceneList.map(() => ({ navigable: true, dimmed: false }));\n }\n return sceneList.map((scene) => ({\n navigable: scene.is_title,\n dimmed: !scene.is_title,\n }));\n}\n"]}
1
+ {"version":3,"sources":["../../src/types/index.ts"],"names":[],"mappings":";AAyvBO,SAAS,sBAAA,CACd,WACA,IAAA,EACmB;AACnB,EAAA,IAAI,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,UAAA,EAAY;AACrC,IAAA,OAAO,SAAA,CAAU,IAAI,OAAO,EAAE,WAAW,IAAA,EAAM,MAAA,EAAQ,OAAM,CAAE,CAAA;AAAA,EACjE;AACA,EAAA,OAAO,SAAA,CAAU,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,IAC/B,WAAW,KAAA,CAAM,QAAA;AAAA,IACjB,MAAA,EAAQ,CAAC,KAAA,CAAM;AAAA,GACjB,CAAE,CAAA;AACJ","file":"index.js","sourcesContent":["// ── Auth ──\n\nexport interface MagicLinkRequest {\n email: string;\n username?: string;\n device_name?: string;\n}\n\nexport interface MagicLinkRequestResponse {\n message: string;\n}\n\nexport interface MagicLinkVerifyRequest {\n email: string;\n token: string;\n device_name?: string;\n}\n\nexport interface MagicLinkVerifyResponse {\n token?: string;\n user: UserProfile;\n}\n\nexport interface OidcExchangeRequest {\n code: string;\n code_verifier: string;\n redirect_uri: string;\n device_name?: string;\n}\n\nexport interface OidcExchangeResponse {\n token?: string;\n user: UserProfile;\n}\n\nexport interface PasswordLoginRequest {\n email: string;\n password: string;\n device_name?: string;\n}\n\nexport interface PasswordLoginResponse {\n token?: string;\n user: UserProfile;\n}\n\n// ── User ──\n\nexport interface UserProfile {\n id: string;\n name: string;\n username: string;\n email: string;\n realm: RealmId | null;\n needs_realm_choice: boolean;\n locale: LocaleId | null;\n avatar_url: string | null;\n is_admin: boolean;\n created_at: string;\n}\n\nexport interface UpdateRealmRequest {\n realm: RealmId;\n}\n\nexport interface UpdateRealmResponse {\n realm: RealmId;\n user: UserProfile;\n}\n\nexport interface UpdateProfileRequest {\n name?: string;\n username?: string;\n}\n\nexport interface UpdateProfileResponse {\n user: UserProfile;\n}\n\nexport interface UsernameAvailabilityResponse {\n username: string;\n available: boolean;\n suggestion?: string;\n}\n\nexport interface PublicDirectorProfile {\n id: string;\n name: string;\n avatar_url: string | null;\n realm: RealmId | null;\n stories_count: number;\n}\n\n// ── Credits & payments ──\n\nexport type CreditPool = \"watch\" | \"create\";\n\nexport interface CreditBalance {\n watch: number;\n create: number;\n}\n\nexport interface CreditPack {\n id: string;\n pool: CreditPool;\n name: string;\n credits: number;\n price_cents: number;\n price: string;\n}\n\nexport interface PaymentMethod {\n id: string;\n type: string;\n last_four: string;\n label: string | null;\n is_default: boolean;\n created_at: string;\n}\n\nexport interface CreditBalanceResponse {\n balance: CreditBalance;\n}\n\nexport interface UploadCostResponse {\n file_size_bytes: number;\n file_size_mb: number;\n base_size_mb: number;\n credits_needed: number;\n balance: CreditBalance;\n can_afford: boolean;\n}\n\nexport interface PurchaseCreditsRequest {\n pack_id: string;\n payment_method_id?: string;\n}\n\nexport interface PurchaseAmountRequest {\n pool: CreditPool;\n credits: number;\n payment_method_id?: string;\n}\n\nexport interface PurchaseCreditsResponse {\n message: string;\n transaction_id?: string;\n balance: CreditBalance;\n}\n\nexport interface StorePaymentMethodRequest {\n type?: string;\n last_four: string;\n label?: string;\n}\n\nexport interface GrantCreditsRequest {\n user_id: string;\n pool: CreditPool;\n amount: number;\n note?: string;\n}\n\nexport interface GrantCreditsResponse {\n message: string;\n balance: CreditBalance;\n}\n\n// ── Catalog ──\n\nexport interface StoryListItem {\n id: string;\n title: string;\n description: string | null;\n genre: string | null;\n content_rating: ContentRating | null;\n published: boolean;\n published_version: number;\n watch_starts_count: number;\n choice_clicks_count: number;\n scenes_count: number;\n choices_count: number;\n tags: string[];\n creator: {\n id: string;\n name: string;\n } | null;\n created_at: string;\n updated_at: string;\n}\n\nexport interface StoryPreview {\n source: \"trailer\" | \"title_scene\" | \"none\";\n title: string;\n preview_url: string | null;\n media_type: string | null;\n is_ready: boolean;\n}\n\nexport interface PopularChoice {\n label: string;\n click_count: number;\n}\n\nexport interface StoryProgress {\n last_scene_id: string | null;\n bookmark_code: string | null;\n playback_seconds: number;\n watched_version: number;\n last_watched_at: string | null;\n}\n\nexport interface CatalogResponse {\n data: StoryListItem[];\n meta: {\n previews: Record<string, StoryPreview>;\n popular_choices: Record<string, PopularChoice[]>;\n creator_avatars: Record<string, string>;\n coverboxes: Record<string, string | null>;\n progress: Record<string, StoryProgress>;\n };\n}\n\nexport interface MineResponse {\n stories: StoryListItem[];\n data?: StoryListItem[];\n progress?: Record<string, StoryProgress>;\n meta: {\n coverboxes: Record<string, string | null>;\n };\n}\n\n// ── Watch / Scene Map ──\n\nexport interface SceneChoice {\n id: string;\n label: string;\n choice_type: string;\n choice_icon: string | null;\n choice_icon_media_id: string | null;\n choice_icon_url: string | null;\n reveal_mode: string;\n start_time_seconds: number | null;\n pause_for_choice: boolean;\n end_time_seconds: number | null;\n target_scene_id: string;\n x: number;\n y: number;\n w: number;\n h: number;\n arrow_rotation: number;\n arrow_scale: number;\n fade_in_ms?: number | null;\n fade_out_ms?: number | null;\n duration_ms?: number | null;\n visibility_rules: VisibilityRules | null;\n state_updates: StateUpdate[];\n}\n\nexport interface VisibilityRules {\n mode: \"all\" | \"any\";\n conditions: VisibilityCondition[];\n}\n\nexport interface VisibilityCondition {\n variable?: string;\n key?: string;\n operator?: string;\n value?: string;\n}\n\nexport interface StateUpdate {\n variable?: string;\n key?: string;\n action?: string;\n value?: string;\n}\n\nexport interface SceneNode {\n type: string;\n properties?: Record<string, unknown>;\n children?: SceneNode[];\n}\n\nexport type SceneActionPayload = Record<string, unknown> | unknown[];\n\nexport interface SceneMapEntry {\n scene_id: string;\n scene_title: string;\n color: string | null;\n is_title: boolean;\n media_title: string | null;\n media_type: string | null;\n media_url: string | null;\n stream_status: string | null;\n choice_style: string;\n choice_overlay_mode: string;\n goto_scene_id: string | null;\n choices: SceneChoice[];\n nodes: SceneNode[];\n}\n\nexport interface TreeGraphNode {\n id: string;\n title: string;\n is_title: boolean;\n is_end: boolean;\n level: number;\n index: number;\n x: number;\n y: number;\n color: string | null;\n}\n\nexport interface TreeGraphEdge {\n id: string;\n source: string;\n target: string;\n type: \"choice\" | \"go_to_scene\";\n}\n\nexport interface TreeGraph {\n nodes: TreeGraphNode[];\n edges: TreeGraphEdge[];\n meta: {\n root_id: string | null;\n level_count: number;\n };\n}\n\nexport interface WatchSnapshot {\n scene_id: string | null;\n state: Record<string, string>;\n history: HistoryEntry[];\n path: string[];\n visited_scene_ids?: string[];\n playback_seconds: number;\n}\n\nexport interface HistoryEntry {\n choice_id: string;\n scene_id: string;\n target_scene_id: string | null;\n at: string | null;\n path_index: number | null;\n}\n\nexport interface SceneMapResponse {\n story: {\n id: string;\n title: string;\n description: string | null;\n genre: string | null;\n content_rating: ContentRating | null;\n published_version: number;\n watch_starts_count: number;\n choice_clicks_count: number;\n };\n start_scene_id: string;\n scene_map: Record<string, SceneMapEntry>;\n tree_graph: TreeGraph;\n initial_snapshot: WatchSnapshot;\n initial_bookmark_code: string | null;\n}\n\n// ── Engagement ──\n\nexport interface TrackEngagementRequest {\n event: \"play_start\" | \"choice_click\";\n choice_id?: string;\n}\n\nexport interface EngagementResponse {\n watch_starts_count: number;\n choice_clicks_count: number;\n}\n\n// ── Bookmarks ──\n\nexport interface SaveBookmarkRequest {\n snapshot: WatchSnapshot;\n}\n\nexport interface BookmarkResponse {\n code: string;\n snapshot: WatchSnapshot;\n}\n\nexport interface BookmarkListItem {\n code: string;\n snapshot: WatchSnapshot;\n updated_at: string;\n}\n\nexport interface BookmarkListResponse {\n bookmarks: BookmarkListItem[];\n}\n\n// ── Pagination ──\n\nexport interface PaginatedLink {\n url: string | null;\n label: string;\n active: boolean;\n}\n\nexport interface PaginatedResponse<T> {\n data: T[];\n current_page: number;\n first_page_url: string;\n from: number | null;\n last_page: number;\n last_page_url: string;\n links: PaginatedLink[];\n next_page_url: string | null;\n path: string;\n per_page: number;\n prev_page_url: string | null;\n to: number | null;\n total: number;\n}\n\n// ── Scenes ──\n\nexport type LineType = \"text\" | \"sound\" | \"image\";\n\nexport interface TextLine {\n id: string;\n type?: LineType;\n media_id?: string;\n html: string;\n appear_at_ms: number;\n fade_in_ms: number;\n fade_out_ms: number;\n duration_ms: number;\n position_x?: number;\n position_y?: number;\n width_pct?: number;\n height_pct?: number;\n persist?: boolean;\n}\n\nexport interface SceneTextContent {\n html: string;\n lines?: TextLine[];\n}\n\nexport interface SceneListItem {\n id: string;\n story_id: string;\n title: string;\n order: number;\n is_title: boolean;\n is_end: boolean;\n color: string | null;\n media_id: string | null;\n alt_media_id: string | null;\n media_mode: \"text\" | \"imagery\" | \"film\" | null;\n text_content: SceneTextContent | null;\n text_style: {\n font_family?: string | null;\n text_color?: string | null;\n background_color?: string | null;\n } | null;\n goto_scene_id?: string | null;\n choice_end_time_seconds?: number | null;\n created_at: string;\n updated_at: string;\n}\n\nexport interface UpdateSceneRequest {\n title?: string;\n order?: number;\n media_id?: string | null;\n alt_media_id?: string | null;\n media_mode?: \"text\" | \"imagery\" | \"film\" | null;\n text_content?: SceneTextContent | null;\n text_style?: {\n font_family?: string | null;\n text_color?: string | null;\n background_color?: string | null;\n } | null;\n color?: string | null;\n is_title?: boolean;\n is_end?: boolean;\n goto_scene_id?: string | null;\n choice_style?: string | null;\n choice_overlay_mode?: string | null;\n choice_reveal_mode?: string | null;\n choice_start_time_seconds?: number | null;\n choice_pause_for_choice?: boolean;\n choice_end_time_seconds?: number | null;\n}\n\nexport interface CreateSceneRequest {\n story_id: string;\n title: string;\n order?: number;\n is_title?: boolean;\n is_end?: boolean;\n}\n\nexport interface SceneActionItem {\n id: string;\n scene_id: string;\n name: string;\n type: string | null;\n scene_choice_id: string | null;\n order: number;\n duration: number;\n properties: SceneActionPayload | null;\n subproperties: SceneActionPayload | null;\n created_at?: string;\n updated_at?: string;\n}\n\nexport interface CreateSceneActionRequest {\n scene_id: string;\n name: string;\n type?: string | null;\n order?: number;\n duration?: number;\n properties?: SceneActionPayload | null;\n subproperties?: SceneActionPayload | null;\n}\n\nexport interface UpdateSceneActionRequest {\n name?: string;\n type?: string | null;\n order?: number;\n duration?: number;\n properties?: SceneActionPayload;\n subproperties?: SceneActionPayload;\n}\n\n// ── AI Generation ──\n\nexport type AiGenerationStatus =\n | \"pending\"\n | \"generating\"\n | \"ready\"\n | \"failed\"\n | \"accepted\"\n | \"rejected\";\n\nexport interface GenerateImageRequest {\n story_id: string;\n scene_id?: string;\n prompt: string;\n mood?: string;\n visual_style?: string;\n rejection_feedback?: string;\n previous_generation_id?: string;\n}\n\nexport interface AiGeneration {\n id: string;\n story_id: string;\n scene_id: string | null;\n status: AiGenerationStatus;\n user_prompt: string;\n style_context: {\n mood?: string;\n visual_style?: string;\n subject_prompt: string;\n full_prompt: string;\n negative_prompt?: string;\n };\n preview_url?: string;\n rejection_feedback: string | null;\n media_id: string | null;\n attempt_number: number;\n credits_charged: number;\n created_at: string;\n updated_at: string;\n}\n\nexport interface GenerateImageResponse {\n generation: AiGeneration;\n balance: { watch: number; create: number };\n}\n\nexport interface AiGenerationShowResponse {\n generation: AiGeneration;\n}\n\nexport interface AcceptGenerationRequest {\n scene_id: string;\n}\n\nexport interface AcceptGenerationResponse {\n generation: AiGeneration;\n scene: SceneListItem;\n media: MediaItem;\n}\n\nexport interface RejectGenerationRequest {\n feedback: string;\n}\n\nexport interface RejectGenerationResponse {\n generation: AiGeneration;\n}\n\nexport interface StoryStyleResponse {\n has_style: boolean;\n style_context: AiGeneration[\"style_context\"] | null;\n}\n\n// ── Media ──\n\nexport interface MediaItem {\n id: string;\n user_id: string;\n s3_key: string;\n source_path: string;\n output_prefix: string | null;\n status: \"uploaded\" | \"processing\" | \"ready\" | \"error\";\n meta: Record<string, unknown>;\n order: number;\n created_at: string;\n updated_at: string;\n}\n\nexport interface UploadUrlRequest {\n filename: string;\n contentType: string;\n}\n\nexport interface UploadUrlResponse {\n key: string;\n uploadUrl: string;\n}\n\nexport interface RegisterMediaRequest {\n key: string;\n s3_key: string;\n filename: string;\n content_type?: string;\n title?: string;\n file_size?: number;\n}\n\nexport interface RegisterMediaResponse extends MediaItem {\n credits_charged: number;\n balance: number;\n}\n\nexport interface TranscodeRequest {\n media_id: string;\n}\n\nexport interface TranscodeResponse {\n message: string;\n job_id: string | null;\n manifest_path?: string;\n}\n\nexport interface PlayUrlResponse {\n play_url: string;\n status: string;\n}\n\nexport interface SourceUrlResponse {\n url: string;\n}\n\n// ── Story authoring ──\n\nexport interface CreateStoryRequest {\n title: string;\n description?: string | null;\n genre?: string | null;\n content_rating?: ContentRating | null;\n}\n\nexport interface UpdateStoryRequest {\n title?: string;\n description?: string | null;\n genre?: string | null;\n content_rating?: ContentRating | null;\n published?: boolean;\n tags?: string[];\n}\n\nexport interface TagListResponse {\n data: string[];\n}\n\n// ── Content Ratings ──\n\nexport type ContentRating = \"G\" | \"PG\" | \"PG-13\" | \"R\" | \"NC-17\";\n\nexport interface ContentRatingDefinition {\n id: ContentRating;\n label: string;\n description: string;\n}\n\n// ── Realm Config ──\n\nexport type RealmId = \"cosmos\" | \"wilds\";\n\n/** Supported UI locales — matches PHP `chuzi_realms.supported_locales`. */\nexport type LocaleId = \"en\" | \"es\" | \"fr\" | \"de\" | \"pt\";\n\nexport interface RealmDefinition {\n label: string;\n short_label: string;\n /** Canonical English lexicon (always present). */\n lexicon: Record<string, string>;\n /** Optional per-locale overrides; missing keys fall through to `lexicon`. */\n locales?: Partial<Record<LocaleId, Record<string, string>>>;\n}\n\nexport interface RealmConfigResponse {\n realms: Record<RealmId, RealmDefinition>;\n fallback_lexicon: Record<string, string>;\n fallback_locales?: Partial<Record<LocaleId, Record<string, string>>>;\n intro: {\n line1: string;\n line2: string;\n };\n intro_locales?: Partial<Record<LocaleId, { line1: string; line2: string }>>;\n profile: {\n title: string;\n current_prefix: string;\n switch_prompt: string;\n };\n profile_locales?: Partial<Record<LocaleId, { title: string; current_prefix: string; switch_prompt: string }>>;\n allowed_realm_ids: RealmId[];\n supported_locales: LocaleId[];\n locale_labels?: Partial<Record<LocaleId, string>>;\n}\n\nexport interface UpdateLocaleRequest {\n locale: LocaleId;\n}\n\nexport interface UpdateLocaleResponse {\n locale: LocaleId;\n}\n\n// ── Scene Visibility ──\n\nexport interface SceneVisibility {\n /** Whether the user can navigate to this scene-star. */\n navigable: boolean;\n /** Whether the star/edge should render at reduced opacity. */\n dimmed: boolean;\n}\n\n/**\n * Compute per-scene navigable/dimmed flags for a constellation.\n *\n * Rules:\n * - If `isCreator` is true, everything is navigable and bright.\n * - Title scenes are always navigable and bright.\n * - If the user has watched the ending (`endingSeen`), all scenes unlock.\n * - Otherwise non-title scenes are locked and dimmed.\n */\nexport function computeSceneVisibility(\n sceneList: Pick<SceneListItem, \"is_title\" | \"is_end\">[],\n opts: { isCreator: boolean; endingSeen: boolean },\n): SceneVisibility[] {\n if (opts.isCreator || opts.endingSeen) {\n return sceneList.map(() => ({ navigable: true, dimmed: false }));\n }\n return sceneList.map((scene) => ({\n navigable: scene.is_title,\n dimmed: !scene.is_title,\n }));\n}\n"]}
package/dist/ui/index.js CHANGED
@@ -62,6 +62,8 @@ var REALMS = {
62
62
  editor_choices: "Choices",
63
63
  editor_scene_settings: "Star Settings",
64
64
  editor_title_scene: "Title Star",
65
+ editor_film_title: "Star System Title",
66
+ editor_film_title_hint: "Click to rename this star system",
65
67
  editor_ending_scene: "Final Star",
66
68
  editor_play: "Engage",
67
69
  editor_pause: "Hold",
@@ -91,6 +93,22 @@ var REALMS = {
91
93
  editor_ai_retry: "Retransmit",
92
94
  editor_ai_feedback_prompt: "How should we adjust the signal?",
93
95
  editor_ai_credit_cost: "1 Stardust per signal",
96
+ spending_modal_title: "Refuel Your Mission",
97
+ spending_modal_body: "You need more credits to continue. Choose how many to add.",
98
+ spending_modal_no_payment: "Add a payment method to purchase credits.",
99
+ spending_modal_credits_label: "Credits to add",
100
+ spending_modal_price_label: "Total",
101
+ spending_modal_admin_note: "Admin \u2014 credits are granted free of charge.",
102
+ spending_modal_purchase: "Purchase Credits",
103
+ spending_modal_grant: "Grant Credits",
104
+ spending_modal_add_card: "Add Payment Method",
105
+ spending_modal_card_last_four: "Card last four digits",
106
+ spending_modal_card_label: "Card label (optional)",
107
+ spending_modal_processing: "Processing\u2026",
108
+ spending_modal_success: "Credits added.",
109
+ spending_modal_error: "Could not complete purchase. Try again.",
110
+ spending_modal_cancel: "Not now",
111
+ spending_insufficient_credits: "Not enough credits. Purchase more to continue.",
94
112
  editor_ai_generating_scene: "Planet forming\u2026",
95
113
  editor_ai_style_badge: "Signal Style",
96
114
  editor_ai_failed: "Signal lost. Try retransmitting.",
@@ -368,6 +386,8 @@ var REALMS = {
368
386
  editor_choices: "Choices",
369
387
  editor_scene_settings: "Clearing Settings",
370
388
  editor_title_scene: "Title Clearing",
389
+ editor_film_title: "Grove Title",
390
+ editor_film_title_hint: "Click to rename this grove",
371
391
  editor_ending_scene: "Roots",
372
392
  editor_play: "Unfurl",
373
393
  editor_pause: "Rest",
@@ -397,6 +417,22 @@ var REALMS = {
397
417
  editor_ai_retry: "Regrow",
398
418
  editor_ai_feedback_prompt: "How should we reshape the growth?",
399
419
  editor_ai_credit_cost: "1 Pollen per bloom",
420
+ spending_modal_title: "Gather More Pollen",
421
+ spending_modal_body: "You need more credits to continue. Choose how many to add.",
422
+ spending_modal_no_payment: "Add a payment method to purchase credits.",
423
+ spending_modal_credits_label: "Credits to add",
424
+ spending_modal_price_label: "Total",
425
+ spending_modal_admin_note: "Admin \u2014 credits are granted free of charge.",
426
+ spending_modal_purchase: "Purchase Credits",
427
+ spending_modal_grant: "Grant Credits",
428
+ spending_modal_add_card: "Add Payment Method",
429
+ spending_modal_card_last_four: "Card last four digits",
430
+ spending_modal_card_label: "Card label (optional)",
431
+ spending_modal_processing: "Processing\u2026",
432
+ spending_modal_success: "Credits added.",
433
+ spending_modal_error: "Could not complete purchase. Try again.",
434
+ spending_modal_cancel: "Not now",
435
+ spending_insufficient_credits: "Not enough credits. Purchase more to continue.",
400
436
  editor_ai_generating_scene: "Clearing emerging\u2026",
401
437
  editor_ai_style_badge: "Growth Style",
402
438
  editor_ai_failed: "Growth withered. Try regrowing.",
@@ -672,6 +708,8 @@ var FALLBACK_LEXICON = {
672
708
  editor_choices: "Choices",
673
709
  editor_scene_settings: "Scene Settings",
674
710
  editor_title_scene: "Title Scene",
711
+ editor_film_title: "Film Title",
712
+ editor_film_title_hint: "Click to rename this film",
675
713
  editor_ending_scene: "Ending Scene",
676
714
  editor_play: "Play",
677
715
  editor_pause: "Pause",
@@ -701,6 +739,22 @@ var FALLBACK_LEXICON = {
701
739
  editor_ai_retry: "Try Again",
702
740
  editor_ai_feedback_prompt: "What should we change?",
703
741
  editor_ai_credit_cost: "1 credit per generation",
742
+ spending_modal_title: "Purchase Credits",
743
+ spending_modal_body: "You need more credits to continue. Choose how many to add.",
744
+ spending_modal_no_payment: "Add a payment method to purchase credits.",
745
+ spending_modal_credits_label: "Credits to add",
746
+ spending_modal_price_label: "Total",
747
+ spending_modal_admin_note: "Admin \u2014 credits are granted free of charge.",
748
+ spending_modal_purchase: "Purchase Credits",
749
+ spending_modal_grant: "Grant Credits",
750
+ spending_modal_add_card: "Add Payment Method",
751
+ spending_modal_card_last_four: "Card last four digits",
752
+ spending_modal_card_label: "Card label (optional)",
753
+ spending_modal_processing: "Processing\u2026",
754
+ spending_modal_success: "Credits added.",
755
+ spending_modal_error: "Could not complete purchase. Try again.",
756
+ spending_modal_cancel: "Not now",
757
+ spending_insufficient_credits: "Not enough credits. Purchase more to continue.",
704
758
  editor_ai_generating_scene: "Scene generating\u2026",
705
759
  editor_ai_style_badge: "Style",
706
760
  editor_ai_failed: "Generation failed. Please try again.",