@campfire-interactive/volume-intelligence-ui 0.10.0 → 0.12.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 (3) hide show
  1. package/dist/index.d.ts +63 -11
  2. package/dist/index.js +131 -4
  3. package/package.json +43 -43
package/dist/index.d.ts CHANGED
@@ -90,8 +90,12 @@ declare function VolumeImport(props: VolumeImportProps): react.JSX.Element;
90
90
 
91
91
  type SourceLayout = 'wide' | 'tall';
92
92
  /** `base` = IHS/AFS historical training signal (one active per tenant);
93
- * `overlay` = EDI-style additive customer-stated demand. */
94
- type VolumeSourceKind = 'base' | 'overlay';
93
+ * `overlay` = EDI-style additive customer-stated demand;
94
+ * `reference` = a second market vendor's curve, drawn beside the active base
95
+ * rather than summed into it. A reference source never drives prediction,
96
+ * never supplies level labels, and is never what an overlay links against —
97
+ * which is why it is a kind of its own rather than a second `base`. */
98
+ type VolumeSourceKind = 'base' | 'overlay' | 'reference';
95
99
  interface VolumeSource {
96
100
  id: string;
97
101
  code: string;
@@ -414,14 +418,6 @@ interface VolumeSourcesListProps {
414
418
  readOnly?: boolean;
415
419
  className?: string;
416
420
  }
417
- /**
418
- * Volume-source list: cards per source, "add from template", manual create,
419
- * and activate (with the data-wipe confirm). Transport-agnostic and router-free
420
- * — the host supplies `transport` and an `onOpenSource` navigation callback.
421
- *
422
- * The VI-webapp's active-source analytical sub-cards (enrichment toggle,
423
- * backtests) are intentionally NOT here — per ADR §6 they stay VI-webapp-only.
424
- */
425
421
  declare function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras, readOnly, className }: VolumeSourcesListProps): react.JSX.Element;
426
422
 
427
423
  /**
@@ -524,6 +520,62 @@ interface ValueAliasesCardProps {
524
520
  }
525
521
  declare function ValueAliasesCard({ transport, sourceId, locked, isOverlay, targets }: ValueAliasesCardProps): react.JSX.Element;
526
522
 
523
+ /**
524
+ * What each source role looks like, and why it looks like that.
525
+ *
526
+ * The colors are not decorative. Each one is the stroke the forecast chart
527
+ * draws that role's data in, so a source badged teal here is the teal dashed
528
+ * curve there. That is the whole point of coloring this screen: the operator
529
+ * setting a source to `reference` should be able to see, before loading a
530
+ * single row, which line on the chart they are about to create.
531
+ *
532
+ * The chart's table lives in the app (`SERIES_STYLE` in ForecastChart) and this
533
+ * one lives in the package, so they cannot literally be the same constant —
534
+ * the package must not depend on the app. They are duplicated deliberately and
535
+ * this comment is the link. If the chart repaints a series, repaint it here.
536
+ */
537
+ interface RoleStyle {
538
+ /** The chart stroke for the series this role feeds. */
539
+ color: string;
540
+ /** Sentence-case name for a badge. */
541
+ label: string;
542
+ /** One line on what the role does, shown under the selector. */
543
+ blurb: string;
544
+ /** Matches the chart's dashed reference curve. */
545
+ dash?: string;
546
+ }
547
+ declare const ROLE_STYLE: Record<VolumeSourceKind, RoleStyle>;
548
+ declare const ROLE_ORDER: VolumeSourceKind[];
549
+
550
+ /** The chart-legend glyph: a stroke with its marker on it.
551
+ *
552
+ * Same shape the forecast chart's provider row uses, for the same reason —
553
+ * these stand for lines, and the reference role's line is dashed. A plain dot
554
+ * would drop that, and teal-dashed against teal-solid is the difference
555
+ * between "a second vendor" and "the main curve". */
556
+ declare function RoleSwatch(props: {
557
+ color: string;
558
+ dash?: string;
559
+ title?: string;
560
+ }): react.JSX.Element;
561
+ /** One joined control with hairline dividers, not a row of separate pills.
562
+ *
563
+ * Mirrors the forecast chart's "View by" control so the two screens read as
564
+ * one product. `activeColor` fills the selected segment, which is how the
565
+ * role's chart color gets stated before any data exists to draw. */
566
+ declare function Segmented<T extends string>(props: {
567
+ value: T;
568
+ options: {
569
+ value: T;
570
+ label: ReactNode;
571
+ title?: string;
572
+ activeColor?: string;
573
+ }[];
574
+ onChange: (value: T) => void;
575
+ disabled?: boolean;
576
+ ariaLabel: string;
577
+ }): react.JSX.Element;
578
+
527
579
  declare const HIERARCHY_LEVEL_COUNT = 6;
528
580
  /** Display labels for hierarchy levels 1..6 (the mapping of source columns to
529
581
  * levels is per-tenant config; this only controls the label). */
@@ -535,4 +587,4 @@ declare const DEFAULT_LEVEL_NAMES: Record<number, string>;
535
587
  declare const AUTOMOTIVE_SLOTS: AutomotiveSlot[];
536
588
  declare const AUTOMOTIVE_TRANSFORM_LABELS: Record<string, string>;
537
589
 
538
- export { AUTOMOTIVE_SLOTS, AUTOMOTIVE_TRANSFORM_LABELS, type AliasStatus, type AliasTarget, type AutomotiveSlot, type ClearSourceDataResult, type CreateSegmentInput, type CreateSourceInput, DEFAULT_LEVEL_NAMES, type DetectSegmentsResult, HIERARCHY_LEVEL_COUNT, type ListErrorsResult, type MappableDimension, type RelinkResult, type RequiredMappingStatus, type SegmentMappingCategory, type SegmentMappingCategoryKeys, type SegmentMappingEntry, type SegmentMappingKeys, type SegmentMappingRow, type SourceAdminTransport, type SourceLayout, type SourceTemplateSummary, type SourceValueAlias, type SourceWithSegments, TallLayoutMappings, type TallLayoutMappingsProps, type TenantVolumeConfig, UnifiedSegmentMapper, type UnifiedSegmentMapperProps, type UpdateSourceInput, type Upload, type UploadError, type UploadErrorRow, type UploadInput, type UploadListParams, type UploadListResponse, type UploadRecord, type UploadStats, type UploadStatus, type ValueAliasEntry, ValueAliasesCard, type ValueAliasesCardProps, VolumeFiles, type VolumeFilesProps, type VolumeFilesTransport, VolumeImport, type VolumeImportProps, type VolumeSource$1 as VolumeImportSource, type VolumeImportTransport, type UploadStatus$1 as VolumeImportUploadStatus, type VolumeSource, VolumeSourceDetail, type VolumeSourceDetailProps, type VolumeSourceKind, type VolumeSourceLayout, type VolumeSourceSegment, VolumeSourcesList, type VolumeSourcesListProps };
590
+ export { AUTOMOTIVE_SLOTS, AUTOMOTIVE_TRANSFORM_LABELS, type AliasStatus, type AliasTarget, type AutomotiveSlot, type ClearSourceDataResult, type CreateSegmentInput, type CreateSourceInput, DEFAULT_LEVEL_NAMES, type DetectSegmentsResult, HIERARCHY_LEVEL_COUNT, type ListErrorsResult, type MappableDimension, ROLE_ORDER, ROLE_STYLE, type RelinkResult, type RequiredMappingStatus, type RoleStyle, RoleSwatch, type SegmentMappingCategory, type SegmentMappingCategoryKeys, type SegmentMappingEntry, type SegmentMappingKeys, type SegmentMappingRow, Segmented, type SourceAdminTransport, type SourceLayout, type SourceTemplateSummary, type SourceValueAlias, type SourceWithSegments, TallLayoutMappings, type TallLayoutMappingsProps, type TenantVolumeConfig, UnifiedSegmentMapper, type UnifiedSegmentMapperProps, type UpdateSourceInput, type Upload, type UploadError, type UploadErrorRow, type UploadInput, type UploadListParams, type UploadListResponse, type UploadRecord, type UploadStats, type UploadStatus, type ValueAliasEntry, ValueAliasesCard, type ValueAliasesCardProps, VolumeFiles, type VolumeFilesProps, type VolumeFilesTransport, VolumeImport, type VolumeImportProps, type VolumeSource$1 as VolumeImportSource, type VolumeImportTransport, type UploadStatus$1 as VolumeImportUploadStatus, type VolumeSource, VolumeSourceDetail, type VolumeSourceDetailProps, type VolumeSourceKind, type VolumeSourceLayout, type VolumeSourceSegment, VolumeSourcesList, type VolumeSourcesListProps };
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ function styleInject(css, { insertAt } = {}) {
25
25
  }
26
26
 
27
27
  // src/styles.css
