@nodaro/shared 3.8.0 → 3.10.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.
Files changed (53) hide show
  1. package/dist/index.cjs +325 -63
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +806 -41
  4. package/dist/index.d.ts +806 -41
  5. package/dist/index.js +295 -64
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/__tests__/credit-identifiers.test.ts +51 -1
  9. package/src/__tests__/gvp-supported-providers.test.ts +20 -3
  10. package/src/__tests__/model-catalog-sections.test.ts +59 -0
  11. package/src/__tests__/model-tree.test.ts +5 -2
  12. package/src/__tests__/parameter-node-value.test.ts +35 -0
  13. package/src/__tests__/pricing-default-duration.test.ts +45 -0
  14. package/src/__tests__/prompt-length-limits.test.ts +4 -0
  15. package/src/__tests__/scene3d-delivery-review.test.ts +512 -0
  16. package/src/__tests__/suno-credit-type.test.ts +46 -0
  17. package/src/__tests__/template-categories.test.ts +45 -0
  18. package/src/__tests__/video-ref-limits.test.ts +11 -2
  19. package/src/credit-identifiers.ts +64 -14
  20. package/src/i18n/camera-motions.ar.ts +5 -5
  21. package/src/i18n/camera-motions.de.ts +5 -5
  22. package/src/i18n/camera-motions.es.ts +5 -5
  23. package/src/i18n/camera-motions.fr.ts +5 -5
  24. package/src/i18n/camera-motions.he.ts +5 -5
  25. package/src/i18n/camera-motions.hi.ts +5 -5
  26. package/src/i18n/camera-motions.ja.ts +5 -5
  27. package/src/i18n/camera-motions.ko.ts +5 -5
  28. package/src/i18n/camera-motions.pt-BR.ts +5 -5
  29. package/src/i18n/camera-motions.ru.ts +5 -5
  30. package/src/i18n/camera-motions.zh-CN.ts +5 -5
  31. package/src/i18n/character-motion.ar.ts +1031 -0
  32. package/src/i18n/character-motion.de.ts +1031 -0
  33. package/src/i18n/character-motion.es.ts +1031 -0
  34. package/src/i18n/character-motion.fr.ts +1031 -0
  35. package/src/i18n/character-motion.he.ts +1031 -0
  36. package/src/i18n/character-motion.hi.ts +1031 -0
  37. package/src/i18n/character-motion.ja.ts +1031 -0
  38. package/src/i18n/character-motion.ko.ts +1031 -0
  39. package/src/i18n/character-motion.pt-BR.ts +1031 -0
  40. package/src/i18n/character-motion.ru.ts +1031 -0
  41. package/src/i18n/character-motion.zh-CN.ts +1031 -0
  42. package/src/i18n/types.ts +1 -0
  43. package/src/index.ts +28 -0
  44. package/src/model-catalog.ts +88 -35
  45. package/src/model-constants.ts +118 -24
  46. package/src/node-execution-state.ts +95 -0
  47. package/src/parameter-node-value.ts +33 -0
  48. package/src/presentation-utils.ts +1 -0
  49. package/src/pro-3d-render.ts +159 -0
  50. package/src/scene3d-delivery-notes.ts +490 -0
  51. package/src/scene3d-v2-plan.ts +8 -2
  52. package/src/smart-cut-windows.ts +15 -10
  53. package/src/template-categories.ts +67 -0
@@ -15,7 +15,7 @@ import {
15
15
  RESOLUTION_DURATION_PRICING,
16
16
  VEO_RESOLUTION_TIERED_PROVIDERS,
17
17
  VIDEO_DURATION_TIERS,
18
- PRICING_DEFAULT_DURATION_SEC,
18
+ pricedOutputDurationSec,
19
19
  PRICING_DEFAULT_RESOLUTION,
20
20
  MOTION_DURATION_TIERS,
21
21
  T2I_TO_I2I_VARIANT,
@@ -27,8 +27,44 @@ import {
27
27
  normalizeWan3Resolution,
28
28
  getVideoAudioCapability,
29
29
  } from "./model-constants.js"
30
- import { isFlux2Model } from "./flux2-pricing.js"
31
- import { MODEL_CATALOG, normalizeModelInput, type ModelInputAdjustment } from "./model-catalog.js"
30
+ import { isFlux2Model, FLUX2_RES_MP, type Flux2Model } from "./flux2-pricing.js"
31
+ import { MODEL_CATALOG, normalizeModelInput, defaultResolutionFor, type ModelInputAdjustment } from "./model-catalog.js"
32
+
33
+ /**
34
+ * The megapixel tier a Flux 2 credit identifier is keyed on, for ANY incoming
35
+ * `resolution` value.
36
+ *
37
+ * Flux 2 is the only family whose identifier INTERPOLATES the resolution
38
+ * instead of matching it against a known set, and its callers are not
39
+ * guaranteed to hand it a Flux 2 value:
40
+ * - the multi-provider cost preview prices one node's data against EVERY
41
+ * selected provider (`frontend/src/ee/hooks/use-providers-credits-sum.ts`),
42
+ * so a node whose `resolution` is "2K" (the flux / nano-banana-pro value
43
+ * space) reaches this branch verbatim;
44
+ * - node data written straight into workflow JSON by an agent, an import or
45
+ * a template never ran the config panel's provider-change fail-safe.
46
+ * Interpolating that produced the off-grid id `flux-2-pro:2KMP:0ref`, which no
47
+ * pricing row can answer — every cost badge 503'd `price_not_configured`
48
+ * (18 production app-reports, 2026-09-07..14).
49
+ *
50
+ * A value off the grid snaps to the model's DEFAULT tier — the same `preferred`
51
+ * value `normalizeModelInput` uses — so the preview asks for exactly the id the
52
+ * route will reserve (both routes and the orchestrator normalize through
53
+ * `resolveNormalizedImageGen` first, which makes this snap a no-op for them).
54
+ * An ABSENT resolution keeps its long-standing meaning ("this caller has no
55
+ * resolution dimension at all") and stays on 1 MP rather than moving to the
56
+ * model default, which would silently re-price every resolution-less node.
57
+ */
58
+ function flux2MegapixelTier(model: Flux2Model, resolution?: string): string {
59
+ const bare = (v: string) => v.replace(/\s*MP$/i, "").trim()
60
+ const raw = typeof resolution === "string" ? bare(resolution) : ""
61
+ if (raw === "") return "1"
62
+ // Numeric match so "2.0"/" 2 MP" land on the same grid point as "2", and the
63
+ // GRID's own spelling is what gets emitted — returning the caller's "2.0"
64
+ // would build ":2.0MP:", another id no pricing row carries.
65
+ const tier = FLUX2_RES_MP.find((t) => Number(t) === Number(raw))
66
+ return tier ?? bare(defaultResolutionFor(model) ?? "1 MP")
67
+ }
32
68
 
33
69
  /**
34
70
  * Compute composite model identifier for variable credit pricing.
@@ -54,8 +90,7 @@ export function buildCreditModelIdentifier(
54
90
  // their cost formula charges per input MP, so the reserved identifier must
55
91
  // reflect refs (there is no metered true-up to correct an under-reserved tier).
56
92
  if (isFlux2Model(provider)) {
57
- const mp = (resolution ?? "1 MP").replace(/\s*MP$/i, "").trim()
58
- return `${provider}:${mp}MP:${Math.min(referenceImageCount ?? 0, 8)}ref`
93
+ return `${provider}:${flux2MegapixelTier(provider, resolution)}MP:${Math.min(referenceImageCount ?? 0, 8)}ref`
59
94
  }
60
95
  if (HIGH_QUALITY_PROVIDERS.has(provider) && quality === "high") {
61
96
  return `${provider}:high`
@@ -329,10 +364,10 @@ export function buildVideoCreditModelIdentifier(
329
364
 
330
365
  // A named-provider request with NO duration renders the model's own default
331
366
  // (kie/models.ts extraParams), so price that default — not the global 5s —
332
- // for providers whose per-second tiers wouldn't snap 5 up to it (minimax-h3).
333
- const durationFallback = PRICING_DEFAULT_DURATION_SEC[effectiveProvider] ?? 5
334
- const parsed = typeof duration === "string" ? parseInt(duration, 10) : (duration ?? durationFallback)
335
- const durationSec = Number.isNaN(parsed) ? durationFallback : parsed
367
+ // for providers whose per-second tiers wouldn't snap 5 up to it
368
+ // (minimax-h3, seedance-2-5). One helper, shared with the dynamic
369
+ // reference-video reservations, so the tier and the scaled reserve agree.
370
+ const durationSec = pricedOutputDurationSec(effectiveProvider, duration)
336
371
  const tiers = VIDEO_DURATION_TIERS[effectiveProvider]
337
372
  if (!tiers) return effectiveProvider
338
373
 
@@ -598,14 +633,29 @@ export const SUNO_SELECT_OPERATIONS = [
598
633
  * node badges. It is a billing key — NEVER a KIE provider id — which is what
599
634
  * satisfies the B3 egress invariant.
600
635
  *
601
- * V5_5 → "suno-v5_5", V5 → "suno-v5", any other version → the operation key.
602
- * A non-version-priced operation is returned unchanged.
636
+ * The version → key map is {@link SUNO_VERSION_CREDIT_KEYS}; a version with no
637
+ * entry (V4 / V4_5 / V4_5ALL / V4_5PLUS) falls back to the operation key, and a
638
+ * non-version-priced operation is returned unchanged.
603
639
  */
604
640
  export function sunoCreditType(model: string | undefined, operation: string): string {
605
641
  if (!(SUNO_VERSION_PRICED_OPERATIONS as readonly string[]).includes(operation)) {
606
642
  return operation
607
643
  }
608
- if (model === "V5_5") return "suno-v5_5"
609
- if (model === "V5") return "suno-v5"
610
- return operation
644
+ return (model && SUNO_VERSION_CREDIT_KEYS[model]) || operation
645
+ }
646
+
647
+ /**
648
+ * Suno version → Nodaro credit key for the version-priced operations. One row
649
+ * per version that has its own `model_pricing` row / `STATIC_CREDIT_COSTS`
650
+ * entry / `MODEL_CATALOG` pricing row, so an admin can reprice one version
651
+ * without touching code. Every ACTIVE version MUST have a row here — guarded
652
+ * by `__tests__/suno-credit-type.test.ts` (a new active version with no key
653
+ * would silently bill at the operation key).
654
+ */
655
+ export const SUNO_VERSION_CREDIT_KEYS: Readonly<Record<string, string>> = {
656
+ V6: "suno-v6",
657
+ V6_WILD: "suno-v6_wild",
658
+ V6_MINI: "suno-v6_mini",
659
+ V5_5: "suno-v5_5",
660
+ V5: "suno-v5",
611
661
  }
@@ -22,8 +22,8 @@ const map: LocaleCatalogMap = {
22
22
  "dolly-in": { label: "Dolly In", description: "دفع الكاميرا نحو الموضوع (تباين منظور)" },
23
23
  "dolly-out": { label: "Dolly Out", description: "سحب الكاميرا للخلف (تباين منظور)" },
24
24
  "dolly-zoom": { label: "Dolly Zoom", description: "تأثير Vertigo: Dolly يعاكس Zoom" },
25
- "push-in": { label: "Push In", description: "دفع بطيء خفيف نحو الموضوع" },
26
- "pull-out": { label: "Pull Out", description: "سحب بطيء خفيف عن الموضوع" },
25
+ "push-in": { label: "Push In", description: "دفع سريع قوي نحو الموضوع" },
26
+ "pull-out": { label: "Pull Out", description: "سحب سريع قوي عن الموضوع" },
27
27
  "breathing": { label: "كاميرا تتنفس", description: "تذبذب مستمر خفيف من الدفع والسحب، إحساس عضوي بالكاميرا المحمولة باليد" },
28
28
  "push-pull": { label: "Push-Pull / تأرجح", description: "تتحرك الكاميرا نحو الموضوع ثم تعود للخلف، اقتراب وانسحاب متأرجح" },
29
29
  "creep-in": { label: "Creep-In", description: "Push-in بطيء بشكل غير محسوس يبني الرعب أو التوتر" },
@@ -39,8 +39,8 @@ const map: LocaleCatalogMap = {
39
39
  "roll-right": { label: "Roll لليمين", description: "تدوير الكاميرا مع عقارب الساعة" },
40
40
  "dutch-angle": { label: "Dutch Angle", description: "إطار ثابت مائل للتوتر" },
41
41
 
42
- "orbit-left": { label: "دوران Orbit يسار", description: "دائرة كاملة حول الموضوع لليسار" },
43
- "orbit-right": { label: "دوران Orbit يمين", description: "دائرة كاملة حول الموضوع لليمين" },
42
+ "orbit-left": { label: "دوران Orbit يسار", description: "دوران جزئي واسع حول الموضوع لليسار" },
43
+ "orbit-right": { label: "دوران Orbit يمين", description: "دوران جزئي واسع حول الموضوع لليمين" },
44
44
  "spin-360": { label: "دوران كامل 360°", description: "تدور الكاميرا 360 درجة كاملة حول محورها" },
45
45
  "orbit-360": { label: "Orbit كامل 360°", description: "تتحرك الكاميرا في قوس كامل 360 درجة حول الموضوع" },
46
46
  "arc-left": { label: "قوس Arc يسار", description: "قوس جزئي حول الموضوع لليسار" },
@@ -75,7 +75,7 @@ const map: LocaleCatalogMap = {
75
75
  "handheld-vlog": { label: "Handheld Vlog", description: "محمول باليد بأسلوب Vlog عفوي" },
76
76
  "pov-walk": { label: "POV مشي", description: "مشي بمنظور الشخص الأول" },
77
77
  "velocity-edit": { label: "Velocity Edit", description: "إيقاع Speed-Ramp بأسلوب TikTok" },
78
- "match-cut-zoom": { label: "Match Cut Zoom", description: "تقريب موقت على الإيقاع للقطع" },
78
+ "match-cut-zoom": { label: "Match Cut Zoom", description: "تقريب سريع مع قطع حاد إلى شكل مطابق" },
79
79
  "screen-tap": { label: "Screen Tap", description: "انتقال نقر إصبع على الشاشة" },
80
80
  "phone-flip": { label: "قلب الهاتف", description: "قلب الكاميرا الأمامية/الخلفية" },
81
81
  // Location-studio extension (PR #2505 follow-up)
@@ -18,8 +18,8 @@ const map: LocaleCatalogMap = {
18
18
  "dolly-in": { label: "Dolly hinein", description: "Kamera zum Motiv schieben (Parallaxe)" },
19
19
  "dolly-out": { label: "Dolly heraus", description: "Kamera wegziehen (Parallaxe)" },
20
20
  "dolly-zoom": { description: "Vertigo-Effekt: Dolly entgegen Zoom" },
21
- "push-in": { label: "Push-In", description: "Langsames sanftes Schieben zum Motiv" },
22
- "pull-out": { label: "Pull-Out", description: "Langsames sanftes Zurückziehen vom Motiv" },
21
+ "push-in": { label: "Push-In", description: "Schnelles kraftvolles Schieben zum Motiv" },
22
+ "pull-out": { label: "Pull-Out", description: "Schnelles kraftvolles Zurückziehen vom Motiv" },
23
23
  "breathing": { label: "Atmende Kamera", description: "Subtile, kontinuierliche Push-In/Pull-Out-Oszillation, organisches Handkamera-Gefühl" },
24
24
  "push-pull": { label: "Push-Pull / Schwingen", description: "Kamera bewegt sich zum Motiv und wieder zurück, schwingende Annäherung und Rückzug" },
25
25
  "creep-in": { label: "Creep-In", description: "Unmerklich langsames Heranschieben, baut Bedrohung oder Spannung auf" },
@@ -31,8 +31,8 @@ const map: LocaleCatalogMap = {
31
31
  "roll-left": { label: "Rollen nach links", description: "Kamera gegen den Uhrzeigersinn drehen" },
32
32
  "roll-right": { label: "Rollen nach rechts", description: "Kamera im Uhrzeigersinn drehen" },
33
33
  "dutch-angle": { description: "Statischer geneigter Bildausschnitt für Spannung" },
34
- "orbit-left": { label: "Orbit nach links", description: "Vollkreis um das Motiv nach links" },
35
- "orbit-right": { label: "Orbit nach rechts", description: "Vollkreis um das Motiv nach rechts" },
34
+ "orbit-left": { label: "Orbit nach links", description: "Großer Teil-Orbit um das Motiv nach links" },
35
+ "orbit-right": { label: "Orbit nach rechts", description: "Großer Teil-Orbit um das Motiv nach rechts" },
36
36
  "spin-360": { label: "Volle 360°-Drehung", description: "Kamera dreht sich vollständig um 360 Grad um die eigene Achse" },
37
37
  "orbit-360": { label: "Voller 360°-Orbit", description: "Kamera beschreibt einen vollständigen 360-Grad-Bogen um das Motiv" },
38
38
  "arc-left": { label: "Bogen nach links", description: "Teilbogen um das Motiv nach links" },
@@ -63,7 +63,7 @@ const map: LocaleCatalogMap = {
63
63
  "handheld-vlog": { label: "Handgehaltenes Vlog", description: "Lässige vlog-artige Handkamera" },
64
64
  "pov-walk": { label: "POV-Gehen", description: "First-Person-Walking-POV" },
65
65
  "velocity-edit": { description: "TikTok-Speed-Ramp-Tempo" },
66
- "match-cut-zoom": { description: "Beat-getakteter Zoom für Schnitte" },
66
+ "match-cut-zoom": { description: "Schneller Zoom mit hartem Schnitt auf eine passende Form" },
67
67
  "screen-tap": { label: "Screen-Tap", description: "On-Screen-Fingertipp-Übergang" },
68
68
  "phone-flip": { label: "Phone-Flip", description: "Front-/Rückkamera-Wechsel" },
69
69
  // Location-studio extension (PR #2505 follow-up)
@@ -22,8 +22,8 @@ const map: LocaleCatalogMap = {
22
22
  "dolly-in": { label: "Dolly In", description: "Empujar la cámara hacia el sujeto (paralaje)" },
23
23
  "dolly-out": { label: "Dolly Out", description: "Alejar la cámara (paralaje)" },
24
24
  "dolly-zoom": { label: "Dolly Zoom", description: "Efecto vértigo: dolly opuesto al zoom" },
25
- "push-in": { label: "Empuje", description: "Empuje sutil y lento hacia el sujeto" },
26
- "pull-out": { label: "Retroceso", description: "Retroceso sutil y lento desde el sujeto" },
25
+ "push-in": { label: "Empuje", description: "Empuje rápido y enérgico hacia el sujeto" },
26
+ "pull-out": { label: "Retroceso", description: "Retroceso rápido y enérgico desde el sujeto" },
27
27
  "breathing": { label: "Cámara Respirante", description: "Oscilación sutil y continua de empuje y retroceso, sensación orgánica de cámara en mano" },
28
28
  "push-pull": { label: "Push-Pull / Vaivén", description: "La cámara se mueve hacia el sujeto y luego se aleja, una aproximación y retirada oscilante" },
29
29
  "creep-in": { label: "Acercamiento Imperceptible", description: "Empuje extremadamente lento e imperceptible que crea pavor o tensión" },
@@ -39,8 +39,8 @@ const map: LocaleCatalogMap = {
39
39
  "roll-right": { label: "Rotación a la Derecha", description: "Rotar la cámara en sentido horario" },
40
40
  "dutch-angle": { description: "Cuadro inclinado estático para tensión" },
41
41
 
42
- "orbit-left": { label: "Órbita a la Izquierda", description: "Círculo completo alrededor del sujeto a la izquierda" },
43
- "orbit-right": { label: "Órbita a la Derecha", description: "Círculo completo alrededor del sujeto a la derecha" },
42
+ "orbit-left": { label: "Órbita a la Izquierda", description: "Órbita parcial amplia alrededor del sujeto a la izquierda" },
43
+ "orbit-right": { label: "Órbita a la Derecha", description: "Órbita parcial amplia alrededor del sujeto a la derecha" },
44
44
  "spin-360": { label: "Giro Completo 360°", description: "La cámara rota 360 grados completos sobre su eje" },
45
45
  "orbit-360": { label: "Órbita Completa 360°", description: "La cámara describe un arco completo de 360 grados alrededor del sujeto" },
46
46
  "arc-left": { label: "Arco a la Izquierda", description: "Arco parcial alrededor del sujeto a la izquierda" },
@@ -75,7 +75,7 @@ const map: LocaleCatalogMap = {
75
75
  "handheld-vlog": { label: "Vlog en Mano", description: "Cámara casual estilo vlog en mano" },
76
76
  "pov-walk": { label: "POV Caminando", description: "POV caminando en primera persona" },
77
77
  "velocity-edit": { label: "Edición de Velocidad", description: "Ritmo con rampa de velocidad estilo TikTok" },
78
- "match-cut-zoom": { label: "Zoom de Match Cut", description: "Zoom sincronizado al ritmo para cortes" },
78
+ "match-cut-zoom": { label: "Zoom de Match Cut", description: "Zoom rápido con corte seco a una forma coincidente" },
79
79
  "screen-tap": { label: "Toque de Pantalla", description: "Transición con toque de dedo en pantalla" },
80
80
  "phone-flip": { label: "Cambio de Cámara del Teléfono", description: "Cambio entre cámara frontal y trasera" },
81
81
  // Location-studio extension (PR #2505 follow-up)
@@ -23,8 +23,8 @@ const map: LocaleCatalogMap = {
23
23
  "dolly-in": { label: "Travelling avant", description: "Pousser la caméra vers le sujet (parallaxe)" },
24
24
  "dolly-out": { label: "Travelling arrière", description: "Reculer la caméra (parallaxe)" },
25
25
  "dolly-zoom": { description: "Effet vertigo : travelling opposé au zoom" },
26
- "push-in": { label: "Push in", description: "Poussée subtile et lente vers le sujet" },
27
- "pull-out": { label: "Pull out", description: "Recul subtil et lent depuis le sujet" },
26
+ "push-in": { label: "Push in", description: "Poussée rapide et énergique vers le sujet" },
27
+ "pull-out": { label: "Pull out", description: "Recul rapide et énergique depuis le sujet" },
28
28
  "breathing": { label: "Caméra respirante", description: "Oscillation continue et subtile de poussée et recul, sensation organique caméra à l'épaule" },
29
29
  "push-pull": { label: "Push-Pull / Va-et-vient", description: "La caméra avance vers le sujet puis recule, approche-retrait oscillant" },
30
30
  "creep-in": { label: "Approche imperceptible", description: "Poussée extrêmement lente et imperceptible qui installe la peur ou la tension" },
@@ -40,8 +40,8 @@ const map: LocaleCatalogMap = {
40
40
  "roll-right": { label: "Roulis à droite", description: "Pivoter la caméra dans le sens horaire" },
41
41
  "dutch-angle": { description: "Cadre incliné statique pour la tension" },
42
42
  // Orbit
43
- "orbit-left": { label: "Orbite à gauche", description: "Cercle complet autour du sujet vers la gauche" },
44
- "orbit-right": { label: "Orbite à droite", description: "Cercle complet autour du sujet vers la droite" },
43
+ "orbit-left": { label: "Orbite à gauche", description: "Grande orbite partielle autour du sujet vers la gauche" },
44
+ "orbit-right": { label: "Orbite à droite", description: "Grande orbite partielle autour du sujet vers la droite" },
45
45
  "spin-360": { label: "Tour complet 360°", description: "La caméra effectue un tour complet de 360 degrés sur son axe" },
46
46
  "orbit-360": { label: "Orbite complète 360°", description: "La caméra décrit un arc complet de 360 degrés autour du sujet" },
47
47
  "arc-left": { label: "Arc à gauche", description: "Arc partiel autour du sujet à gauche" },
@@ -75,7 +75,7 @@ const map: LocaleCatalogMap = {
75
75
  "handheld-vlog": { label: "Vlog à l'épaule", description: "Caméra à l'épaule décontractée style vlog" },
76
76
  "pov-walk": { label: "Marche en POV", description: "POV de marche à la première personne" },
77
77
  "velocity-edit": { description: "Cadence en speed-ramp style TikTok" },
78
- "match-cut-zoom": { description: "Zoom synchronisé sur un beat pour des coupes" },
78
+ "match-cut-zoom": { description: "Zoom rapide avec coupe franche sur une forme identique" },
79
79
  "screen-tap": { description: "Transition tap à l'écran à l'image" },
80
80
  "phone-flip": { description: "Bascule entre caméra avant et arrière" },
81
81
  // Location-studio extension (PR #2505 follow-up)
@@ -22,8 +22,8 @@ const map: LocaleCatalogMap = {
22
22
  "dolly-in": { label: "Dolly In", description: "דחף מצלמה לעבר הסובייקט (פרלקסה)" },
23
23
  "dolly-out": { label: "Dolly Out", description: "משוך מצלמה הרחק (פרלקסה)" },
24
24
  "dolly-zoom": { label: "Dolly Zoom", description: "אפקט ורטיגו: dolly מנוגד ל-zoom" },
25
- "push-in": { label: "Push In", description: "דחיפה איטית עדינה לעבר הסובייקט" },
26
- "pull-out": { label: "Pull Out", description: "משיכה איטית עדינה מהסובייקט" },
25
+ "push-in": { label: "Push In", description: "דחיפה מהירה ונמרצת לעבר הסובייקט" },
26
+ "pull-out": { label: "Pull Out", description: "משיכה מהירה ונמרצת מהסובייקט" },
27
27
  "breathing": { label: "מצלמה נושמת", description: "תנודה עדינה ורציפה של push-in / pull-out, תחושת handheld אורגנית" },
28
28
  "push-pull": { label: "Push-Pull / נדנוד", description: "המצלמה נעה לעבר הסובייקט וחזרה אחורה, גישה והתרחקות מתנדנדות" },
29
29
  "creep-in": { label: "Creep-In", description: "Push-in איטי באופן בלתי מורגש, בונה אימה או מתח" },
@@ -39,8 +39,8 @@ const map: LocaleCatalogMap = {
39
39
  "roll-right": { label: "Roll ימינה", description: "סובב מצלמה עם כיוון השעון" },
40
40
  "dutch-angle": { label: "Dutch Angle", description: "פריים מוטה סטטי ליצירת מתח" },
41
41
 
42
- "orbit-left": { label: "Orbit שמאלה", description: "מעגל שלם סביב הסובייקט שמאלה" },
43
- "orbit-right": { label: "Orbit ימינה", description: "מעגל שלם סביב הסובייקט ימינה" },
42
+ "orbit-left": { label: "Orbit שמאלה", description: "הקפה חלקית רחבה סביב הסובייקט שמאלה" },
43
+ "orbit-right": { label: "Orbit ימינה", description: "הקפה חלקית רחבה סביב הסובייקט ימינה" },
44
44
  "spin-360": { label: "סיבוב מלא 360°", description: "המצלמה מסתובבת 360 מעלות מלאות סביב צירה" },
45
45
  "orbit-360": { label: "Orbit מלא 360°", description: "המצלמה מתארת קשת מלאה של 360 מעלות סביב הסובייקט" },
46
46
  "arc-left": { label: "Arc שמאלה", description: "קשת חלקית סביב הסובייקט שמאלה" },
@@ -75,7 +75,7 @@ const map: LocaleCatalogMap = {
75
75
  "handheld-vlog": { label: "Vlog יד", description: "מצלמת יד קז'ואל בסגנון vlog" },
76
76
  "pov-walk": { label: "POV הליכה", description: "POV הליכה בגוף ראשון" },
77
77
  "velocity-edit": { label: "Velocity Edit", description: "קצב Speed-ramp של TikTok" },
78
- "match-cut-zoom": { label: "Match Cut Zoom", description: "Zoom בקצב לחיתוכים" },
78
+ "match-cut-zoom": { label: "Match Cut Zoom", description: "Zoom מהיר עם חיתוך חד לצורה תואמת" },
79
79
  "screen-tap": { label: "Screen Tap", description: "מעבר עם נקישת אצבע על המסך" },
80
80
  "phone-flip": { label: "היפוך טלפון", description: "היפוך מצלמה קדמית/אחורית" },
81
81
  // Location-studio extension (PR #2505 follow-up)
@@ -27,8 +27,8 @@ const map: LocaleCatalogMap = {
27
27
  "dolly-in": { label: "Dolly In", description: "camera को subject की ओर धकेलें (parallax)" },
28
28
  "dolly-out": { label: "Dolly Out", description: "camera को दूर खींचें (parallax)" },
29
29
  "dolly-zoom": { label: "Dolly Zoom", description: "Vertigo प्रभाव: dolly zoom के विपरीत" },
30
- "push-in": { label: "Push In", description: "subject की ओर धीमा सूक्ष्म push" },
31
- "pull-out": { label: "Pull Out", description: "subject से धीमा सूक्ष्म pull back" },
30
+ "push-in": { label: "Push In", description: "subject की ओर तेज़ ज़ोरदार push" },
31
+ "pull-out": { label: "Pull Out", description: "subject से तेज़ ज़ोरदार pull back" },
32
32
  "breathing": { label: "Breathing Camera", description: "सूक्ष्म निरंतर push-in / pull-out दोलन, organic handheld अनुभव" },
33
33
  "push-pull": { label: "Push-Pull / Swing", description: "Camera subject की ओर बढ़ता है फिर पीछे, झूलता हुआ approach-and-retreat" },
34
34
  "creep-in": { label: "Creep-In", description: "अदृश्य रूप से धीमा push-in जो भय या तनाव बढ़ाता है" },
@@ -48,8 +48,8 @@ const map: LocaleCatalogMap = {
48
48
  "dutch-angle": { label: "Dutch Angle", description: "तनाव के लिए स्थिर tilted frame" },
49
49
 
50
50
  // Orbit / Arc
51
- "orbit-left": { label: "Orbit बाएँ", description: "subject के चारों ओर बाएँ पूरा वृत्त" },
52
- "orbit-right": { label: "Orbit दाएँ", description: "subject के चारों ओर दाएँ पूरा वृत्त" },
51
+ "orbit-left": { label: "Orbit बाएँ", description: "subject के चारों ओर बाएँ बड़ा आंशिक orbit" },
52
+ "orbit-right": { label: "Orbit दाएँ", description: "subject के चारों ओर दाएँ बड़ा आंशिक orbit" },
53
53
  "spin-360": { label: "Full 360 Spin", description: "Camera अपनी धुरी पर पूरे 360 degree घूमता है" },
54
54
  "orbit-360": { label: "Full 360 Orbit", description: "Camera subject के चारों ओर पूरे 360 degree का arc बनाता है" },
55
55
  "arc-left": { label: "Arc बाएँ", description: "subject के चारों ओर बाएँ आंशिक arc" },
@@ -88,7 +88,7 @@ const map: LocaleCatalogMap = {
88
88
  "handheld-vlog": { label: "Handheld Vlog", description: "Casual vlog-शैली का handheld" },
89
89
  "pov-walk": { label: "POV Walk", description: "प्रथम-व्यक्ति चलते हुए POV" },
90
90
  "velocity-edit": { label: "Velocity Edit", description: "TikTok speed-ramp pacing" },
91
- "match-cut-zoom": { label: "Match Cut Zoom", description: "Cut के लिए beat-समय वाला zoom" },
91
+ "match-cut-zoom": { label: "Match Cut Zoom", description: "मेल खाती आकृति पर hard cut वाला तेज़ zoom" },
92
92
  "screen-tap": { label: "Screen Tap", description: "On-screen finger-tap transition" },
93
93
  "phone-flip": { label: "Phone Flip", description: "आगे/पीछे camera flip" },
94
94
  // Location-studio extension (PR #2505 follow-up)
@@ -27,8 +27,8 @@ const map: LocaleCatalogMap = {
27
27
  "dolly-in": { label: "ドリー・イン", description: "被写体に向かってカメラを押し込む(パララックスあり)" },
28
28
  "dolly-out": { label: "ドリー・アウト", description: "カメラを引いていく(パララックスあり)" },
29
29
  "dolly-zoom": { label: "ドリーズーム", description: "ヴァーティゴ効果:ドリーがズームと逆方向" },
30
- "push-in": { label: "プッシュイン", description: "被写体へのゆっくり繊細な押し込み" },
31
- "pull-out": { label: "プルアウト", description: "被写体からのゆっくり繊細な引き戻し" },
30
+ "push-in": { label: "プッシュイン", description: "被写体への速く力強い押し込み" },
31
+ "pull-out": { label: "プルアウト", description: "被写体からの速く力強い引き戻し" },
32
32
  "breathing": { label: "ブリージング・カメラ", description: "繊細で連続的なプッシュインとプルアウトの揺らぎ、有機的な手持ちの感触" },
33
33
  "push-pull": { label: "プッシュプル/スウィング", description: "カメラが被写体に近づいてから離れる、振り子のような接近と後退" },
34
34
  "creep-in": { label: "クリープイン", description: "ほとんど気づかれないほどゆっくりのプッシュイン、恐怖や緊張を高める" },
@@ -48,8 +48,8 @@ const map: LocaleCatalogMap = {
48
48
  "dutch-angle": { label: "ダッチアングル", description: "緊張感を与える固定の傾いたフレーム" },
49
49
 
50
50
  // Orbit
51
- "orbit-left": { label: "オービット・レフト", description: "被写体の周りを左に1周回る" },
52
- "orbit-right": { label: "オービット・ライト", description: "被写体の周りを右に1周回る" },
51
+ "orbit-left": { label: "オービット・レフト", description: "被写体の周りを左に大きく部分的に回る" },
52
+ "orbit-right": { label: "オービット・ライト", description: "被写体の周りを右に大きく部分的に回る" },
53
53
  "spin-360": { label: "フル360°スピン", description: "カメラが軸上で360度フル回転する" },
54
54
  "orbit-360": { label: "フル360°オービット", description: "カメラが被写体の周りを360度の完全な弧で回る" },
55
55
  "arc-left": { label: "アーク・レフト", description: "被写体の周りを左に部分的に弧を描く" },
@@ -88,7 +88,7 @@ const map: LocaleCatalogMap = {
88
88
  "handheld-vlog": { label: "ハンドヘルド・ヴログ", description: "カジュアルなヴログ風の手持ち" },
89
89
  "pov-walk": { label: "POVウォーク", description: "一人称の歩行POV" },
90
90
  "velocity-edit": { label: "ベロシティエディット", description: "TikTok風のスピードランプ・ペーシング" },
91
- "match-cut-zoom": { label: "マッチカット・ズーム", description: "カット用にビートに合わせたズーム" },
91
+ "match-cut-zoom": { label: "マッチカット・ズーム", description: "同じ形にハードカットする速いズーム" },
92
92
  "screen-tap": { label: "スクリーンタップ", description: "画面上の指タップによるトランジション" },
93
93
  "phone-flip": { label: "フォンフリップ", description: "前面/背面カメラの切り替え" },
94
94
  // Location-studio extension (PR #2505 follow-up)
@@ -27,8 +27,8 @@ const map: LocaleCatalogMap = {
27
27
  "dolly-in": { label: "달리 인", description: "피사체를 향해 카메라를 밀어 넣습니다 (시차)" },
28
28
  "dolly-out": { label: "달리 아웃", description: "카메라를 멀리 밀어냅니다 (시차)" },
29
29
  "dolly-zoom": { label: "달리 줌", description: "버티고 효과: 달리와 줌이 반대로 동작합니다" },
30
- "push-in": { label: "푸시 인", description: "피사체를 향한 천천히 미묘한 푸시입니다" },
31
- "pull-out": { label: "풀 아웃", description: "피사체로부터 천천히 미묘한 풀백입니다" },
30
+ "push-in": { label: "푸시 인", description: "피사체를 향한 빠르고 힘찬 푸시입니다" },
31
+ "pull-out": { label: "풀 아웃", description: "피사체로부터 빠르고 힘찬 풀백입니다" },
32
32
  "breathing": { label: "브리딩 카메라", description: "은은하게 지속되는 푸시 인 / 풀 아웃의 진동, 유기적인 핸드헬드 느낌입니다" },
33
33
  "push-pull": { label: "푸시 풀 / 스윙", description: "카메라가 피사체를 향해 다가갔다가 다시 멀어지는, 흔들리는 접근과 후퇴입니다" },
34
34
  "creep-in": { label: "크리프 인", description: "감지하기 어려울 만큼 느린 푸시 인으로 공포감이나 긴장감을 쌓아갑니다" },
@@ -48,8 +48,8 @@ const map: LocaleCatalogMap = {
48
48
  "dutch-angle": { label: "더치 앵글", description: "긴장감을 주는 정적 기울어진 프레임입니다" },
49
49
 
50
50
  // Orbit / Arc
51
- "orbit-left": { label: "오비트 왼쪽", description: "피사체 주위를 왼쪽으로 한 바퀴 도는 궤도입니다" },
52
- "orbit-right": { label: "오비트 오른쪽", description: "피사체 주위를 오른쪽으로 한 바퀴 도는 궤도입니다" },
51
+ "orbit-left": { label: "오비트 왼쪽", description: "피사체 주위를 왼쪽으로 크게 부분 선회합니다" },
52
+ "orbit-right": { label: "오비트 오른쪽", description: "피사체 주위를 오른쪽으로 크게 부분 선회합니다" },
53
53
  "spin-360": { label: "풀 360° 스핀", description: "카메라가 자신의 축을 중심으로 360도 완전 회전합니다" },
54
54
  "orbit-360": { label: "풀 360° 오비트", description: "카메라가 피사체 주위를 360도 완전한 호로 돕니다" },
55
55
  "arc-left": { label: "아크 왼쪽", description: "피사체 주위를 왼쪽으로 부분적으로 도는 호입니다" },
@@ -88,7 +88,7 @@ const map: LocaleCatalogMap = {
88
88
  "handheld-vlog": { label: "핸드헬드 블로그", description: "캐주얼한 블로그 스타일 핸드헬드입니다" },
89
89
  "pov-walk": { label: "POV 워크", description: "1인칭 보행 POV입니다" },
90
90
  "velocity-edit": { label: "벨로시티 에디트", description: "TikTok 스피드 램프 페이싱입니다" },
91
- "match-cut-zoom": { label: "매치 컷 줌", description: "컷에 맞춘 비트 타이밍 줌입니다" },
91
+ "match-cut-zoom": { label: "매치 컷 줌", description: "일치하는 형태로 하드 컷하는 빠른 줌입니다" },
92
92
  "screen-tap": { label: "스크린 탭", description: "화면 위 손가락 탭 트랜지션입니다" },
93
93
  "phone-flip": { label: "폰 플립", description: "전면/후면 카메라 플립입니다" },
94
94
  // Location-studio extension (PR #2505 follow-up)
@@ -27,8 +27,8 @@ const map: LocaleCatalogMap = {
27
27
  "dolly-in": { description: "Empurra a câmera em direção ao sujeito (com paralaxe)" },
28
28
  "dolly-out": { description: "Afasta a câmera (com paralaxe)" },
29
29
  "dolly-zoom": { description: "Efeito vertigo: dolly oposto ao zoom" },
30
- "push-in": { description: "Empurro lento e sutil em direção ao sujeito" },
31
- "pull-out": { description: "Afastamento lento e sutil do sujeito" },
30
+ "push-in": { description: "Empurro rápido e enérgico em direção ao sujeito" },
31
+ "pull-out": { description: "Afastamento rápido e enérgico do sujeito" },
32
32
  "breathing": { label: "Câmera Respirante", description: "Oscilação contínua e sutil de empurro e afastamento, sensação orgânica de câmera na mão" },
33
33
  "push-pull": { label: "Push-Pull / Vai-e-Vem", description: "A câmera se move em direção ao sujeito e depois se afasta, aproximação e retirada oscilantes" },
34
34
  "creep-in": { label: "Aproximação Imperceptível", description: "Empurro extremamente lento e imperceptível que constrói pavor ou tensão" },
@@ -48,8 +48,8 @@ const map: LocaleCatalogMap = {
48
48
  "dutch-angle": { description: "Quadro estático inclinado para criar tensão" },
49
49
 
50
50
  // Orbit / Arc
51
- "orbit-left": { description: "Volta completa em torno do sujeito para a esquerda" },
52
- "orbit-right": { description: "Volta completa em torno do sujeito para a direita" },
51
+ "orbit-left": { description: "Órbita parcial ampla em torno do sujeito para a esquerda" },
52
+ "orbit-right": { description: "Órbita parcial ampla em torno do sujeito para a direita" },
53
53
  "spin-360": { label: "Giro Completo 360°", description: "A câmera gira 360 graus completos sobre seu próprio eixo" },
54
54
  "orbit-360": { label: "Órbita Completa 360°", description: "A câmera descreve um arco completo de 360 graus em torno do sujeito" },
55
55
  "arc-left": { description: "Arco parcial em torno do sujeito pela esquerda" },
@@ -88,7 +88,7 @@ const map: LocaleCatalogMap = {
88
88
  "handheld-vlog": { description: "Câmera na mão estilo vlog descontraído" },
89
89
  "pov-walk": { description: "POV de caminhada em primeira pessoa" },
90
90
  "velocity-edit": { description: "Pacing com speed-ramp estilo TikTok" },
91
- "match-cut-zoom": { description: "Zoom no tempo de batida para corte" },
91
+ "match-cut-zoom": { description: "Zoom rápido com corte seco para uma forma correspondente" },
92
92
  "screen-tap": { description: "Transição com toque na tela" },
93
93
  "phone-flip": { description: "Troca entre câmera frontal e traseira" },
94
94
  // Location-studio extension (PR #2505 follow-up)
@@ -27,8 +27,8 @@ const map: LocaleCatalogMap = {
27
27
  "dolly-in": { label: "Долли вперёд", description: "Толчок камеры к объекту (с параллаксом)" },
28
28
  "dolly-out": { label: "Долли назад", description: "Отъезд камеры назад (с параллаксом)" },
29
29
  "dolly-zoom": { label: "Долли-зум", description: "Эффект головокружения: долли против зума" },
30
- "push-in": { label: "Лёгкий наезд", description: "Медленный мягкий наезд на объект" },
31
- "pull-out": { label: "Лёгкий отъезд", description: "Медленный мягкий отъезд от объекта" },
30
+ "push-in": { label: "Резкий наезд", description: "Быстрый энергичный наезд на объект" },
31
+ "pull-out": { label: "Резкий отъезд", description: "Быстрый энергичный отъезд от объекта" },
32
32
  "breathing": { label: "Дышащая камера", description: "Тонкое непрерывное колебание наезд-отъезд, органичное ощущение съёмки с рук" },
33
33
  "push-pull": { label: "Push-Pull / Качание", description: "Камера движется к объекту, затем обратно, маятниковое приближение и отдаление" },
34
34
  "creep-in": { label: "Незаметный наезд", description: "Чрезвычайно медленный незаметный наезд, нагнетающий страх или напряжение" },
@@ -48,8 +48,8 @@ const map: LocaleCatalogMap = {
48
48
  "dutch-angle": { label: "Голландский угол", description: "Статичный наклонённый кадр для напряжения" },
49
49
 
50
50
  // Orbit / Arc
51
- "orbit-left": { label: "Орбита влево", description: "Полный круг вокруг объекта влево" },
52
- "orbit-right": { label: "Орбита вправо", description: "Полный круг вокруг объекта вправо" },
51
+ "orbit-left": { label: "Орбита влево", description: "Большая частичная орбита вокруг объекта влево" },
52
+ "orbit-right": { label: "Орбита вправо", description: "Большая частичная орбита вокруг объекта вправо" },
53
53
  "spin-360": { label: "Полный оборот 360°", description: "Камера поворачивается на полные 360 градусов вокруг своей оси" },
54
54
  "orbit-360": { label: "Полная орбита 360°", description: "Камера описывает полную дугу 360 градусов вокруг объекта" },
55
55
  "arc-left": { label: "Дуга влево", description: "Частичная дуга вокруг объекта влево" },
@@ -88,7 +88,7 @@ const map: LocaleCatalogMap = {
88
88
  "handheld-vlog": { label: "Влог с рук", description: "Непринуждённая ручная съёмка в стиле влога" },
89
89
  "pov-walk": { label: "POV-ходьба", description: "POV-ходьба от первого лица" },
90
90
  "velocity-edit": { label: "Скоростной монтаж", description: "Темп с ускорением в стиле TikTok" },
91
- "match-cut-zoom": { label: "Зум для match cut", description: "Зум, синхронизированный с битом для склейки" },
91
+ "match-cut-zoom": { label: "Зум для match cut", description: "Быстрый зум с резкой склейкой на совпадающую форму" },
92
92
  "screen-tap": { label: "Касание экрана", description: "Переход с касанием пальца по экрану" },
93
93
  "phone-flip": { label: "Переключение камеры телефона", description: "Переключение между фронтальной и основной камерой" },
94
94
  // Location-studio extension (PR #2505 follow-up)
@@ -27,8 +27,8 @@ const map: LocaleCatalogMap = {
27
27
  "dolly-in": { label: "推轨", description: "推动机位靠近被摄对象(带视差)" },
28
28
  "dolly-out": { label: "拉轨", description: "拉远机位远离被摄对象(带视差)" },
29
29
  "dolly-zoom": { label: "滑动变焦", description: "眩晕效果:推轨与变焦反向" },
30
- "push-in": { label: "缓推", description: "缓慢轻柔地推向被摄对象" },
31
- "pull-out": { label: "缓拉", description: "缓慢轻柔地拉远被摄对象" },
30
+ "push-in": { label: "急推", description: "快速有力地推向被摄对象" },
31
+ "pull-out": { label: "急拉", description: "快速有力地拉远被摄对象" },
32
32
  "breathing": { label: "呼吸式镜头", description: "细微而连续的推拉震荡,有机的手持质感" },
33
33
  "push-pull": { label: "推拉/摇摆", description: "镜头先靠近主体再后退,摆动式的接近与撤离" },
34
34
  "creep-in": { label: "渐近推近", description: "几乎察觉不到的极慢推近,营造恐惧或紧张感" },
@@ -48,8 +48,8 @@ const map: LocaleCatalogMap = {
48
48
  "dutch-angle": { label: "荷兰角", description: "营造紧张感的固定倾斜画面" },
49
49
 
50
50
  // Orbit / Arc
51
- "orbit-left": { label: "向左环绕", description: "镜头绕被摄对象向左完整环绕" },
52
- "orbit-right": { label: "向右环绕", description: "镜头绕被摄对象向右完整环绕" },
51
+ "orbit-left": { label: "向左环绕", description: "镜头绕被摄对象向左大幅部分环绕" },
52
+ "orbit-right": { label: "向右环绕", description: "镜头绕被摄对象向右大幅部分环绕" },
53
53
  "spin-360": { label: "360°整圈自转", description: "镜头围绕自身轴心整圈360度旋转" },
54
54
  "orbit-360": { label: "360°整圈环绕", description: "镜头围绕主体整圈360度环行" },
55
55
  "arc-left": { label: "左弧形运镜", description: "围绕被摄对象向左做局部弧线" },
@@ -88,7 +88,7 @@ const map: LocaleCatalogMap = {
88
88
  "handheld-vlog": { label: "手持 Vlog", description: "随性的 Vlog 风格手持" },
89
89
  "pov-walk": { label: "主观行走", description: "第一人称行走主观镜头" },
90
90
  "velocity-edit": { label: "速度剪辑", description: "TikTok 节奏的速率变化" },
91
- "match-cut-zoom": { label: "节拍变焦", description: "卡点变焦用于剪辑" },
91
+ "match-cut-zoom": { label: "节拍变焦", description: "快速变焦后硬切到相同形状" },
92
92
  "screen-tap": { label: "点屏切换", description: "屏幕上手指点击触发的转场" },
93
93
  "phone-flip": { label: "翻面切换", description: "前后摄像头翻转切换" },
94
94
  // Location-studio extension (PR #2505 follow-up)