28
- styleInject(".cfi-volume-import {\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n font-size: var(--cfi-font-size-base, 14px);\n color: var(--cfi-color-gray-900, #111827);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 12px);\n max-width: 640px;\n}\n.cfi-volume-import .cfi-vi-field {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-xs, 4px);\n}\n.cfi-volume-import .cfi-vi-row {\n display: flex;\n gap: var(--cfi-spacing-md, 12px);\n flex-wrap: wrap;\n}\n.cfi-volume-import .cfi-vi-row .cfi-vi-field {\n flex: 1 1 8rem;\n}\n.cfi-volume-import .cfi-vi-label {\n font-size: var(--cfi-font-size-sm, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-volume-import .cfi-vi-select,\n.cfi-volume-import .cfi-vi-file {\n padding: 6px 8px;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n font: inherit;\n color: inherit;\n}\n.cfi-volume-import .cfi-vi-select:focus,\n.cfi-volume-import .cfi-vi-file:focus {\n outline: 2px solid var(--cfi-color-primary-600, #2563eb);\n outline-offset: 1px;\n}\n.cfi-volume-import .cfi-vi-button {\n display: inline-flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 6px);\n align-self: flex-start;\n padding: 8px 14px;\n border: none;\n border-radius: var(--cfi-radius-md, 6px);\n background: var(--cfi-color-primary-600, #2563eb);\n color: #fff;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n.cfi-volume-import .cfi-vi-button:hover:not(:disabled) {\n background: var(--cfi-color-primary-700, #1d4ed8);\n}\n.cfi-volume-import .cfi-vi-button:disabled {\n background: var(--cfi-color-gray-300, #d1d5db);\n color: var(--cfi-color-gray-500, #6b7280);\n cursor: not-allowed;\n}\n.cfi-volume-import .cfi-vi-loading,\n.cfi-volume-import .cfi-vi-alert {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 6px);\n padding: 8px 10px;\n border-radius: var(--cfi-radius-md, 6px);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-volume-import .cfi-vi-alert--error {\n background: var(--cfi-color-gray-100, #fef2f2);\n color: var(--cfi-color-error, #b91c1c);\n}\n.cfi-volume-import .cfi-vi-alert--warn {\n background: #fffbeb;\n color: #92400e;\n align-items: flex-start;\n}\n.cfi-volume-import .cfi-vi-blockers {\n margin: 0;\n padding-left: 1.1rem;\n}\n.cfi-volume-import .cfi-vi-status {\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 6px);\n padding: var(--cfi-spacing-md, 12px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-volume-import .cfi-vi-status-head {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-volume-import .cfi-vi-filename {\n font-weight: 600;\n}\n.cfi-volume-import .cfi-vi-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n border-radius: 999px;\n font-size: var(--cfi-font-size-xs, 11px);\n text-transform: capitalize;\n background: var(--cfi-color-gray-100, #f3f4f6);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-volume-import .cfi-vi-badge--done {\n background: #dcfce7;\n color: #166534;\n}\n.cfi-volume-import .cfi-vi-badge--done_with_errors {\n background: #fef9c3;\n color: #854d0e;\n}\n.cfi-volume-import .cfi-vi-badge--failed {\n background: #fee2e2;\n color: #991b1b;\n}\n.cfi-volume-import .cfi-vi-badge--processing,\n.cfi-volume-import .cfi-vi-badge--pending,\n.cfi-volume-import .cfi-vi-badge--awaiting_calendar {\n background: #dbeafe;\n color: #1e40af;\n}\n.cfi-volume-import .cfi-vi-link {\n background: none;\n border: none;\n padding: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--cfi-color-primary-600, #2563eb);\n font: inherit;\n cursor: pointer;\n}\n.cfi-volume-import .cfi-vi-stats {\n display: flex;\n gap: var(--cfi-spacing-md, 12px);\n color: var(--cfi-color-gray-600, #4b5563);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-volume-import .cfi-vi-errors {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-volume-import .cfi-vi-errors th,\n.cfi-volume-import .cfi-vi-errors td {\n text-align: left;\n padding: 4px 8px;\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n vertical-align: top;\n}\n.cfi-volume-import .cfi-vi-spin {\n animation: cfi-vi-spin 0.8s linear infinite;\n}\n@keyframes cfi-vi-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.cfi-vi {\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n font-size: var(--cfi-font-size-base, 14px);\n color: var(--cfi-color-gray-900, #111827);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 12px);\n}\n.cfi-vi *,\n.cfi-vi *::before,\n.cfi-vi *::after {\n box-sizing: border-box;\n}\n.cfi-vi .cfi-vi-row {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-vi .cfi-vi-row--between {\n justify-content: space-between;\n}\n.cfi-vi .cfi-vi-spacer {\n flex: 1;\n}\n.cfi-vi .cfi-vi-muted {\n color: var(--cfi-color-gray-600, #4b5563);\n}\n.cfi-vi .cfi-vi-error {\n color: var(--cfi-color-error, #b91c1c);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-vi .cfi-vi-mono {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n monospace;\n font-size: var(--cfi-font-size-xs, 11px);\n}\n.cfi-vi .cfi-vi-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n border-radius: var(--cfi-radius-md, 6px);\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n white-space: nowrap;\n}\n.cfi-vi .cfi-vi-btn--sm {\n padding: 4px 10px;\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-vi .cfi-vi-btn--md {\n padding: 8px 14px;\n}\n.cfi-vi .cfi-vi-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.cfi-vi .cfi-vi-btn--primary {\n background: var(--cfi-color-primary-600, #2563eb);\n color: #fff;\n}\n.cfi-vi .cfi-vi-btn--primary:hover:not(:disabled) {\n background: var(--cfi-color-primary-700, #1d4ed8);\n}\n.cfi-vi .cfi-vi-btn--outline {\n background: #fff;\n border-color: var(--cfi-color-gray-300, #d1d5db);\n color: var(--cfi-color-gray-900, #111827);\n}\n.cfi-vi .cfi-vi-btn--outline:hover:not(:disabled) {\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-vi .cfi-vi-btn--ghost {\n background: transparent;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-btn--ghost:hover:not(:disabled) {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-vi .cfi-vi-btn--destructive {\n background: var(--cfi-color-error, #dc2626);\n color: #fff;\n}\n.cfi-vi .cfi-vi-btn--destructive:hover:not(:disabled) {\n filter: brightness(0.92);\n}\n.cfi-vi .cfi-vi-icon-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--cfi-color-gray-500, #6b7280);\n border-radius: var(--cfi-radius-sm, 4px);\n display: inline-flex;\n}\n.cfi-vi .cfi-vi-icon-btn:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-vi .cfi-vi-tag {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: var(--cfi-font-size-xs, 11px);\n font-weight: 600;\n}\n.cfi-vi .cfi-vi-tag--default {\n background: var(--cfi-color-gray-100, #f3f4f6);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-tag--outline {\n background: transparent;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-tag--secondary {\n background: var(--cfi-color-gray-200, #e5e7eb);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-tag--success {\n background: #16a34a;\n color: #fff;\n}\n.cfi-vi .cfi-vi-tag--purple {\n background: var(--cfi-color-violet-600, #7c3aed);\n color: #fff;\n}\n.cfi-vi .cfi-vi-card {\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-lg, 8px);\n background: #fff;\n}\n.cfi-vi .cfi-vi-card--active {\n border-color: #86efac;\n background: #f0fdf4;\n}\n.cfi-vi .cfi-vi-card--hover:hover {\n border-color: var(--cfi-color-primary-600, #2563eb);\n}\n.cfi-vi .cfi-vi-card-head {\n padding: var(--cfi-spacing-md, 12px);\n padding-bottom: var(--cfi-spacing-sm, 8px);\n}\n.cfi-vi .cfi-vi-card-body {\n padding: 0 var(--cfi-spacing-md, 12px) var(--cfi-spacing-md, 12px);\n}\n.cfi-vi .cfi-vi-card-title {\n font-size: var(--cfi-font-size-base, 15px);\n font-weight: 600;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.cfi-vi .cfi-vi-link {\n background: none;\n border: none;\n padding: 0;\n color: inherit;\n font: inherit;\n cursor: pointer;\n text-align: left;\n}\n.cfi-vi .cfi-vi-link:hover {\n text-decoration: underline;\n}\n.cfi-vi .cfi-vi-field {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-xs, 4px);\n}\n.cfi-vi .cfi-vi-label {\n font-size: var(--cfi-font-size-sm, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-hint {\n font-size: var(--cfi-font-size-xs, 11px);\n color: var(--cfi-color-gray-500, #6b7280);\n margin: 2px 0 0;\n}\n.cfi-vi .cfi-vi-input,\n.cfi-vi select.cfi-vi-input {\n padding: 6px 8px;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n font: inherit;\n color: inherit;\n width: 100%;\n}\n.cfi-vi .cfi-vi-input:focus {\n outline: 2px solid var(--cfi-color-primary-600, #2563eb);\n outline-offset: 1px;\n}\n.cfi-vi-modal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 50;\n padding: 16px;\n}\n.cfi-vi-modal {\n background: #fff;\n border-radius: var(--cfi-radius-lg, 8px);\n width: 100%;\n max-width: 32rem;\n max-height: 90vh;\n overflow: auto;\n box-shadow: var(--cfi-shadow-lg, 0 10px 30px rgba(0,0,0,0.2));\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n color: var(--cfi-color-gray-900, #111827);\n display: flex;\n flex-direction: column;\n}\n.cfi-vi-modal-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--cfi-spacing-md, 14px);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-vi-modal-title {\n font-size: var(--cfi-font-size-lg, 16px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi-modal-body {\n padding: var(--cfi-spacing-md, 14px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 12px);\n}\n.cfi-vi-modal-foot {\n display: flex;\n justify-content: flex-end;\n gap: var(--cfi-spacing-sm, 8px);\n padding: var(--cfi-spacing-md, 14px);\n border-top: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-vi-icon-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--cfi-color-gray-500, #6b7280);\n border-radius: 4px;\n display: inline-flex;\n}\n.cfi-vi .cfi-vi-spinner {\n display: inline-block;\n border: 2px solid var(--cfi-color-gray-300, #d1d5db);\n border-top-color: var(--cfi-color-primary-600, #2563eb);\n border-radius: 50%;\n animation: cfi-vi-spin 0.8s linear infinite;\n}\n.cfi-vi .cfi-vi-list {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 10px);\n}\n.cfi-vi .cfi-vi-list-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n}\n.cfi-vi .cfi-vi-h1 {\n font-size: var(--cfi-font-size-lg, 18px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi .cfi-vi-active-line {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: var(--cfi-font-size-sm, 13px);\n color: var(--cfi-color-gray-600, #4b5563);\n margin-top: 2px;\n}\n.cfi-vi .cfi-vi-active-name {\n font-weight: 600;\n color: #15803d;\n}\n.cfi-vi .cfi-vi-active-extras {\n margin-top: var(--cfi-spacing-md, 16px);\n padding-top: var(--cfi-spacing-md, 16px);\n border-top: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 16px);\n}\n.cfi-vi-mapper {\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--cfi-spacing-lg, 24px);\n align-items: start;\n}\n@media (min-width: 1024px) {\n .cfi-vi-mapper {\n grid-template-columns: 1fr 1fr;\n }\n}\n.cfi-vi-mapper .cfi-vi-mapper-col {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-lg, 24px);\n min-width: 0;\n}\n.cfi-vi-mapper .cfi-vi-mapper-section {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-vi-mapper .cfi-vi-mapper-section-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.cfi-vi-mapper .cfi-vi-mapper-h3 {\n font-size: var(--cfi-font-size-xs, 11px);\n font-weight: 700;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--cfi-color-gray-500, #6b7280);\n margin: 0;\n}\n.cfi-vi-mapper .cfi-vi-mapper-note {\n font-size: var(--cfi-font-size-xs, 11px);\n color: var(--cfi-color-gray-500, #6b7280);\n}\n.cfi-vi-mapper .cfi-vi-mapper-box {\n border: 1px solid var(--cfi-color-gray-100, #f3f4f6);\n border-radius: var(--cfi-radius-lg, 8px);\n background: #fff;\n padding: var(--cfi-spacing-md, 14px);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.cfi-vi-mapper .cfi-vi-mapper-err {\n font-size: var(--cfi-font-size-xs, 11px);\n color: var(--cfi-color-error, #b91c1c);\n margin: 2px 0 0;\n}\n.cfi-vi-mapper .cfi-vi-mapper-save {\n display: flex;\n align-items: center;\n gap: 12px;\n padding-top: 4px;\n}\n.cfi-vi-mapper .cfi-vi-mapper-dirty {\n font-size: var(--cfi-font-size-xs, 11px);\n color: #b45309;\n}\n.cfi-vi-mapper .cfi-vi-hier-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.cfi-vi-mapper .cfi-vi-hier-elbow {\n width: 18px;\n align-self: stretch;\n border-left: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n height: 18px;\n margin-bottom: 9px;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-hier-dot {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n border: 2px solid var(--cfi-color-gray-300, #d1d5db);\n background: #fff;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-hier-dot--filled {\n border-color: var(--cfi-color-primary-600, #6366f1);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-hier-name {\n font-size: var(--cfi-font-size-xs, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-600, #4b5563);\n width: 96px;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-leaf {\n font-size: 10px;\n font-weight: 600;\n color: #b45309;\n background: #fffbeb;\n border: 1px solid #fde68a;\n padding: 1px 6px;\n border-radius: 999px;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-dropzone {\n flex: 1;\n min-height: 36px;\n border-radius: var(--cfi-radius-md, 6px);\n border: 2px solid;\n display: flex;\n align-items: center;\n padding: 0 10px;\n min-width: 0;\n transition: all 0.12s;\n}\n.cfi-vi-mapper .cfi-vi-dropzone--active {\n border-color: var(--cfi-color-primary-600, #6366f1);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-dropzone--filled {\n border-style: solid;\n border-color: var(--cfi-color-gray-200, #e5e7eb);\n background: #fff;\n}\n.cfi-vi-mapper .cfi-vi-dropzone--empty {\n border-style: dashed;\n border-color: var(--cfi-color-gray-200, #e5e7eb);\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-vi-mapper .cfi-vi-dropzone--covered {\n border-style: solid;\n border-color: #a7f3d0;\n background: #ecfdf5;\n}\n.cfi-vi-mapper .cfi-vi-dropzone-ph {\n font-size: var(--cfi-font-size-xs, 12px);\n color: var(--cfi-color-gray-400, #9ca3af);\n user-select: none;\n}\n.cfi-vi-mapper .cfi-vi-base-hint {\n font-size: 10px;\n font-family: ui-monospace, monospace;\n color: #1d4ed8;\n background: #eff6ff;\n border: 1px solid #bfdbfe;\n padding: 1px 6px;\n border-radius: 4px;\n flex-shrink: 0;\n white-space: nowrap;\n}\n.cfi-vi-mapper .cfi-vi-auto-row {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n.cfi-vi-mapper .cfi-vi-auto-label {\n width: 160px;\n flex-shrink: 0;\n padding-top: 6px;\n}\n.cfi-vi-mapper .cfi-vi-auto-label-text {\n font-size: var(--cfi-font-size-xs, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi-mapper .cfi-vi-auto-cover {\n font-size: 10px;\n margin: 2px 0 0;\n color: var(--cfi-color-gray-500, #6b7280);\n}\n.cfi-vi-mapper .cfi-vi-auto-cover--on {\n color: #047857;\n}\n.cfi-vi-mapper .cfi-vi-auto-auto {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-width: 0;\n color: #047857;\n}\n.cfi-vi-mapper .cfi-vi-auto-auto-label {\n font-size: var(--cfi-font-size-xs, 12px);\n font-family: ui-monospace, monospace;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cfi-vi-mapper .cfi-vi-auto-auto-tag {\n font-size: 10px;\n font-style: italic;\n color: #059669;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-auto-transform {\n width: 224px;\n flex-shrink: 0;\n padding-top: 6px;\n font-size: 11px;\n color: var(--cfi-color-gray-500, #6b7280);\n line-height: 1.3;\n}\n.cfi-vi-mapper .cfi-vi-auto-transform-label {\n font-weight: 600;\n}\n.cfi-vi-mapper .cfi-vi-pool-col {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 8px);\n}\n@media (min-width: 1024px) {\n .cfi-vi-mapper .cfi-vi-pool-col {\n position: sticky;\n top: 16px;\n align-self: start;\n }\n}\n.cfi-vi-mapper .cfi-vi-search {\n position: relative;\n}\n.cfi-vi-mapper .cfi-vi-search .cfi-vi-input {\n padding-left: 28px;\n}\n.cfi-vi-mapper .cfi-vi-search-icon {\n position: absolute;\n left: 8px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--cfi-color-gray-400, #9ca3af);\n pointer-events: none;\n}\n.cfi-vi-mapper .cfi-vi-pool {\n border: 2px dashed var(--cfi-color-gray-200, #e5e7eb);\n background: var(--cfi-color-gray-50, #f9fafb);\n border-radius: var(--cfi-radius-lg, 8px);\n padding: 12px;\n min-height: 200px;\n max-height: 600px;\n overflow-y: auto;\n}\n.cfi-vi-mapper .cfi-vi-pool--active {\n border-color: var(--cfi-color-primary-600, #818cf8);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-pool-empty {\n font-size: var(--cfi-font-size-xs, 12px);\n color: var(--cfi-color-gray-400, #9ca3af);\n font-style: italic;\n}\n.cfi-vi-mapper .cfi-vi-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n.cfi-vi-mapper .cfi-vi-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n cursor: grab;\n user-select: none;\n font-size: var(--cfi-font-size-xs, 12px);\n font-family: ui-monospace, monospace;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi-mapper .cfi-vi-chip:hover {\n border-color: var(--cfi-color-primary-600, #818cf8);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-chip:active {\n cursor: grabbing;\n}\n.cfi-vi-mapper .cfi-vi-chip--assigned {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-width: 0;\n cursor: grab;\n}\n.cfi-vi-mapper .cfi-vi-chip--assigned:active {\n cursor: grabbing;\n}\n.cfi-vi-mapper .cfi-vi-chip-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--cfi-color-gray-400, #9ca3af);\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-chip-dot--on {\n background: var(--cfi-color-primary-600, #6366f1);\n}\n.cfi-vi-mapper .cfi-vi-chip-label {\n font-size: var(--cfi-font-size-xs, 12px);\n font-family: ui-monospace, monospace;\n color: #3730a3;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cfi-vi-mapper .cfi-vi-chip-x {\n margin-left: 4px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n color: var(--cfi-color-gray-400, #9ca3af);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-chip-x:hover {\n background: #fee2e2;\n color: #ef4444;\n}\n.cfi-vi .cfi-vi-spin {\n animation: cfi-vi-spin 0.8s linear infinite;\n}\n.cfi-vi .cfi-vi-detail {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-lg, 20px);\n}\n.cfi-vi .cfi-vi-h2 {\n font-size: var(--cfi-font-size-base, 15px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi .cfi-vi-panel {\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n padding: var(--cfi-spacing-md, 16px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 12px);\n}\n.cfi-vi .cfi-vi-grid2 {\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--cfi-spacing-md, 16px);\n}\n@media (min-width: 768px) {\n .cfi-vi .cfi-vi-grid2 {\n grid-template-columns: 1fr 1fr;\n }\n}\n.cfi-vi .cfi-vi-lock {\n display: flex;\n align-items: center;\n gap: 8px;\n border: 1px solid #fde68a;\n background: #fffbeb;\n color: #92400e;\n border-radius: var(--cfi-radius-md, 6px);\n padding: 8px 12px;\n font-size: var(--cfi-font-size-xs, 12px);\n}\n.cfi-vi .cfi-vi-table {\n width: 100%;\n border-collapse: collapse;\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 6px);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-vi .cfi-vi-table th,\n.cfi-vi .cfi-vi-table td {\n text-align: left;\n padding: 6px 10px;\n border-bottom: 1px solid var(--cfi-color-gray-100, #f3f4f6);\n vertical-align: middle;\n}\n.cfi-vi .cfi-vi-table th {\n font-size: var(--cfi-font-size-xs, 11px);\n font-weight: 600;\n color: var(--cfi-color-gray-500, #6b7280);\n}\n.cfi-vi .cfi-vi-table tr:last-child td {\n border-bottom: none;\n}\n.cfi-vi .cfi-vi-danger {\n border: 1px solid rgba(220, 38, 38, 0.4);\n background: rgba(220, 38, 38, 0.05);\n border-radius: var(--cfi-radius-md, 6px);\n padding: var(--cfi-spacing-md, 16px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 12px);\n}\n.cfi-vi .cfi-vi-aliases {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 12px);\n}\n.cfi-vi .cfi-vi-alias-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.cfi-vi .cfi-vi-tabs {\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-vi .cfi-vi-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 14px;\n margin-bottom: -1px;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n font: inherit;\n font-size: var(--cfi-font-size-sm, 13px);\n cursor: pointer;\n color: var(--cfi-color-gray-600, #4b5563);\n}\n.cfi-vi .cfi-vi-tab:hover {\n color: var(--cfi-color-gray-900, #111827);\n}\n.cfi-vi .cfi-vi-tab--active {\n color: var(--cfi-color-primary-600, #2563eb);\n border-bottom-color: var(--cfi-color-primary-600, #2563eb);\n font-weight: 600;\n}\n.cfi-vi .cfi-vi-tab--danger:hover {\n color: #dc2626;\n}\n.cfi-vi .cfi-vi-tab--danger.cfi-vi-tab--active {\n color: #dc2626;\n border-bottom-color: #dc2626;\n}\n.cfi-vi .cfi-vi-tabpanel {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-lg, 20px);\n}\n.cfi-vi .cfi-vi-tag--warn {\n background: #fffbeb;\n color: #92400e;\n border: 1px solid #fde68a;\n}\n.cfi-vi .cfi-vi-tag--danger {\n background: #fef2f2;\n color: #b91c1c;\n border: 1px solid #fecaca;\n}\n.cfi-vi .cfi-vi-select {\n padding: 6px 8px;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n font: inherit;\n}\n.cfi-vi .cfi-vi-select:focus {\n outline: 2px solid var(--cfi-color-primary-600, #2563eb);\n outline-offset: -1px;\n}\n.cfi-vi .cfi-vi-file-input {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n border: 0;\n}\n.cfi-vi .cfi-vi-filepick {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 8px);\n min-width: 0;\n}\n.cfi-vi .cfi-vi-filepick-name {\n font-size: var(--cfi-font-size-sm, 13px);\n color: var(--cfi-color-gray-600, #4b5563);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n.cfi-vi .cfi-vi-alert {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: var(--cfi-radius-md, 6px);\n font-size: var(--cfi-font-size-sm, 13px);\n margin: 8px 0;\n}\n.cfi-vi .cfi-vi-alert--error {\n background: #fef2f2;\n color: #b91c1c;\n}\n.cfi-vi .cfi-vi-alert--warn {\n background: #fffbeb;\n color: #92400e;\n}\n.cfi-vi .cfi-vi-alert ul.cfi-vi-blockers {\n margin: 0;\n padding-left: 18px;\n}\n.cfi-vi .cfi-vi-loading {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--cfi-color-gray-500, #6b7280);\n padding: 12px 0;\n}\n.cfi-vi .cfi-vi-files-head {\n align-items: center;\n margin-bottom: 12px;\n}\n.cfi-vi .cfi-vi-files-title {\n font-size: var(--cfi-font-size-lg, 18px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi .cfi-vi-files-filters {\n gap: 8px;\n align-items: center;\n margin-bottom: 12px;\n}\n.cfi-vi .cfi-vi-files-table th,\n.cfi-vi .cfi-vi-files-table td {\n white-space: nowrap;\n}\n.cfi-vi .cfi-vi-files-file {\n font-family: var(--cfi-font-mono, ui-monospace, monospace);\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.cfi-vi .cfi-vi-files-actions {\n display: flex;\n gap: 4px;\n justify-content: flex-end;\n}\n.cfi-vi .cfi-vi-files-detail td {\n background: var(--cfi-color-gray-50, #f9fafb);\n white-space: normal;\n}\n.cfi-vi .cfi-vi-files-pager {\n align-items: center;\n gap: 10px;\n margin-top: 12px;\n}\n.cfi-vi .cfi-vi-ta-right {\n text-align: right;\n}\n.cfi-vi .cfi-vi-danger-text {\n color: #b91c1c;\n}\n");
28
+ styleInject(".cfi-volume-import {\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n font-size: var(--cfi-font-size-base, 14px);\n color: var(--cfi-color-gray-900, #111827);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 12px);\n max-width: 640px;\n}\n.cfi-volume-import .cfi-vi-field {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-xs, 4px);\n}\n.cfi-volume-import .cfi-vi-row {\n display: flex;\n gap: var(--cfi-spacing-md, 12px);\n flex-wrap: wrap;\n}\n.cfi-volume-import .cfi-vi-row .cfi-vi-field {\n flex: 1 1 8rem;\n}\n.cfi-volume-import .cfi-vi-label {\n font-size: var(--cfi-font-size-sm, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-volume-import .cfi-vi-select,\n.cfi-volume-import .cfi-vi-file {\n padding: 6px 8px;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n font: inherit;\n color: inherit;\n}\n.cfi-volume-import .cfi-vi-select:focus,\n.cfi-volume-import .cfi-vi-file:focus {\n outline: 2px solid var(--cfi-color-primary-600, #2563eb);\n outline-offset: 1px;\n}\n.cfi-volume-import .cfi-vi-button {\n display: inline-flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 6px);\n align-self: flex-start;\n padding: 8px 14px;\n border: none;\n border-radius: var(--cfi-radius-md, 6px);\n background: var(--cfi-color-primary-600, #2563eb);\n color: #fff;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n.cfi-volume-import .cfi-vi-button:hover:not(:disabled) {\n background: var(--cfi-color-primary-700, #1d4ed8);\n}\n.cfi-volume-import .cfi-vi-button:disabled {\n background: var(--cfi-color-gray-300, #d1d5db);\n color: var(--cfi-color-gray-500, #6b7280);\n cursor: not-allowed;\n}\n.cfi-volume-import .cfi-vi-loading,\n.cfi-volume-import .cfi-vi-alert {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 6px);\n padding: 8px 10px;\n border-radius: var(--cfi-radius-md, 6px);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-volume-import .cfi-vi-alert--error {\n background: var(--cfi-color-gray-100, #fef2f2);\n color: var(--cfi-color-error, #b91c1c);\n}\n.cfi-volume-import .cfi-vi-alert--warn {\n background: #fffbeb;\n color: #92400e;\n align-items: flex-start;\n}\n.cfi-volume-import .cfi-vi-blockers {\n margin: 0;\n padding-left: 1.1rem;\n}\n.cfi-volume-import .cfi-vi-status {\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 6px);\n padding: var(--cfi-spacing-md, 12px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-volume-import .cfi-vi-status-head {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-volume-import .cfi-vi-filename {\n font-weight: 600;\n}\n.cfi-volume-import .cfi-vi-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n border-radius: 999px;\n font-size: var(--cfi-font-size-xs, 11px);\n text-transform: capitalize;\n background: var(--cfi-color-gray-100, #f3f4f6);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-volume-import .cfi-vi-badge--done {\n background: #dcfce7;\n color: #166534;\n}\n.cfi-volume-import .cfi-vi-badge--done_with_errors {\n background: #fef9c3;\n color: #854d0e;\n}\n.cfi-volume-import .cfi-vi-badge--failed {\n background: #fee2e2;\n color: #991b1b;\n}\n.cfi-volume-import .cfi-vi-badge--processing,\n.cfi-volume-import .cfi-vi-badge--pending,\n.cfi-volume-import .cfi-vi-badge--awaiting_calendar {\n background: #dbeafe;\n color: #1e40af;\n}\n.cfi-volume-import .cfi-vi-link {\n background: none;\n border: none;\n padding: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--cfi-color-primary-600, #2563eb);\n font: inherit;\n cursor: pointer;\n}\n.cfi-volume-import .cfi-vi-stats {\n display: flex;\n gap: var(--cfi-spacing-md, 12px);\n color: var(--cfi-color-gray-600, #4b5563);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-volume-import .cfi-vi-errors {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-volume-import .cfi-vi-errors th,\n.cfi-volume-import .cfi-vi-errors td {\n text-align: left;\n padding: 4px 8px;\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n vertical-align: top;\n}\n.cfi-volume-import .cfi-vi-spin {\n animation: cfi-vi-spin 0.8s linear infinite;\n}\n@keyframes cfi-vi-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.cfi-vi {\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n font-size: var(--cfi-font-size-base, 14px);\n color: var(--cfi-color-gray-900, #111827);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 12px);\n}\n.cfi-vi *,\n.cfi-vi *::before,\n.cfi-vi *::after {\n box-sizing: border-box;\n}\n.cfi-vi .cfi-vi-row {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-vi .cfi-vi-row--between {\n justify-content: space-between;\n}\n.cfi-vi .cfi-vi-spacer {\n flex: 1;\n}\n.cfi-vi .cfi-vi-muted {\n color: var(--cfi-color-gray-600, #4b5563);\n}\n.cfi-vi .cfi-vi-error {\n color: var(--cfi-color-error, #b91c1c);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-vi .cfi-vi-mono {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n monospace;\n font-size: var(--cfi-font-size-xs, 11px);\n}\n.cfi-vi .cfi-vi-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n border-radius: var(--cfi-radius-md, 6px);\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n border: 1px solid transparent;\n white-space: nowrap;\n}\n.cfi-vi .cfi-vi-btn--sm {\n padding: 4px 10px;\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-vi .cfi-vi-btn--md {\n padding: 8px 14px;\n}\n.cfi-vi .cfi-vi-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.cfi-vi .cfi-vi-btn--primary {\n background: var(--cfi-color-primary-600, #2563eb);\n color: #fff;\n}\n.cfi-vi .cfi-vi-btn--primary:hover:not(:disabled) {\n background: var(--cfi-color-primary-700, #1d4ed8);\n}\n.cfi-vi .cfi-vi-btn--outline {\n background: #fff;\n border-color: var(--cfi-color-gray-300, #d1d5db);\n color: var(--cfi-color-gray-900, #111827);\n}\n.cfi-vi .cfi-vi-btn--outline:hover:not(:disabled) {\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-vi .cfi-vi-btn--ghost {\n background: transparent;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-btn--ghost:hover:not(:disabled) {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-vi .cfi-vi-btn--destructive {\n background: var(--cfi-color-error, #dc2626);\n color: #fff;\n}\n.cfi-vi .cfi-vi-btn--destructive:hover:not(:disabled) {\n filter: brightness(0.92);\n}\n.cfi-vi .cfi-vi-icon-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--cfi-color-gray-500, #6b7280);\n border-radius: var(--cfi-radius-sm, 4px);\n display: inline-flex;\n}\n.cfi-vi .cfi-vi-icon-btn:hover {\n background: var(--cfi-color-gray-100, #f3f4f6);\n}\n.cfi-vi .cfi-vi-tag {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 1px 8px;\n border-radius: 999px;\n font-size: var(--cfi-font-size-xs, 11px);\n font-weight: 600;\n}\n.cfi-vi .cfi-vi-tag--default {\n background: var(--cfi-color-gray-100, #f3f4f6);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-tag--outline {\n background: transparent;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-tag--secondary {\n background: var(--cfi-color-gray-200, #e5e7eb);\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-tag--success {\n background: #16a34a;\n color: #fff;\n}\n.cfi-vi .cfi-vi-tag--purple {\n background: var(--cfi-color-violet-600, #7c3aed);\n color: #fff;\n}\n.cfi-vi .cfi-vi-card {\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-lg, 8px);\n background: #fff;\n}\n.cfi-vi .cfi-vi-card--active {\n border-color: #86efac;\n background: #f0fdf4;\n}\n.cfi-vi .cfi-vi-card--hover:hover {\n border-color: var(--cfi-color-primary-600, #2563eb);\n}\n.cfi-vi .cfi-vi-card-head {\n padding: var(--cfi-spacing-md, 12px);\n padding-bottom: var(--cfi-spacing-sm, 8px);\n}\n.cfi-vi .cfi-vi-card-body {\n padding: 0 var(--cfi-spacing-md, 12px) var(--cfi-spacing-md, 12px);\n}\n.cfi-vi .cfi-vi-card-title {\n font-size: var(--cfi-font-size-base, 15px);\n font-weight: 600;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.cfi-vi .cfi-vi-link {\n background: none;\n border: none;\n padding: 0;\n color: inherit;\n font: inherit;\n cursor: pointer;\n text-align: left;\n}\n.cfi-vi .cfi-vi-link:hover {\n text-decoration: underline;\n}\n.cfi-vi .cfi-vi-field {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-xs, 4px);\n}\n.cfi-vi .cfi-vi-label {\n font-size: var(--cfi-font-size-sm, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi .cfi-vi-hint {\n font-size: var(--cfi-font-size-xs, 11px);\n color: var(--cfi-color-gray-500, #6b7280);\n margin: 2px 0 0;\n}\n.cfi-vi .cfi-vi-input,\n.cfi-vi select.cfi-vi-input {\n padding: 6px 8px;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n font: inherit;\n color: inherit;\n width: 100%;\n}\n.cfi-vi .cfi-vi-input:focus {\n outline: 2px solid var(--cfi-color-primary-600, #2563eb);\n outline-offset: 1px;\n}\n.cfi-vi-modal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 50;\n padding: 16px;\n}\n.cfi-vi-modal {\n background: #fff;\n border-radius: var(--cfi-radius-lg, 8px);\n width: 100%;\n max-width: 32rem;\n max-height: 90vh;\n overflow: auto;\n box-shadow: var(--cfi-shadow-lg, 0 10px 30px rgba(0,0,0,0.2));\n font-family: var(--cfi-font-family, system-ui, sans-serif);\n color: var(--cfi-color-gray-900, #111827);\n display: flex;\n flex-direction: column;\n}\n.cfi-vi-modal-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--cfi-spacing-md, 14px);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-vi-modal-title {\n font-size: var(--cfi-font-size-lg, 16px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi-modal-body {\n padding: var(--cfi-spacing-md, 14px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 12px);\n}\n.cfi-vi-modal-foot {\n display: flex;\n justify-content: flex-end;\n gap: var(--cfi-spacing-sm, 8px);\n padding: var(--cfi-spacing-md, 14px);\n border-top: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-vi-icon-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--cfi-color-gray-500, #6b7280);\n border-radius: 4px;\n display: inline-flex;\n}\n.cfi-vi .cfi-vi-spinner {\n display: inline-block;\n border: 2px solid var(--cfi-color-gray-300, #d1d5db);\n border-top-color: var(--cfi-color-primary-600, #2563eb);\n border-radius: 50%;\n animation: cfi-vi-spin 0.8s linear infinite;\n}\n.cfi-vi .cfi-vi-list {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 10px);\n}\n.cfi-vi .cfi-vi-list-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n}\n.cfi-vi .cfi-vi-h1 {\n font-size: var(--cfi-font-size-lg, 18px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi .cfi-vi-active-line {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: var(--cfi-font-size-sm, 13px);\n color: var(--cfi-color-gray-600, #4b5563);\n margin-top: 2px;\n}\n.cfi-vi .cfi-vi-active-name {\n font-weight: 600;\n color: #15803d;\n}\n.cfi-vi .cfi-vi-active-extras {\n margin-top: var(--cfi-spacing-md, 16px);\n padding-top: var(--cfi-spacing-md, 16px);\n border-top: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 16px);\n}\n.cfi-vi-mapper {\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--cfi-spacing-lg, 24px);\n align-items: start;\n}\n@media (min-width: 1024px) {\n .cfi-vi-mapper {\n grid-template-columns: 1fr 1fr;\n }\n}\n.cfi-vi-mapper .cfi-vi-mapper-col {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-lg, 24px);\n min-width: 0;\n}\n.cfi-vi-mapper .cfi-vi-mapper-section {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 8px);\n}\n.cfi-vi-mapper .cfi-vi-mapper-section-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.cfi-vi-mapper .cfi-vi-mapper-h3 {\n font-size: var(--cfi-font-size-xs, 11px);\n font-weight: 700;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n color: var(--cfi-color-gray-500, #6b7280);\n margin: 0;\n}\n.cfi-vi-mapper .cfi-vi-mapper-note {\n font-size: var(--cfi-font-size-xs, 11px);\n color: var(--cfi-color-gray-500, #6b7280);\n}\n.cfi-vi-mapper .cfi-vi-mapper-box {\n border: 1px solid var(--cfi-color-gray-100, #f3f4f6);\n border-radius: var(--cfi-radius-lg, 8px);\n background: #fff;\n padding: var(--cfi-spacing-md, 14px);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.cfi-vi-mapper .cfi-vi-mapper-err {\n font-size: var(--cfi-font-size-xs, 11px);\n color: var(--cfi-color-error, #b91c1c);\n margin: 2px 0 0;\n}\n.cfi-vi-mapper .cfi-vi-mapper-save {\n display: flex;\n align-items: center;\n gap: 12px;\n padding-top: 4px;\n}\n.cfi-vi-mapper .cfi-vi-mapper-dirty {\n font-size: var(--cfi-font-size-xs, 11px);\n color: #b45309;\n}\n.cfi-vi-mapper .cfi-vi-hier-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.cfi-vi-mapper .cfi-vi-hier-elbow {\n width: 18px;\n align-self: stretch;\n border-left: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n height: 18px;\n margin-bottom: 9px;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-hier-dot {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n border: 2px solid var(--cfi-color-gray-300, #d1d5db);\n background: #fff;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-hier-dot--filled {\n border-color: var(--cfi-color-primary-600, #6366f1);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-hier-name {\n font-size: var(--cfi-font-size-xs, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-600, #4b5563);\n width: 96px;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-leaf {\n font-size: 10px;\n font-weight: 600;\n color: #b45309;\n background: #fffbeb;\n border: 1px solid #fde68a;\n padding: 1px 6px;\n border-radius: 999px;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-dropzone {\n flex: 1;\n min-height: 36px;\n border-radius: var(--cfi-radius-md, 6px);\n border: 2px solid;\n display: flex;\n align-items: center;\n padding: 0 10px;\n min-width: 0;\n transition: all 0.12s;\n}\n.cfi-vi-mapper .cfi-vi-dropzone--active {\n border-color: var(--cfi-color-primary-600, #6366f1);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-dropzone--filled {\n border-style: solid;\n border-color: var(--cfi-color-gray-200, #e5e7eb);\n background: #fff;\n}\n.cfi-vi-mapper .cfi-vi-dropzone--empty {\n border-style: dashed;\n border-color: var(--cfi-color-gray-200, #e5e7eb);\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-vi-mapper .cfi-vi-dropzone--covered {\n border-style: solid;\n border-color: #a7f3d0;\n background: #ecfdf5;\n}\n.cfi-vi-mapper .cfi-vi-dropzone-ph {\n font-size: var(--cfi-font-size-xs, 12px);\n color: var(--cfi-color-gray-400, #9ca3af);\n user-select: none;\n}\n.cfi-vi-mapper .cfi-vi-base-hint {\n font-size: 10px;\n font-family: ui-monospace, monospace;\n color: #1d4ed8;\n background: #eff6ff;\n border: 1px solid #bfdbfe;\n padding: 1px 6px;\n border-radius: 4px;\n flex-shrink: 0;\n white-space: nowrap;\n}\n.cfi-vi-mapper .cfi-vi-auto-row {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n.cfi-vi-mapper .cfi-vi-auto-label {\n width: 160px;\n flex-shrink: 0;\n padding-top: 6px;\n}\n.cfi-vi-mapper .cfi-vi-auto-label-text {\n font-size: var(--cfi-font-size-xs, 12px);\n font-weight: 600;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi-mapper .cfi-vi-auto-cover {\n font-size: 10px;\n margin: 2px 0 0;\n color: var(--cfi-color-gray-500, #6b7280);\n}\n.cfi-vi-mapper .cfi-vi-auto-cover--on {\n color: #047857;\n}\n.cfi-vi-mapper .cfi-vi-auto-auto {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-width: 0;\n color: #047857;\n}\n.cfi-vi-mapper .cfi-vi-auto-auto-label {\n font-size: var(--cfi-font-size-xs, 12px);\n font-family: ui-monospace, monospace;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cfi-vi-mapper .cfi-vi-auto-auto-tag {\n font-size: 10px;\n font-style: italic;\n color: #059669;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-auto-transform {\n width: 224px;\n flex-shrink: 0;\n padding-top: 6px;\n font-size: 11px;\n color: var(--cfi-color-gray-500, #6b7280);\n line-height: 1.3;\n}\n.cfi-vi-mapper .cfi-vi-auto-transform-label {\n font-weight: 600;\n}\n.cfi-vi-mapper .cfi-vi-pool-col {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 8px);\n}\n@media (min-width: 1024px) {\n .cfi-vi-mapper .cfi-vi-pool-col {\n position: sticky;\n top: 16px;\n align-self: start;\n }\n}\n.cfi-vi-mapper .cfi-vi-search {\n position: relative;\n}\n.cfi-vi-mapper .cfi-vi-search .cfi-vi-input {\n padding-left: 28px;\n}\n.cfi-vi-mapper .cfi-vi-search-icon {\n position: absolute;\n left: 8px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--cfi-color-gray-400, #9ca3af);\n pointer-events: none;\n}\n.cfi-vi-mapper .cfi-vi-pool {\n border: 2px dashed var(--cfi-color-gray-200, #e5e7eb);\n background: var(--cfi-color-gray-50, #f9fafb);\n border-radius: var(--cfi-radius-lg, 8px);\n padding: 12px;\n min-height: 200px;\n max-height: 600px;\n overflow-y: auto;\n}\n.cfi-vi-mapper .cfi-vi-pool--active {\n border-color: var(--cfi-color-primary-600, #818cf8);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-pool-empty {\n font-size: var(--cfi-font-size-xs, 12px);\n color: var(--cfi-color-gray-400, #9ca3af);\n font-style: italic;\n}\n.cfi-vi-mapper .cfi-vi-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n.cfi-vi-mapper .cfi-vi-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n cursor: grab;\n user-select: none;\n font-size: var(--cfi-font-size-xs, 12px);\n font-family: ui-monospace, monospace;\n color: var(--cfi-color-gray-700, #374151);\n}\n.cfi-vi-mapper .cfi-vi-chip:hover {\n border-color: var(--cfi-color-primary-600, #818cf8);\n background: var(--cfi-color-primary-50, #eef2ff);\n}\n.cfi-vi-mapper .cfi-vi-chip:active {\n cursor: grabbing;\n}\n.cfi-vi-mapper .cfi-vi-chip--assigned {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-width: 0;\n cursor: grab;\n}\n.cfi-vi-mapper .cfi-vi-chip--assigned:active {\n cursor: grabbing;\n}\n.cfi-vi-mapper .cfi-vi-chip-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--cfi-color-gray-400, #9ca3af);\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-chip-dot--on {\n background: var(--cfi-color-primary-600, #6366f1);\n}\n.cfi-vi-mapper .cfi-vi-chip-label {\n font-size: var(--cfi-font-size-xs, 12px);\n font-family: ui-monospace, monospace;\n color: #3730a3;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cfi-vi-mapper .cfi-vi-chip-x {\n margin-left: 4px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n border: none;\n background: none;\n cursor: pointer;\n color: var(--cfi-color-gray-400, #9ca3af);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.cfi-vi-mapper .cfi-vi-chip-x:hover {\n background: #fee2e2;\n color: #ef4444;\n}\n.cfi-vi .cfi-vi-spin {\n animation: cfi-vi-spin 0.8s linear infinite;\n}\n.cfi-vi .cfi-vi-detail {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-lg, 20px);\n}\n.cfi-vi .cfi-vi-h2 {\n font-size: var(--cfi-font-size-base, 15px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi .cfi-vi-panel {\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n padding: var(--cfi-spacing-md, 16px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 12px);\n}\n.cfi-vi .cfi-vi-grid2 {\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--cfi-spacing-md, 16px);\n}\n@media (min-width: 768px) {\n .cfi-vi .cfi-vi-grid2 {\n grid-template-columns: 1fr 1fr;\n }\n}\n.cfi-vi .cfi-vi-lock {\n display: flex;\n align-items: center;\n gap: 8px;\n border: 1px solid #fde68a;\n background: #fffbeb;\n color: #92400e;\n border-radius: var(--cfi-radius-md, 6px);\n padding: 8px 12px;\n font-size: var(--cfi-font-size-xs, 12px);\n}\n.cfi-vi .cfi-vi-table {\n width: 100%;\n border-collapse: collapse;\n border: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n border-radius: var(--cfi-radius-md, 6px);\n font-size: var(--cfi-font-size-sm, 13px);\n}\n.cfi-vi .cfi-vi-table th,\n.cfi-vi .cfi-vi-table td {\n text-align: left;\n padding: 6px 10px;\n border-bottom: 1px solid var(--cfi-color-gray-100, #f3f4f6);\n vertical-align: middle;\n}\n.cfi-vi .cfi-vi-table th {\n font-size: var(--cfi-font-size-xs, 11px);\n font-weight: 600;\n color: var(--cfi-color-gray-500, #6b7280);\n}\n.cfi-vi .cfi-vi-table tr:last-child td {\n border-bottom: none;\n}\n.cfi-vi .cfi-vi-danger {\n border: 1px solid rgba(220, 38, 38, 0.4);\n background: rgba(220, 38, 38, 0.05);\n border-radius: var(--cfi-radius-md, 6px);\n padding: var(--cfi-spacing-md, 16px);\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 12px);\n}\n.cfi-vi .cfi-vi-aliases {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 12px);\n}\n.cfi-vi .cfi-vi-alias-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.cfi-vi .cfi-vi-tabs {\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n border-bottom: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n}\n.cfi-vi .cfi-vi-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 14px;\n margin-bottom: -1px;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n font: inherit;\n font-size: var(--cfi-font-size-sm, 13px);\n cursor: pointer;\n color: var(--cfi-color-gray-600, #4b5563);\n}\n.cfi-vi .cfi-vi-tab:hover {\n color: var(--cfi-color-gray-900, #111827);\n}\n.cfi-vi .cfi-vi-tab--active {\n color: var(--cfi-color-primary-600, #2563eb);\n border-bottom-color: var(--cfi-color-primary-600, #2563eb);\n font-weight: 600;\n}\n.cfi-vi .cfi-vi-tab--danger:hover {\n color: #dc2626;\n}\n.cfi-vi .cfi-vi-tab--danger.cfi-vi-tab--active {\n color: #dc2626;\n border-bottom-color: #dc2626;\n}\n.cfi-vi .cfi-vi-tabpanel {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-lg, 20px);\n}\n.cfi-vi .cfi-vi-tag--warn {\n background: #fffbeb;\n color: #92400e;\n border: 1px solid #fde68a;\n}\n.cfi-vi .cfi-vi-tag--danger {\n background: #fef2f2;\n color: #b91c1c;\n border: 1px solid #fecaca;\n}\n.cfi-vi .cfi-vi-select {\n padding: 6px 8px;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n border-radius: var(--cfi-radius-md, 6px);\n background: #fff;\n font: inherit;\n}\n.cfi-vi .cfi-vi-select:focus {\n outline: 2px solid var(--cfi-color-primary-600, #2563eb);\n outline-offset: -1px;\n}\n.cfi-vi .cfi-vi-file-input {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n border: 0;\n}\n.cfi-vi .cfi-vi-filepick {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 8px);\n min-width: 0;\n}\n.cfi-vi .cfi-vi-filepick-name {\n font-size: var(--cfi-font-size-sm, 13px);\n color: var(--cfi-color-gray-600, #4b5563);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n.cfi-vi .cfi-vi-alert {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-radius: var(--cfi-radius-md, 6px);\n font-size: var(--cfi-font-size-sm, 13px);\n margin: 8px 0;\n}\n.cfi-vi .cfi-vi-alert--error {\n background: #fef2f2;\n color: #b91c1c;\n}\n.cfi-vi .cfi-vi-alert--warn {\n background: #fffbeb;\n color: #92400e;\n}\n.cfi-vi .cfi-vi-alert ul.cfi-vi-blockers {\n margin: 0;\n padding-left: 18px;\n}\n.cfi-vi .cfi-vi-loading {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--cfi-color-gray-500, #6b7280);\n padding: 12px 0;\n}\n.cfi-vi .cfi-vi-files-head {\n align-items: center;\n margin-bottom: 12px;\n}\n.cfi-vi .cfi-vi-files-title {\n font-size: var(--cfi-font-size-lg, 18px);\n font-weight: 600;\n margin: 0;\n}\n.cfi-vi .cfi-vi-files-filters {\n gap: 8px;\n align-items: center;\n margin-bottom: 12px;\n}\n.cfi-vi .cfi-vi-files-table th,\n.cfi-vi .cfi-vi-files-table td {\n white-space: nowrap;\n}\n.cfi-vi .cfi-vi-files-file {\n font-family: var(--cfi-font-mono, ui-monospace, monospace);\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.cfi-vi .cfi-vi-files-actions {\n display: flex;\n gap: 4px;\n justify-content: flex-end;\n}\n.cfi-vi .cfi-vi-files-detail td {\n background: var(--cfi-color-gray-50, #f9fafb);\n white-space: normal;\n}\n.cfi-vi .cfi-vi-files-pager {\n align-items: center;\n gap: 10px;\n margin-top: 12px;\n}\n.cfi-vi .cfi-vi-ta-right {\n text-align: right;\n}\n.cfi-vi .cfi-vi-danger-text {\n color: #b91c1c;\n}\n.cfi-vi .cfi-vi-swatch {\n flex-shrink: 0;\n vertical-align: middle;\n}\n.cfi-vi .cfi-vi-tag--role {\n background: transparent;\n border: 1px solid currentColor;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n}\n.cfi-vi .cfi-vi-seg {\n display: inline-flex;\n border: 1px solid var(--cfi-color-gray-300, #d1d5db);\n border-radius: var(--cfi-radius-md, 6px);\n overflow: hidden;\n background: #fff;\n}\n.cfi-vi .cfi-vi-seg-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n font: inherit;\n font-size: 13px;\n font-weight: 500;\n background: #fff;\n color: var(--cfi-color-gray-600, #4b5563);\n border: 0;\n border-left: 1px solid var(--cfi-color-gray-200, #e5e7eb);\n cursor: pointer;\n transition: background-color 120ms, color 120ms;\n}\n.cfi-vi .cfi-vi-seg-btn:first-child {\n border-left: 0;\n}\n.cfi-vi .cfi-vi-seg-btn:hover:not(:disabled):not(.cfi-vi-seg-btn--on) {\n background: var(--cfi-color-gray-50, #f9fafb);\n}\n.cfi-vi .cfi-vi-seg-btn:disabled {\n cursor: not-allowed;\n opacity: 0.55;\n}\n.cfi-vi .cfi-vi-seg-btn:focus-visible {\n outline: 2px solid var(--cfi-color-primary-600, #2563eb);\n outline-offset: -2px;\n}\n.cfi-vi .cfi-vi-seg-btn--on .cfi-vi-swatch circle {\n stroke: currentColor;\n}\n");
29
29
 
30
30
  // src/VolumeImport.tsx
31
31
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -290,6 +290,55 @@ function Btn(props) {
290
290
  function Badge(props) {
291
291
  return /* @__PURE__ */ jsx2("span", { className: `cfi-vi-tag cfi-vi-tag--${props.variant ?? "default"}`, title: props.title, children: props.children });
292
292
  }
293
+ function RoleSwatch(props) {
294
+ return /* @__PURE__ */ jsxs2(
295
+ "svg",
296
+ {
297
+ className: "cfi-vi-swatch",
298
+ width: "22",
299
+ height: "10",
300
+ viewBox: "0 0 22 10",
301
+ role: "img",
302
+ "aria-label": props.title,
303
+ children: [
304
+ props.title ? /* @__PURE__ */ jsx2("title", { children: props.title }) : null,
305
+ /* @__PURE__ */ jsx2(
306
+ "line",
307
+ {
308
+ x1: "0",
309
+ y1: "5",
310
+ x2: "22",
311
+ y2: "5",
312
+ stroke: props.color,
313
+ strokeWidth: "2.5",
314
+ strokeDasharray: props.dash,
315
+ strokeLinecap: "round"
316
+ }
317
+ ),
318
+ /* @__PURE__ */ jsx2("circle", { cx: "11", cy: "5", r: "3.5", fill: props.color, stroke: "#fff", strokeWidth: "1.5" })
319
+ ]
320
+ }
321
+ );
322
+ }
323
+ function Segmented(props) {
324
+ return /* @__PURE__ */ jsx2("div", { className: "cfi-vi-seg", role: "group", "aria-label": props.ariaLabel, children: props.options.map((o) => {
325
+ const on = o.value === props.value;
326
+ return /* @__PURE__ */ jsx2(
327
+ "button",
328
+ {
329
+ type: "button",
330
+ "aria-pressed": on,
331
+ title: o.title,
332
+ disabled: props.disabled,
333
+ onClick: () => props.onChange(o.value),
334
+ className: `cfi-vi-seg-btn${on ? " cfi-vi-seg-btn--on" : ""}`,
335
+ style: on && o.activeColor ? { background: o.activeColor, borderColor: o.activeColor, color: "#fff" } : void 0,
336
+ children: o.label
337
+ },
338
+ o.value
339
+ );
340
+ }) });
341
+ }
293
342
  function Card(props) {
294
343
  return /* @__PURE__ */ jsx2("div", { className: props.className ? `cfi-vi-card ${props.className}` : "cfi-vi-card", children: props.children });
295
344
  }
@@ -784,8 +833,38 @@ function VolumeFiles({ transport, className }) {
784
833
  // src/VolumeSourcesList.tsx
785
834
  import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
786
835
  import { CheckCircle2, Sparkles } from "lucide-react";
836
+
837
+ // src/roleStyle.ts
838
+ var ROLE_STYLE = {
839
+ base: {
840
+ color: "#f59e0b",
841
+ label: "Base",
842
+ blurb: "The tenant's own volume. The active base source is what prediction runs on."
843
+ },
844
+ overlay: {
845
+ color: "#a855f7",
846
+ label: "Overlay",
847
+ blurb: "Customer-stated demand. Active whenever data is uploaded \u2014 no Set Active step."
848
+ },
849
+ reference: {
850
+ color: "#06b6d4",
851
+ dash: "6 3",
852
+ blurb: "Another vendor's curve, drawn beside the active base rather than summed into it. Never drives prediction.",
853
+ label: "Reference"
854
+ }
855
+ };
856
+ var ROLE_ORDER = ["base", "overlay", "reference"];
857
+
858
+ // src/VolumeSourcesList.tsx
787
859
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
788
860
  var EMPTY_FORM = { code: "", name: "", description: "", headerRowIndex: 0, volumeHeaderFormat: "MMM yyyy" };
861
+ function RoleBadge({ kind }) {
862
+ const style = ROLE_STYLE[kind];
863
+ return /* @__PURE__ */ jsxs4("span", { className: "cfi-vi-tag cfi-vi-tag--role", style: { color: style.color }, title: style.blurb, children: [
864
+ /* @__PURE__ */ jsx4(RoleSwatch, { color: style.color, dash: style.dash }),
865
+ style.label
866
+ ] });
867
+ }
789
868
  function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras, readOnly = false, className }) {
790
869
  const [sources, setSources] = useState3([]);
791
870
  const [activeSourceId, setActiveSourceId] = useState3(null);
@@ -901,7 +980,7 @@ function VolumeSourcesList({ transport, onOpenSource, renderActiveSourceExtras,
901
980
  /* @__PURE__ */ jsxs4("span", { className: "cfi-vi-card-title", children: [
902
981
  /* @__PURE__ */ jsx4("button", { type: "button", className: "cfi-vi-link", onClick: () => onOpenSource(src.id), children: src.name }),
903
982
  isActive && /* @__PURE__ */ jsx4(Badge, { variant: "success", children: "Active" }),
904
- src.kind === "overlay" && /* @__PURE__ */ jsx4(Badge, { variant: "purple", title: "Overlay sources are active whenever data is uploaded \u2014 no Set Active step.", children: "Overlay" })
983
+ /* @__PURE__ */ jsx4(RoleBadge, { kind: src.kind })
905
984
  ] }),
906
985
  /* @__PURE__ */ jsxs4("span", { className: "cfi-vi-row", children: [
907
986
  /* @__PURE__ */ jsx4(Badge, { variant: "outline", children: src.code }),
@@ -1664,6 +1743,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, readOnl
1664
1743
  const [savingLayout, setSavingLayout] = useState7(false);
1665
1744
  const [editLayout, setEditLayout] = useState7("wide");
1666
1745
  const [editValueDateFormat, setEditValueDateFormat] = useState7("yyyy-MM-dd");
1746
+ const [editKind, setEditKind] = useState7("base");
1667
1747
  const [newSegmentLabel, setNewSegmentLabel] = useState7("");
1668
1748
  const [newSegmentDataType, setNewSegmentDataType] = useState7("string");
1669
1749
  const [addingSegment, setAddingSegment] = useState7(false);
@@ -1701,6 +1781,7 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, readOnl
1701
1781
  setSource(src);
1702
1782
  setSelectedKeyId(src.uniqueKeySegmentId ?? "__none__");
1703
1783
  setEditLayout(src.layout);
1784
+ setEditKind(src.kind);
1704
1785
  setEditValueDateFormat(src.valueDateFormat);
1705
1786
  setHasData(uploads.items.some((u) => u.status === "done" || u.status === "done_with_errors"));
1706
1787
  setMappingKeys(keys);
@@ -1832,12 +1913,17 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, readOnl
1832
1913
  setSavingKey(false);
1833
1914
  }
1834
1915
  }
1916
+ const crossesMeasure = !!source && editKind !== source.kind && (editKind === "overlay" || source.kind === "overlay");
1835
1917
  async function saveLayoutAndRouting() {
1836
1918
  if (!id) return;
1837
1919
  setSavingLayout(true);
1838
1920
  setError(null);
1839
1921
  try {
1840
- await transport.updateSource(id, { layout: editLayout, valueDateFormat: editValueDateFormat });
1922
+ await transport.updateSource(id, {
1923
+ layout: editLayout,
1924
+ valueDateFormat: editValueDateFormat,
1925
+ kind: editKind
1926
+ });
1841
1927
  await load();
1842
1928
  } catch (e) {
1843
1929
  setError(e instanceof Error ? e.message : "Failed to save layout");
@@ -1969,9 +2055,46 @@ function VolumeSourceDetail({ transport, sourceId, onOpenSource, onBack, readOnl
1969
2055
  onChange: (e) => setEditValueDateFormat(e.target.value)
1970
2056
  }
1971
2057
  )
2058
+ ] }),
2059
+ /* @__PURE__ */ jsxs8("div", { className: "cfi-vi-field", style: { flex: 1 }, children: [
2060
+ /* @__PURE__ */ jsx8("label", { className: "cfi-vi-label", children: "Role" }),
2061
+ /* @__PURE__ */ jsx8(
2062
+ Segmented,
2063
+ {
2064
+ ariaLabel: "Source role",
2065
+ value: editKind,
2066
+ disabled: savingLayout || readOnly,
2067
+ onChange: setEditKind,
2068
+ options: ROLE_ORDER.map((k) => ({
2069
+ value: k,
2070
+ title: ROLE_STYLE[k].blurb,
2071
+ activeColor: ROLE_STYLE[k].color,
2072
+ label: /* @__PURE__ */ jsxs8(Fragment3, { children: [
2073
+ /* @__PURE__ */ jsx8(RoleSwatch, { color: ROLE_STYLE[k].color, dash: ROLE_STYLE[k].dash }),
2074
+ ROLE_STYLE[k].label
2075
+ ] })
2076
+ }))
2077
+ }
2078
+ ),
2079
+ /* @__PURE__ */ jsx8("p", { className: "cfi-vi-hint", children: ROLE_STYLE[editKind].blurb })
1972
2080
  ] })
1973
2081
  ] }),
1974
- /* @__PURE__ */ jsx8(Btn, { size: "sm", onClick: () => void saveLayoutAndRouting(), disabled: hasData || savingLayout || readOnly, children: savingLayout ? "Saving\u2026" : "Save layout" })
2082
+ hasData && crossesMeasure && /* @__PURE__ */ jsxs8("p", { className: "cfi-vi-hint", style: { color: "#b45309" }, children: [
2083
+ "This source has data. Changing between ",
2084
+ source.kind,
2085
+ " and ",
2086
+ editKind,
2087
+ " would reinterpret parts as vehicles or the reverse \u2014 clear its data first."
2088
+ ] }),
2089
+ /* @__PURE__ */ jsx8(
2090
+ Btn,
2091
+ {
2092
+ size: "sm",
2093
+ onClick: () => void saveLayoutAndRouting(),
2094
+ disabled: savingLayout || readOnly || hasData && crossesMeasure || hasData && editKind === source.kind,
2095
+ children: savingLayout ? "Saving\u2026" : "Save"
2096
+ }
2097
+ )
1975
2098
  ] }),
1976
2099
  /* @__PURE__ */ jsxs8("div", { className: "cfi-vi-panel", children: [
1977
2100
  /* @__PURE__ */ jsxs8("div", { className: "cfi-vi-row", children: [
@@ -2186,6 +2309,10 @@ export {
2186
2309
  AUTOMOTIVE_TRANSFORM_LABELS,
2187
2310
  DEFAULT_LEVEL_NAMES,
2188
2311
  HIERARCHY_LEVEL_COUNT,
2312
+ ROLE_ORDER,
2313
+ ROLE_STYLE,
2314
+ RoleSwatch,
2315
+ Segmented,
2189
2316
  TallLayoutMappings,
2190
2317
  UnifiedSegmentMapper,
2191
2318
  ValueAliasesCard,
package/package.json CHANGED
@@ -1,43 +1,43 @@
1
- {
2
- "name": "@campfire-interactive/volume-intelligence-ui",
3
- "version": "0.10.0",
4
- "description": "Shared Volume Intelligence UI components (volume import) for the Campfire Suite embedded by the VI webapp and by consuming apps (OMSF) so there is one import surface, not two that drift.",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "files": [
15
- "dist"
16
- ],
17
- "scripts": {
18
- "build": "tsup",
19
- "dev": "tsup --watch"
20
- },
21
- "peerDependencies": {
22
- "react": "^18.0.0 || ^19.0.0",
23
- "react-dom": "^18.0.0 || ^19.0.0"
24
- },
25
- "dependencies": {
26
- "@campfire-interactive/design-tokens": "*",
27
- "lucide-react": "^0.487.0"
28
- },
29
- "devDependencies": {
30
- "@types/react": "^18.2.0",
31
- "react": "^18.2.0",
32
- "react-dom": "^18.2.0",
33
- "tsup": "^8.0.0",
34
- "typescript": "^5.3.3"
35
- },
36
- "publishConfig": {
37
- "registry": "https://registry.npmjs.org",
38
- "access": "public"
39
- },
40
- "engines": {
41
- "node": ">=18.0.0"
42
- }
43
- }
1
+ {
2
+ "name": "@campfire-interactive/volume-intelligence-ui",
3
+ "version": "0.12.0",
4
+ "description": "Shared Volume Intelligence UI components (volume import) for the Campfire Suite \u2014 embedded by the VI webapp and by consuming apps (OMSF) so there is one import surface, not two that drift.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch"
20
+ },
21
+ "peerDependencies": {
22
+ "react": "^18.0.0 || ^19.0.0",
23
+ "react-dom": "^18.0.0 || ^19.0.0"
24
+ },
25
+ "dependencies": {
26
+ "@campfire-interactive/design-tokens": "*",
27
+ "lucide-react": "^0.487.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^18.2.0",
31
+ "react": "^18.2.0",
32
+ "react-dom": "^18.2.0",
33
+ "tsup": "^8.0.0",
34
+ "typescript": "^5.3.3"
35
+ },
36
+ "publishConfig": {
37
+ "registry": "https://registry.npmjs.org",
38
+ "access": "public"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ }
43
+ }