3261 lines
98 KiB
JavaScript
3261 lines
98 KiB
JavaScript
/* @ds-bundle: {"format":3,"namespace":"ChicoryTVDesignSystem_eb3b61","components":[{"name":"Badge","sourcePath":"components/data-display/Badge.jsx"},{"name":"Card","sourcePath":"components/data-display/Card.jsx"},{"name":"ChannelLogo","sourcePath":"components/data-display/ChannelLogo.jsx"},{"name":"ProgressBar","sourcePath":"components/data-display/ProgressBar.jsx"},{"name":"Stat","sourcePath":"components/data-display/Stat.jsx"},{"name":"StatusDot","sourcePath":"components/data-display/StatusDot.jsx"},{"name":"Tag","sourcePath":"components/data-display/Tag.jsx"},{"name":"Spinner","sourcePath":"components/feedback/Spinner.jsx"},{"name":"Toast","sourcePath":"components/feedback/Toast.jsx"},{"name":"Tooltip","sourcePath":"components/feedback/Tooltip.jsx"},{"name":"Button","sourcePath":"components/forms/Button.jsx"},{"name":"Checkbox","sourcePath":"components/forms/Checkbox.jsx"},{"name":"IconButton","sourcePath":"components/forms/IconButton.jsx"},{"name":"Input","sourcePath":"components/forms/Input.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"NavItem","sourcePath":"components/navigation/NavItem.jsx"},{"name":"NavSection","sourcePath":"components/navigation/NavItem.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"}],"sourceHashes":{"components/data-display/Badge.jsx":"a2d5da49e43f","components/data-display/Card.jsx":"b6e830c9f9f0","components/data-display/ChannelLogo.jsx":"af406aecdc6a","components/data-display/ProgressBar.jsx":"54ab9452240b","components/data-display/Stat.jsx":"695d12eae330","components/data-display/StatusDot.jsx":"a924e27b2a1c","components/data-display/Tag.jsx":"bdf96d4cdf7f","components/feedback/Spinner.jsx":"13341131fe7d","components/feedback/Toast.jsx":"f18ffea8c6ce","components/feedback/Tooltip.jsx":"f17876ac005e","components/forms/Button.jsx":"fa14c86210e0","components/forms/Checkbox.jsx":"81750526a791","components/forms/IconButton.jsx":"1ea757879b1c","components/forms/Input.jsx":"8759cd8e610f","components/forms/Select.jsx":"2a3af2c7bce8","components/forms/Switch.jsx":"1fce47dca064","components/navigation/NavItem.jsx":"8b46c82d5c25","components/navigation/Tabs.jsx":"95d81be93d0f","design_handoff_channel_builder/ChannelBuilder.jsx":"904a465a69fd"},"inlinedExternals":[],"unexposedExports":[]} */
|
||
|
||
(() => {
|
||
|
||
const __ds_ns = (window.ChicoryTVDesignSystem_eb3b61 = window.ChicoryTVDesignSystem_eb3b61 || {});
|
||
|
||
const __ds_scope = {};
|
||
|
||
(__ds_ns.__errors = __ds_ns.__errors || []);
|
||
|
||
// components/data-display/Badge.jsx
|
||
try { (() => {
|
||
const tones = {
|
||
neutral: {
|
||
fg: "var(--text-secondary)",
|
||
bg: "var(--ctv-surface-3)",
|
||
bd: "var(--border-hairline)"
|
||
},
|
||
accent: {
|
||
fg: "var(--ctv-accent)",
|
||
bg: "var(--ctv-accent-soft)",
|
||
bd: "rgba(91,124,250,0.35)"
|
||
},
|
||
ok: {
|
||
fg: "var(--status-ok)",
|
||
bg: "var(--ctv-ok-soft)",
|
||
bd: "rgba(63,185,132,0.35)"
|
||
},
|
||
warn: {
|
||
fg: "var(--status-warn)",
|
||
bg: "var(--ctv-warn-soft)",
|
||
bd: "rgba(224,168,61,0.35)"
|
||
},
|
||
error: {
|
||
fg: "var(--status-error)",
|
||
bg: "var(--ctv-error-soft)",
|
||
bd: "rgba(229,72,77,0.35)"
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Small status/label badge. Soft (tinted) or solid.
|
||
*/
|
||
function Badge({
|
||
children,
|
||
tone = "neutral",
|
||
solid = false,
|
||
dot = false,
|
||
style = {}
|
||
}) {
|
||
const t = tones[tone] || tones.neutral;
|
||
const solidBg = {
|
||
neutral: "var(--ctv-surface-3)",
|
||
accent: "var(--ctv-accent)",
|
||
ok: "var(--status-ok)",
|
||
warn: "var(--status-warn)",
|
||
error: "var(--status-error)"
|
||
};
|
||
return /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "5px",
|
||
height: "20px",
|
||
padding: dot ? "0 8px 0 6px" : "0 8px",
|
||
borderRadius: "var(--radius-xs)",
|
||
font: `var(--weight-medium) var(--text-2xs)/1 var(--font-sans)`,
|
||
letterSpacing: "0.02em",
|
||
color: solid ? tone === "warn" ? "#1A1206" : "#fff" : t.fg,
|
||
background: solid ? solidBg[tone] : t.bg,
|
||
border: solid ? "1px solid transparent" : `1px solid ${t.bd}`,
|
||
whiteSpace: "nowrap",
|
||
...style
|
||
}
|
||
}, dot && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
width: 6,
|
||
height: 6,
|
||
borderRadius: "50%",
|
||
background: solid ? "rgba(255,255,255,0.9)" : t.fg
|
||
}
|
||
}), children);
|
||
}
|
||
Object.assign(__ds_scope, { Badge });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data-display/Badge.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/data-display/Card.jsx
|
||
try { (() => {
|
||
/**
|
||
* Surface container. Optional header (title + actions) and body padding.
|
||
*/
|
||
function Card({
|
||
title,
|
||
subtitle,
|
||
actions = null,
|
||
children,
|
||
padded = true,
|
||
inset = false,
|
||
style = {}
|
||
}) {
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
background: inset ? "var(--ctv-bg-sunken)" : "var(--surface-card)",
|
||
border: "1px solid var(--border-hairline)",
|
||
borderRadius: "var(--radius-md)",
|
||
overflow: "hidden",
|
||
...style
|
||
}
|
||
}, (title || actions) && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "space-between",
|
||
gap: "12px",
|
||
padding: "13px 16px",
|
||
borderBottom: "1px solid var(--border-hairline)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
minWidth: 0
|
||
}
|
||
}, title && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-semibold) var(--text-md)/1.2 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, title), subtitle && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 3,
|
||
font: "var(--text-xs)/1.3 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, subtitle)), actions && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: "8px",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, actions)), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
padding: padded ? "16px" : 0
|
||
}
|
||
}, children));
|
||
}
|
||
Object.assign(__ds_scope, { Card });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data-display/Card.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/data-display/ChannelLogo.jsx
|
||
try { (() => {
|
||
const palette = [["#5B7CFA", "#2E3A66"], ["#3FB984", "#1E4536"], ["#E0A83D", "#4A3818"], ["#E5484D", "#4A1F21"], ["#B06CF0", "#38235A"], ["#48B0C8", "#193E47"]];
|
||
function hashIndex(str) {
|
||
let h = 0;
|
||
for (let i = 0; i < str.length; i++) h = h * 31 + str.charCodeAt(i) >>> 0;
|
||
return h % palette.length;
|
||
}
|
||
|
||
/**
|
||
* Channel logo tile. Renders an image when given, else a generated
|
||
* initials chip keyed to the channel name — mirrors ErsatzTV's logo/gen.
|
||
*/
|
||
function ChannelLogo({
|
||
name = "",
|
||
src = null,
|
||
size = 40,
|
||
radius = "var(--radius-sm)",
|
||
style = {}
|
||
}) {
|
||
const [fg, bg] = palette[hashIndex(name)];
|
||
const initials = name.split(/[\s\-|:]+/).filter(Boolean).slice(0, 2).map(w => w[0]).join("").toUpperCase() || "?";
|
||
return /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
width: size,
|
||
height: size,
|
||
flex: "0 0 auto",
|
||
overflow: "hidden",
|
||
borderRadius: radius,
|
||
background: src ? "var(--ctv-bg-sunken)" : bg,
|
||
border: "1px solid var(--border-hairline)",
|
||
...style
|
||
}
|
||
}, src ? /*#__PURE__*/React.createElement("img", {
|
||
src: src,
|
||
alt: name,
|
||
style: {
|
||
maxWidth: "100%",
|
||
maxHeight: "100%",
|
||
objectFit: "contain"
|
||
}
|
||
}) : /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: `var(--weight-semibold) ${Math.round(size * 0.34)}px/1 var(--font-mono)`,
|
||
color: fg,
|
||
letterSpacing: "0.02em"
|
||
}
|
||
}, initials));
|
||
}
|
||
Object.assign(__ds_scope, { ChannelLogo });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data-display/ChannelLogo.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/data-display/ProgressBar.jsx
|
||
try { (() => {
|
||
const toneColor = {
|
||
accent: "var(--action-primary)",
|
||
ok: "var(--status-ok)",
|
||
warn: "var(--status-warn)",
|
||
error: "var(--status-error)"
|
||
};
|
||
|
||
/**
|
||
* Thin determinate/indeterminate progress bar — library scans, transcodes.
|
||
*/
|
||
function ProgressBar({
|
||
value = null,
|
||
tone = "accent",
|
||
height = 6,
|
||
showLabel = false,
|
||
style = {}
|
||
}) {
|
||
const c = toneColor[tone] || toneColor.accent;
|
||
const indeterminate = value == null;
|
||
const pct = Math.max(0, Math.min(100, value ?? 0));
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: "10px",
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "relative",
|
||
flex: 1,
|
||
height,
|
||
borderRadius: "var(--radius-pill)",
|
||
background: "var(--ctv-surface-3)",
|
||
overflow: "hidden"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("style", null, `@keyframes ctv-indet{0%{left:-40%}100%{left:100%}}`), indeterminate ? /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "absolute",
|
||
top: 0,
|
||
bottom: 0,
|
||
width: "40%",
|
||
borderRadius: "inherit",
|
||
background: c,
|
||
animation: "ctv-indet 1.1s var(--ease-standard) infinite"
|
||
}
|
||
}) : /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "absolute",
|
||
top: 0,
|
||
left: 0,
|
||
bottom: 0,
|
||
width: `${pct}%`,
|
||
borderRadius: "inherit",
|
||
background: c,
|
||
transition: "width var(--dur-slow) var(--ease-out)"
|
||
}
|
||
})), showLabel && !indeterminate && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-mono)",
|
||
color: "var(--text-secondary)",
|
||
minWidth: 34,
|
||
textAlign: "right",
|
||
fontVariantNumeric: "tabular-nums"
|
||
}
|
||
}, Math.round(pct), "%"));
|
||
}
|
||
Object.assign(__ds_scope, { ProgressBar });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data-display/ProgressBar.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/data-display/Stat.jsx
|
||
try { (() => {
|
||
/**
|
||
* Dashboard metric. Big mono value, label, optional delta + icon.
|
||
*/
|
||
function Stat({
|
||
label,
|
||
value,
|
||
unit,
|
||
delta = null,
|
||
deltaTone = "neutral",
|
||
icon = null,
|
||
style = {}
|
||
}) {
|
||
const dc = {
|
||
up: "var(--status-ok)",
|
||
down: "var(--status-error)",
|
||
neutral: "var(--text-secondary)"
|
||
};
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: "8px",
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: "7px"
|
||
}
|
||
}, icon && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, icon), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
letterSpacing: "0.03em",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, label)), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "baseline",
|
||
gap: "6px"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-semibold) var(--text-2xl)/1 var(--font-mono)",
|
||
color: "var(--text-primary)",
|
||
letterSpacing: "-0.01em",
|
||
fontVariantNumeric: "tabular-nums"
|
||
}
|
||
}, value), unit && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--text-sm)/1 var(--font-sans)",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, unit), delta != null && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
marginLeft: 2,
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-mono)",
|
||
color: dc[deltaTone]
|
||
}
|
||
}, delta)));
|
||
}
|
||
Object.assign(__ds_scope, { Stat });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data-display/Stat.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/data-display/StatusDot.jsx
|
||
try { (() => {
|
||
const colors = {
|
||
live: "var(--status-live)",
|
||
ok: "var(--status-ok)",
|
||
warn: "var(--status-warn)",
|
||
error: "var(--status-error)",
|
||
idle: "var(--text-disabled)"
|
||
};
|
||
|
||
/**
|
||
* Status dot with optional pulsing ring — on-air / live indicators.
|
||
*/
|
||
function StatusDot({
|
||
status = "idle",
|
||
pulse = false,
|
||
size = 8,
|
||
label,
|
||
style = {}
|
||
}) {
|
||
const c = colors[status] || colors.idle;
|
||
const doPulse = pulse || status === "live";
|
||
return /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "7px",
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "relative",
|
||
width: size,
|
||
height: size,
|
||
flex: "0 0 auto"
|
||
}
|
||
}, doPulse && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "absolute",
|
||
inset: 0,
|
||
borderRadius: "50%",
|
||
background: c,
|
||
animation: "ctv-ping 1.6s cubic-bezier(0,0,0.2,1) infinite"
|
||
}
|
||
}), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "absolute",
|
||
inset: 0,
|
||
borderRadius: "50%",
|
||
background: c,
|
||
boxShadow: doPulse ? `0 0 6px ${c}` : "none"
|
||
}
|
||
}), /*#__PURE__*/React.createElement("style", null, `@keyframes ctv-ping{0%{transform:scale(1);opacity:.55}70%,100%{transform:scale(2.4);opacity:0}}`)), label && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--text-xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, label));
|
||
}
|
||
Object.assign(__ds_scope, { StatusDot });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data-display/StatusDot.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/data-display/Tag.jsx
|
||
try { (() => {
|
||
/**
|
||
* Removable chip/tag — smart-collection filters, tags on media.
|
||
*/
|
||
function Tag({
|
||
children,
|
||
onRemove,
|
||
icon = null,
|
||
tone = "neutral",
|
||
style = {}
|
||
}) {
|
||
const [hover, setHover] = React.useState(false);
|
||
const tint = tone === "accent";
|
||
return /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "6px",
|
||
height: "22px",
|
||
padding: onRemove ? "0 5px 0 8px" : "0 9px",
|
||
borderRadius: "var(--radius-xs)",
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
color: tint ? "var(--ctv-accent)" : "var(--text-primary)",
|
||
background: tint ? "var(--ctv-accent-soft)" : "var(--ctv-surface-3)",
|
||
border: `1px solid ${tint ? "rgba(91,124,250,0.35)" : "var(--border-hairline)"}`,
|
||
whiteSpace: "nowrap",
|
||
...style
|
||
}
|
||
}, icon && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, icon), children, onRemove && /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: onRemove,
|
||
onMouseEnter: () => setHover(true),
|
||
onMouseLeave: () => setHover(false),
|
||
"aria-label": "Remove",
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
width: 15,
|
||
height: 15,
|
||
padding: 0,
|
||
border: "none",
|
||
borderRadius: "3px",
|
||
cursor: "pointer",
|
||
color: hover ? "var(--text-primary)" : "var(--text-disabled)",
|
||
background: hover ? "rgba(255,255,255,0.08)" : "transparent"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("svg", {
|
||
width: "10",
|
||
height: "10",
|
||
viewBox: "0 0 24 24",
|
||
fill: "none"
|
||
}, /*#__PURE__*/React.createElement("path", {
|
||
d: "M6 6l12 12M18 6L6 18",
|
||
stroke: "currentColor",
|
||
strokeWidth: "2.4",
|
||
strokeLinecap: "round"
|
||
}))));
|
||
}
|
||
Object.assign(__ds_scope, { Tag });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data-display/Tag.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/feedback/Spinner.jsx
|
||
try { (() => {
|
||
/**
|
||
* Indeterminate ring spinner.
|
||
*/
|
||
function Spinner({
|
||
size = 18,
|
||
tone = "accent",
|
||
stroke = 2.4,
|
||
style = {}
|
||
}) {
|
||
const c = {
|
||
accent: "var(--action-primary)",
|
||
muted: "var(--text-disabled)",
|
||
ok: "var(--status-ok)"
|
||
}[tone] || "var(--action-primary)";
|
||
return /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("style", null, `@keyframes ctv-spin{to{transform:rotate(360deg)}}`), /*#__PURE__*/React.createElement("svg", {
|
||
width: size,
|
||
height: size,
|
||
viewBox: "0 0 24 24",
|
||
fill: "none",
|
||
style: {
|
||
animation: "ctv-spin 0.7s linear infinite"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("circle", {
|
||
cx: "12",
|
||
cy: "12",
|
||
r: "9",
|
||
stroke: c,
|
||
strokeOpacity: "0.2",
|
||
strokeWidth: stroke
|
||
}), /*#__PURE__*/React.createElement("path", {
|
||
d: "M21 12a9 9 0 0 0-9-9",
|
||
stroke: c,
|
||
strokeWidth: stroke,
|
||
strokeLinecap: "round"
|
||
})));
|
||
}
|
||
Object.assign(__ds_scope, { Spinner });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Spinner.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/feedback/Toast.jsx
|
||
try { (() => {
|
||
const map = {
|
||
ok: {
|
||
c: "var(--status-ok)",
|
||
bg: "var(--ctv-ok-soft)",
|
||
icon: "M5 12.5l4.5 4.5L19 7"
|
||
},
|
||
warn: {
|
||
c: "var(--status-warn)",
|
||
bg: "var(--ctv-warn-soft)",
|
||
icon: "M12 8v5M12 17h.01"
|
||
},
|
||
error: {
|
||
c: "var(--status-error)",
|
||
bg: "var(--ctv-error-soft)",
|
||
icon: "M12 8v5M12 17h.01"
|
||
},
|
||
info: {
|
||
c: "var(--ctv-accent)",
|
||
bg: "var(--ctv-accent-soft)",
|
||
icon: "M12 11v5M12 8h.01"
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Toast / inline alert. Icon, message, optional close.
|
||
*/
|
||
function Toast({
|
||
tone = "info",
|
||
title,
|
||
message,
|
||
onClose,
|
||
style = {}
|
||
}) {
|
||
const t = map[tone] || map.info;
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
role: "status",
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "flex-start",
|
||
gap: "11px",
|
||
width: "min(380px, 100%)",
|
||
padding: "12px 13px",
|
||
background: "var(--surface-raised)",
|
||
border: "1px solid var(--border-hairline)",
|
||
borderLeft: `2px solid ${t.c}`,
|
||
borderRadius: "var(--radius-md)",
|
||
boxShadow: "var(--shadow-md)",
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
width: 22,
|
||
height: 22,
|
||
borderRadius: "var(--radius-xs)",
|
||
background: t.bg,
|
||
flex: "0 0 auto",
|
||
marginTop: 1
|
||
}
|
||
}, /*#__PURE__*/React.createElement("svg", {
|
||
width: "14",
|
||
height: "14",
|
||
viewBox: "0 0 24 24",
|
||
fill: "none"
|
||
}, /*#__PURE__*/React.createElement("circle", {
|
||
cx: "12",
|
||
cy: "12",
|
||
r: "9",
|
||
stroke: t.c,
|
||
strokeWidth: "0",
|
||
fill: "none"
|
||
}), /*#__PURE__*/React.createElement("path", {
|
||
d: t.icon,
|
||
stroke: t.c,
|
||
strokeWidth: "2.2",
|
||
strokeLinecap: "round",
|
||
strokeLinejoin: "round"
|
||
}))), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0
|
||
}
|
||
}, title && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-semibold) var(--text-sm)/1.3 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, title), message && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: title ? 2 : 0,
|
||
font: "var(--text-xs)/1.45 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, message)), onClose && /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: onClose,
|
||
"aria-label": "Dismiss",
|
||
style: {
|
||
display: "inline-flex",
|
||
padding: 3,
|
||
border: "none",
|
||
background: "transparent",
|
||
color: "var(--text-disabled)",
|
||
cursor: "pointer",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("svg", {
|
||
width: "14",
|
||
height: "14",
|
||
viewBox: "0 0 24 24",
|
||
fill: "none"
|
||
}, /*#__PURE__*/React.createElement("path", {
|
||
d: "M6 6l12 12M18 6L6 18",
|
||
stroke: "currentColor",
|
||
strokeWidth: "2",
|
||
strokeLinecap: "round"
|
||
}))));
|
||
}
|
||
Object.assign(__ds_scope, { Toast });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Toast.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/feedback/Tooltip.jsx
|
||
try { (() => {
|
||
/**
|
||
* Hover tooltip. Wraps a trigger; shows a small dark label.
|
||
*/
|
||
function Tooltip({
|
||
label,
|
||
placement = "top",
|
||
children,
|
||
style = {}
|
||
}) {
|
||
const [show, setShow] = React.useState(false);
|
||
const pos = {
|
||
top: {
|
||
bottom: "calc(100% + 7px)",
|
||
left: "50%",
|
||
transform: "translateX(-50%)"
|
||
},
|
||
bottom: {
|
||
top: "calc(100% + 7px)",
|
||
left: "50%",
|
||
transform: "translateX(-50%)"
|
||
},
|
||
left: {
|
||
right: "calc(100% + 7px)",
|
||
top: "50%",
|
||
transform: "translateY(-50%)"
|
||
},
|
||
right: {
|
||
left: "calc(100% + 7px)",
|
||
top: "50%",
|
||
transform: "translateY(-50%)"
|
||
}
|
||
}[placement];
|
||
return /*#__PURE__*/React.createElement("span", {
|
||
onMouseEnter: () => setShow(true),
|
||
onMouseLeave: () => setShow(false),
|
||
style: {
|
||
position: "relative",
|
||
display: "inline-flex",
|
||
...style
|
||
}
|
||
}, children, show && label && /*#__PURE__*/React.createElement("span", {
|
||
role: "tooltip",
|
||
style: {
|
||
position: "absolute",
|
||
zIndex: 50,
|
||
...pos,
|
||
padding: "5px 9px",
|
||
borderRadius: "var(--radius-sm)",
|
||
background: "var(--ctv-surface-3)",
|
||
border: "1px solid var(--border-control)",
|
||
boxShadow: "var(--shadow-pop)",
|
||
color: "var(--text-primary)",
|
||
font: "var(--weight-medium) var(--text-xs)/1.2 var(--font-sans)",
|
||
whiteSpace: "nowrap",
|
||
pointerEvents: "none"
|
||
}
|
||
}, label));
|
||
}
|
||
Object.assign(__ds_scope, { Tooltip });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Tooltip.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/forms/Button.jsx
|
||
try { (() => {
|
||
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
||
const sizeMap = {
|
||
sm: {
|
||
height: "var(--control-h-sm)",
|
||
padding: "0 12px",
|
||
font: "var(--text-xs)"
|
||
},
|
||
md: {
|
||
height: "var(--control-h)",
|
||
padding: "0 14px",
|
||
font: "var(--text-sm)"
|
||
}
|
||
};
|
||
const variantStyle = {
|
||
primary: {
|
||
background: "var(--action-primary)",
|
||
color: "var(--text-on-accent)",
|
||
border: "1px solid transparent"
|
||
},
|
||
secondary: {
|
||
background: "var(--ctv-surface-2)",
|
||
color: "var(--text-primary)",
|
||
border: "1px solid var(--border-control)"
|
||
},
|
||
ghost: {
|
||
background: "transparent",
|
||
color: "var(--text-secondary)",
|
||
border: "1px solid transparent"
|
||
},
|
||
danger: {
|
||
background: "var(--ctv-error-soft)",
|
||
color: "var(--status-error)",
|
||
border: "1px solid rgba(229,72,77,0.35)"
|
||
}
|
||
};
|
||
const hoverBg = {
|
||
primary: "var(--action-primary-hover)",
|
||
secondary: "var(--ctv-surface-3)",
|
||
ghost: "var(--ctv-surface-2)",
|
||
danger: "rgba(229,72,77,0.22)"
|
||
};
|
||
|
||
/**
|
||
* ChicoryTV primary action button.
|
||
*/
|
||
function Button({
|
||
children,
|
||
variant = "primary",
|
||
size = "md",
|
||
startIcon = null,
|
||
endIcon = null,
|
||
disabled = false,
|
||
loading = false,
|
||
fullWidth = false,
|
||
onClick,
|
||
type = "button",
|
||
style = {},
|
||
...rest
|
||
}) {
|
||
const [hover, setHover] = React.useState(false);
|
||
const [active, setActive] = React.useState(false);
|
||
const s = sizeMap[size] || sizeMap.md;
|
||
const v = variantStyle[variant] || variantStyle.primary;
|
||
const isDisabled = disabled || loading;
|
||
return /*#__PURE__*/React.createElement("button", _extends({
|
||
type: type,
|
||
disabled: isDisabled,
|
||
onClick: onClick,
|
||
onMouseEnter: () => setHover(true),
|
||
onMouseLeave: () => {
|
||
setHover(false);
|
||
setActive(false);
|
||
},
|
||
onMouseDown: () => setActive(true),
|
||
onMouseUp: () => setActive(false),
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: "7px",
|
||
height: s.height,
|
||
padding: s.padding,
|
||
width: fullWidth ? "100%" : "auto",
|
||
font: `var(--weight-medium) ${s.font}/1 var(--font-sans)`,
|
||
letterSpacing: "0.01em",
|
||
borderRadius: "var(--radius-sm)",
|
||
cursor: isDisabled ? "not-allowed" : "pointer",
|
||
opacity: isDisabled ? 0.45 : 1,
|
||
whiteSpace: "nowrap",
|
||
transition: "background var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard)",
|
||
transform: active && !isDisabled ? "translateY(0.5px)" : "none",
|
||
...v,
|
||
background: !isDisabled && hover ? hoverBg[variant] : v.background,
|
||
...style
|
||
}
|
||
}, rest), loading ? /*#__PURE__*/React.createElement(Spinner16, null) : startIcon, children, !loading && endIcon);
|
||
}
|
||
function Spinner16() {
|
||
return /*#__PURE__*/React.createElement("svg", {
|
||
width: "14",
|
||
height: "14",
|
||
viewBox: "0 0 24 24",
|
||
fill: "none",
|
||
style: {
|
||
animation: "ctv-spin 0.7s linear infinite"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("style", null, `@keyframes ctv-spin{to{transform:rotate(360deg)}}`), /*#__PURE__*/React.createElement("circle", {
|
||
cx: "12",
|
||
cy: "12",
|
||
r: "9",
|
||
stroke: "currentColor",
|
||
strokeOpacity: "0.25",
|
||
strokeWidth: "3"
|
||
}), /*#__PURE__*/React.createElement("path", {
|
||
d: "M21 12a9 9 0 0 0-9-9",
|
||
stroke: "currentColor",
|
||
strokeWidth: "3",
|
||
strokeLinecap: "round"
|
||
}));
|
||
}
|
||
Object.assign(__ds_scope, { Button });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Button.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/forms/Checkbox.jsx
|
||
try { (() => {
|
||
/**
|
||
* Checkbox with label. Controlled via checked/onChange.
|
||
*/
|
||
function Checkbox({
|
||
checked = false,
|
||
onChange,
|
||
disabled = false,
|
||
label,
|
||
indeterminate = false,
|
||
style = {}
|
||
}) {
|
||
const toggle = () => {
|
||
if (!disabled && onChange) onChange(!checked);
|
||
};
|
||
const on = checked || indeterminate;
|
||
return /*#__PURE__*/React.createElement("label", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "8px",
|
||
cursor: disabled ? "not-allowed" : "pointer",
|
||
opacity: disabled ? 0.5 : 1,
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
role: "checkbox",
|
||
"aria-checked": indeterminate ? "mixed" : checked,
|
||
onClick: toggle,
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
width: 17,
|
||
height: 17,
|
||
flex: "0 0 auto",
|
||
borderRadius: "var(--radius-xs)",
|
||
background: on ? "var(--action-primary)" : "var(--ctv-bg-sunken)",
|
||
border: `1px solid ${on ? "transparent" : "var(--border-control)"}`,
|
||
transition: "background var(--dur-fast), border-color var(--dur-fast)"
|
||
}
|
||
}, indeterminate ? /*#__PURE__*/React.createElement("svg", {
|
||
width: "11",
|
||
height: "11",
|
||
viewBox: "0 0 24 24"
|
||
}, /*#__PURE__*/React.createElement("path", {
|
||
d: "M6 12h12",
|
||
stroke: "#fff",
|
||
strokeWidth: "3",
|
||
strokeLinecap: "round"
|
||
})) : checked ? /*#__PURE__*/React.createElement("svg", {
|
||
width: "12",
|
||
height: "12",
|
||
viewBox: "0 0 24 24",
|
||
fill: "none"
|
||
}, /*#__PURE__*/React.createElement("path", {
|
||
d: "M5 12.5l4.5 4.5L19 7",
|
||
stroke: "#fff",
|
||
strokeWidth: "3",
|
||
strokeLinecap: "round",
|
||
strokeLinejoin: "round"
|
||
})) : null), label && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--text-sm)/1 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, label));
|
||
}
|
||
Object.assign(__ds_scope, { Checkbox });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Checkbox.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/forms/IconButton.jsx
|
||
try { (() => {
|
||
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
||
/**
|
||
* Compact square icon button — table row actions, toolbars.
|
||
*/
|
||
function IconButton({
|
||
children,
|
||
size = "md",
|
||
variant = "ghost",
|
||
disabled = false,
|
||
active = false,
|
||
title,
|
||
onClick,
|
||
style = {},
|
||
...rest
|
||
}) {
|
||
const [hover, setHover] = React.useState(false);
|
||
const dim = size === "sm" ? 28 : 32;
|
||
const base = variant === "solid" ? {
|
||
background: "var(--action-primary)",
|
||
color: "var(--text-on-accent)"
|
||
} : {
|
||
background: active ? "var(--ctv-surface-3)" : "transparent",
|
||
color: active ? "var(--text-primary)" : "var(--text-secondary)"
|
||
};
|
||
const hoverBg = variant === "solid" ? "var(--action-primary-hover)" : "var(--ctv-surface-2)";
|
||
return /*#__PURE__*/React.createElement("button", _extends({
|
||
type: "button",
|
||
title: title,
|
||
"aria-label": title,
|
||
disabled: disabled,
|
||
onClick: onClick,
|
||
onMouseEnter: () => setHover(true),
|
||
onMouseLeave: () => setHover(false),
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
width: dim,
|
||
height: dim,
|
||
flex: "0 0 auto",
|
||
borderRadius: "var(--radius-sm)",
|
||
border: "1px solid transparent",
|
||
cursor: disabled ? "not-allowed" : "pointer",
|
||
opacity: disabled ? 0.4 : 1,
|
||
color: base.color,
|
||
background: !disabled && hover ? hoverBg : base.background,
|
||
transition: "background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard)",
|
||
...(!disabled && hover && variant !== "solid" ? {
|
||
color: "var(--text-primary)"
|
||
} : {}),
|
||
...style
|
||
}
|
||
}, rest), children);
|
||
}
|
||
Object.assign(__ds_scope, { IconButton });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/IconButton.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/forms/Input.jsx
|
||
try { (() => {
|
||
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
||
/**
|
||
* Text input with optional label, leading icon and error state.
|
||
*/
|
||
function Input({
|
||
label,
|
||
value,
|
||
onChange,
|
||
placeholder,
|
||
leadingIcon = null,
|
||
trailing = null,
|
||
error = null,
|
||
disabled = false,
|
||
size = "md",
|
||
type = "text",
|
||
fullWidth = true,
|
||
style = {},
|
||
...rest
|
||
}) {
|
||
const [focus, setFocus] = React.useState(false);
|
||
const height = size === "sm" ? "var(--control-h-sm)" : "var(--control-h)";
|
||
const borderColor = error ? "var(--status-error)" : focus ? "var(--action-primary)" : "var(--border-control)";
|
||
return /*#__PURE__*/React.createElement("label", {
|
||
style: {
|
||
display: "block",
|
||
width: fullWidth ? "100%" : "auto"
|
||
}
|
||
}, label && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "block",
|
||
marginBottom: "6px",
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, label), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: "8px",
|
||
height,
|
||
padding: "0 10px",
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: `1px solid ${borderColor}`,
|
||
borderRadius: "var(--radius-sm)",
|
||
boxShadow: focus && !error ? "0 0 0 3px var(--focus-ring)" : "none",
|
||
transition: "border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard)",
|
||
opacity: disabled ? 0.5 : 1,
|
||
...style
|
||
}
|
||
}, leadingIcon && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "flex",
|
||
color: "var(--text-disabled)",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, leadingIcon), /*#__PURE__*/React.createElement("input", _extends({
|
||
type: type,
|
||
value: value,
|
||
onChange: onChange,
|
||
placeholder: placeholder,
|
||
disabled: disabled,
|
||
onFocus: () => setFocus(true),
|
||
onBlur: () => setFocus(false),
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0,
|
||
background: "transparent",
|
||
border: "none",
|
||
outline: "none",
|
||
color: "var(--text-primary)",
|
||
font: "var(--weight-normal) var(--text-sm)/1 var(--font-sans)"
|
||
}
|
||
}, rest)), trailing && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "flex",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, trailing)), error && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "block",
|
||
marginTop: "5px",
|
||
font: "var(--text-xs)/1.3 var(--font-sans)",
|
||
color: "var(--status-error)"
|
||
}
|
||
}, error));
|
||
}
|
||
Object.assign(__ds_scope, { Input });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Input.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/forms/Select.jsx
|
||
try { (() => {
|
||
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
||
/**
|
||
* Styled select built on a native <select> for accessibility.
|
||
*/
|
||
function Select({
|
||
label,
|
||
value,
|
||
onChange,
|
||
options = [],
|
||
disabled = false,
|
||
size = "md",
|
||
fullWidth = true,
|
||
style = {},
|
||
...rest
|
||
}) {
|
||
const [focus, setFocus] = React.useState(false);
|
||
const height = size === "sm" ? "var(--control-h-sm)" : "var(--control-h)";
|
||
const opts = options.map(o => typeof o === "string" ? {
|
||
value: o,
|
||
label: o
|
||
} : o);
|
||
return /*#__PURE__*/React.createElement("label", {
|
||
style: {
|
||
display: "block",
|
||
width: fullWidth ? "100%" : "auto"
|
||
}
|
||
}, label && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "block",
|
||
marginBottom: "6px",
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, label), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "relative",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
height,
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: `1px solid ${focus ? "var(--action-primary)" : "var(--border-control)"}`,
|
||
borderRadius: "var(--radius-sm)",
|
||
boxShadow: focus ? "0 0 0 3px var(--focus-ring)" : "none",
|
||
transition: "border-color var(--dur-fast), box-shadow var(--dur-fast)",
|
||
opacity: disabled ? 0.5 : 1,
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("select", _extends({
|
||
value: value,
|
||
onChange: onChange,
|
||
disabled: disabled,
|
||
onFocus: () => setFocus(true),
|
||
onBlur: () => setFocus(false),
|
||
style: {
|
||
appearance: "none",
|
||
WebkitAppearance: "none",
|
||
flex: 1,
|
||
height: "100%",
|
||
padding: "0 30px 0 10px",
|
||
background: "transparent",
|
||
border: "none",
|
||
outline: "none",
|
||
color: "var(--text-primary)",
|
||
font: "var(--weight-normal) var(--text-sm)/1 var(--font-sans)",
|
||
cursor: disabled ? "not-allowed" : "pointer"
|
||
}
|
||
}, rest), opts.map(o => /*#__PURE__*/React.createElement("option", {
|
||
key: o.value,
|
||
value: o.value,
|
||
style: {
|
||
background: "var(--ctv-surface-2)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, o.label))), /*#__PURE__*/React.createElement("svg", {
|
||
width: "14",
|
||
height: "14",
|
||
viewBox: "0 0 24 24",
|
||
fill: "none",
|
||
style: {
|
||
position: "absolute",
|
||
right: 9,
|
||
pointerEvents: "none",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("path", {
|
||
d: "M6 9l6 6 6-6",
|
||
stroke: "currentColor",
|
||
strokeWidth: "2",
|
||
strokeLinecap: "round",
|
||
strokeLinejoin: "round"
|
||
}))));
|
||
}
|
||
Object.assign(__ds_scope, { Select });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Select.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/forms/Switch.jsx
|
||
try { (() => {
|
||
/**
|
||
* On/off toggle switch.
|
||
*/
|
||
function Switch({
|
||
checked = false,
|
||
onChange,
|
||
disabled = false,
|
||
label,
|
||
size = "md",
|
||
style = {}
|
||
}) {
|
||
const w = size === "sm" ? 30 : 36;
|
||
const h = size === "sm" ? 17 : 20;
|
||
const knob = h - 5;
|
||
const toggle = () => {
|
||
if (!disabled && onChange) onChange(!checked);
|
||
};
|
||
return /*#__PURE__*/React.createElement("label", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "9px",
|
||
cursor: disabled ? "not-allowed" : "pointer",
|
||
opacity: disabled ? 0.5 : 1,
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
role: "switch",
|
||
"aria-checked": checked,
|
||
onClick: toggle,
|
||
style: {
|
||
position: "relative",
|
||
width: w,
|
||
height: h,
|
||
flex: "0 0 auto",
|
||
borderRadius: "var(--radius-pill)",
|
||
background: checked ? "var(--action-primary)" : "var(--ctv-surface-3)",
|
||
border: `1px solid ${checked ? "transparent" : "var(--border-control)"}`,
|
||
transition: "background var(--dur-base) var(--ease-standard)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "absolute",
|
||
top: "50%",
|
||
left: checked ? `calc(100% - ${knob + 2}px)` : "2px",
|
||
transform: "translateY(-50%)",
|
||
width: knob,
|
||
height: knob,
|
||
borderRadius: "50%",
|
||
background: "#fff",
|
||
boxShadow: "0 1px 2px rgba(0,0,0,0.5)",
|
||
transition: "left var(--dur-base) var(--ease-out)"
|
||
}
|
||
})), label && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--text-sm)/1 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, label));
|
||
}
|
||
Object.assign(__ds_scope, { Switch });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Switch.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/navigation/NavItem.jsx
|
||
try { (() => {
|
||
/**
|
||
* Single left-sidebar navigation item. Compose a list of these.
|
||
*/
|
||
function NavItem({
|
||
icon = null,
|
||
label,
|
||
active = false,
|
||
badge = null,
|
||
badgeTone = "warn",
|
||
onClick,
|
||
href,
|
||
style = {}
|
||
}) {
|
||
const [hover, setHover] = React.useState(false);
|
||
const Comp = href ? "a" : "button";
|
||
const bt = {
|
||
warn: "var(--status-warn)",
|
||
error: "var(--status-error)",
|
||
accent: "var(--action-primary)"
|
||
}[badgeTone];
|
||
return /*#__PURE__*/React.createElement(Comp, {
|
||
href: href,
|
||
onClick: onClick,
|
||
onMouseEnter: () => setHover(true),
|
||
onMouseLeave: () => setHover(false),
|
||
style: {
|
||
position: "relative",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: "10px",
|
||
width: "100%",
|
||
height: "34px",
|
||
padding: "0 10px",
|
||
border: "none",
|
||
borderRadius: "var(--radius-sm)",
|
||
textDecoration: "none",
|
||
cursor: "pointer",
|
||
textAlign: "left",
|
||
font: `${active ? "var(--weight-medium)" : "var(--weight-normal)"} var(--text-sm)/1 var(--font-sans)`,
|
||
color: active ? "var(--text-primary)" : hover ? "var(--text-primary)" : "var(--text-secondary)",
|
||
background: active ? "var(--ctv-accent-soft)" : hover ? "var(--ctv-surface-2)" : "transparent",
|
||
transition: "background var(--dur-fast) var(--ease-standard), color var(--dur-fast)",
|
||
...style
|
||
}
|
||
}, active && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "absolute",
|
||
left: 0,
|
||
top: 7,
|
||
bottom: 7,
|
||
width: 2.5,
|
||
borderRadius: 2,
|
||
background: "var(--action-primary)"
|
||
}
|
||
}), icon && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
color: active ? "var(--action-primary)" : "currentColor",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, icon), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0,
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap"
|
||
}
|
||
}, label), badge != null && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
minWidth: 17,
|
||
height: 17,
|
||
padding: "0 5px",
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
borderRadius: "var(--radius-pill)",
|
||
background: bt,
|
||
color: badgeTone === "warn" ? "#1A1206" : "#fff",
|
||
font: "var(--weight-semibold) var(--text-2xs)/1 var(--font-mono)"
|
||
}
|
||
}, badge));
|
||
}
|
||
|
||
/**
|
||
* Sidebar section label (eyebrow) that groups NavItems.
|
||
*/
|
||
function NavSection({
|
||
children,
|
||
style = {}
|
||
}) {
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
padding: "14px 10px 6px",
|
||
font: "var(--weight-semibold) var(--text-2xs)/1 var(--font-sans)",
|
||
letterSpacing: "var(--tracking-caps)",
|
||
textTransform: "uppercase",
|
||
color: "var(--text-disabled)",
|
||
...style
|
||
}
|
||
}, children);
|
||
}
|
||
Object.assign(__ds_scope, { NavItem, NavSection });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/navigation/NavItem.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// components/navigation/Tabs.jsx
|
||
try { (() => {
|
||
/**
|
||
* Underlined tab bar. Controlled via value/onChange.
|
||
*/
|
||
function Tabs({
|
||
tabs = [],
|
||
value,
|
||
onChange,
|
||
style = {}
|
||
}) {
|
||
const items = tabs.map(t => typeof t === "string" ? {
|
||
value: t,
|
||
label: t
|
||
} : t);
|
||
const active = value ?? items[0]?.value;
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
gap: "2px",
|
||
borderBottom: "1px solid var(--border-hairline)",
|
||
...style
|
||
}
|
||
}, items.map(t => {
|
||
const on = t.value === active;
|
||
return /*#__PURE__*/React.createElement(Tab, {
|
||
key: t.value,
|
||
tab: t,
|
||
on: on,
|
||
onChange: onChange
|
||
});
|
||
}));
|
||
}
|
||
function Tab({
|
||
tab,
|
||
on,
|
||
onChange
|
||
}) {
|
||
const [hover, setHover] = React.useState(false);
|
||
return /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: () => onChange && onChange(tab.value),
|
||
onMouseEnter: () => setHover(true),
|
||
onMouseLeave: () => setHover(false),
|
||
style: {
|
||
position: "relative",
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "7px",
|
||
height: "34px",
|
||
padding: "0 12px",
|
||
border: "none",
|
||
background: "transparent",
|
||
cursor: "pointer",
|
||
font: `${on ? "var(--weight-medium)" : "var(--weight-normal)"} var(--text-sm)/1 var(--font-sans)`,
|
||
color: on ? "var(--text-primary)" : hover ? "var(--text-primary)" : "var(--text-secondary)",
|
||
transition: "color var(--dur-fast)"
|
||
}
|
||
}, tab.icon && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
color: on ? "var(--action-primary)" : "currentColor"
|
||
}
|
||
}, tab.icon), tab.label, tab.count != null && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--text-2xs)/1 var(--font-mono)",
|
||
color: "var(--text-disabled)",
|
||
background: "var(--ctv-surface-3)",
|
||
padding: "2px 5px",
|
||
borderRadius: "var(--radius-xs)"
|
||
}
|
||
}, tab.count), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
position: "absolute",
|
||
left: 6,
|
||
right: 6,
|
||
bottom: -1,
|
||
height: 2,
|
||
borderRadius: 2,
|
||
background: on ? "var(--action-primary)" : "transparent",
|
||
transition: "background var(--dur-fast)"
|
||
}
|
||
}));
|
||
}
|
||
Object.assign(__ds_scope, { Tabs });
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/navigation/Tabs.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
// design_handoff_channel_builder/ChannelBuilder.jsx
|
||
try { (() => {
|
||
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
||
// Channel Builder — GROUND-UP replacement for the four-editor create chain
|
||
// (Channel -> Schedule -> Schedule Items -> Playout). One library-to-lineup screen.
|
||
// Left: searchable library browser. Center: the channel lineup (add by double-click
|
||
// or drag; drag to reorder). Right: friendly options + Channel Template + Advanced.
|
||
// Behind the scenes "Create" builds Channel + Collection + Schedule + Items + Playout.
|
||
(function () {
|
||
const NS = window.ChicoryTVDesignSystem_eb3b61;
|
||
const {
|
||
Button,
|
||
IconButton,
|
||
Input,
|
||
Select,
|
||
Switch,
|
||
Badge,
|
||
ChannelLogo,
|
||
Tooltip
|
||
} = NS;
|
||
const Ico = window.Ico;
|
||
const mono = {
|
||
fontFamily: "var(--font-mono)",
|
||
fontVariantNumeric: "tabular-nums"
|
||
};
|
||
const eyebrow = {
|
||
font: "var(--weight-semibold) var(--text-2xs)/1 var(--font-sans)",
|
||
letterSpacing: "0.06em",
|
||
textTransform: "uppercase",
|
||
color: "var(--text-disabled)"
|
||
};
|
||
|
||
// ---- Mock library (shows / movies / music / docs across libraries) --------
|
||
const LIB = [{
|
||
id: "s1",
|
||
title: "Looney Tunes",
|
||
type: "Show",
|
||
library: "Cartoons",
|
||
sub: "247 shorts",
|
||
min: 96
|
||
}, {
|
||
id: "s2",
|
||
title: "Tom & Jerry",
|
||
type: "Show",
|
||
library: "Cartoons",
|
||
sub: "164 shorts",
|
||
min: 72
|
||
}, {
|
||
id: "s3",
|
||
title: "Popeye",
|
||
type: "Show",
|
||
library: "Cartoons",
|
||
sub: "108 shorts",
|
||
min: 54
|
||
}, {
|
||
id: "s4",
|
||
title: "The Flintstones",
|
||
type: "Show",
|
||
library: "Cartoons",
|
||
sub: "166 episodes",
|
||
min: 84
|
||
}, {
|
||
id: "s5",
|
||
title: "Scooby-Doo",
|
||
type: "Show",
|
||
library: "Cartoons",
|
||
sub: "97 episodes",
|
||
min: 60
|
||
}, {
|
||
id: "s6",
|
||
title: "Pink Panther",
|
||
type: "Show",
|
||
library: "Cartoons",
|
||
sub: "124 shorts",
|
||
min: 48
|
||
}, {
|
||
id: "m1",
|
||
title: "Blade Runner",
|
||
type: "Movie",
|
||
library: "Movies",
|
||
sub: "1982 · Sci-Fi",
|
||
min: 117
|
||
}, {
|
||
id: "m2",
|
||
title: "Alien",
|
||
type: "Movie",
|
||
library: "Movies",
|
||
sub: "1979 · Horror",
|
||
min: 117
|
||
}, {
|
||
id: "m3",
|
||
title: "The Thing",
|
||
type: "Movie",
|
||
library: "Movies",
|
||
sub: "1982 · Horror",
|
||
min: 109
|
||
}, {
|
||
id: "m4",
|
||
title: "Metropolis",
|
||
type: "Movie",
|
||
library: "Movies",
|
||
sub: "1927 · Sci-Fi",
|
||
min: 153
|
||
}, {
|
||
id: "m5",
|
||
title: "Nosferatu",
|
||
type: "Movie",
|
||
library: "Movies",
|
||
sub: "1922 · Horror",
|
||
min: 94
|
||
}, {
|
||
id: "t1",
|
||
title: "The Twilight Zone",
|
||
type: "Show",
|
||
library: "TV Shows",
|
||
sub: "156 episodes",
|
||
min: 51
|
||
}, {
|
||
id: "t2",
|
||
title: "Star Trek",
|
||
type: "Show",
|
||
library: "TV Shows",
|
||
sub: "79 episodes",
|
||
min: 50
|
||
}, {
|
||
id: "t3",
|
||
title: "Columbo",
|
||
type: "Show",
|
||
library: "TV Shows",
|
||
sub: "69 episodes",
|
||
min: 94
|
||
}, {
|
||
id: "t4",
|
||
title: "Cheers",
|
||
type: "Show",
|
||
library: "TV Shows",
|
||
sub: "275 episodes",
|
||
min: 24
|
||
}, {
|
||
id: "u1",
|
||
title: "80s Synthpop",
|
||
type: "Music",
|
||
library: "Music Videos",
|
||
sub: "212 videos",
|
||
min: 45
|
||
}, {
|
||
id: "u2",
|
||
title: "90s Alt Rock",
|
||
type: "Music",
|
||
library: "Music Videos",
|
||
sub: "188 videos",
|
||
min: 45
|
||
}, {
|
||
id: "u3",
|
||
title: "Chart Countdown",
|
||
type: "Music",
|
||
library: "Music Videos",
|
||
sub: "Top 40",
|
||
min: 60
|
||
}, {
|
||
id: "d1",
|
||
title: "Blue Planet",
|
||
type: "Doc",
|
||
library: "Documentaries",
|
||
sub: "8 episodes",
|
||
min: 50
|
||
}, {
|
||
id: "d2",
|
||
title: "Planet Earth",
|
||
type: "Doc",
|
||
library: "Documentaries",
|
||
sub: "11 episodes",
|
||
min: 50
|
||
}, {
|
||
id: "d3",
|
||
title: "Cosmos",
|
||
type: "Doc",
|
||
library: "Documentaries",
|
||
sub: "13 episodes",
|
||
min: 60
|
||
}];
|
||
const LIBRARIES = ["All", "Cartoons", "Movies", "TV Shows", "Music Videos", "Documentaries"];
|
||
const TYPE_ICON = {
|
||
Show: "Tv",
|
||
Movie: "Film",
|
||
Music: "Music",
|
||
Doc: "Radio",
|
||
Multi: "Layers",
|
||
Smart: "Sparkles",
|
||
Manual: "FolderTree",
|
||
Rerun: "Repeat"
|
||
};
|
||
|
||
// Collections — curated groups (Manual / Smart / Multi / Rerun) addable as a
|
||
// single lineup entry, so the builder isn't limited to raw library items.
|
||
const COLLECTIONS = [{
|
||
id: "c1",
|
||
title: "Prime Time Cartoons",
|
||
type: "Multi",
|
||
library: "Collection",
|
||
sub: "Multi · 62 items",
|
||
min: 180,
|
||
coll: true
|
||
}, {
|
||
id: "c2",
|
||
title: "Classic Sci-Fi",
|
||
type: "Smart",
|
||
library: "Collection",
|
||
sub: "Smart · rating \u2265 8",
|
||
min: 240,
|
||
coll: true
|
||
}, {
|
||
id: "c3",
|
||
title: "Retro Station IDs",
|
||
type: "Manual",
|
||
library: "Collection",
|
||
sub: "Manual · 24 items",
|
||
min: 12,
|
||
coll: true
|
||
}, {
|
||
id: "c4",
|
||
title: "Saturday Morning",
|
||
type: "Rerun",
|
||
library: "Collection",
|
||
sub: "Rerun block · weekly",
|
||
min: 120,
|
||
coll: true
|
||
}, {
|
||
id: "c5",
|
||
title: "Halloween Marathon",
|
||
type: "Manual",
|
||
library: "Collection",
|
||
sub: "Manual · 9 films",
|
||
min: 900,
|
||
coll: true
|
||
}];
|
||
|
||
// Channel Templates — bundle the clunky technical config; one is pre-selected.
|
||
const TEMPLATES = [{
|
||
id: "std",
|
||
name: "Standard",
|
||
builtin: true,
|
||
desc: "General-purpose 1080p H.264, retro bumpers.",
|
||
shuffle: false,
|
||
always: true,
|
||
sets: ["1080p H.264", "HLS Segmenter", "Sequential", "Always on", "Pre + post filler"]
|
||
}, {
|
||
id: "music",
|
||
name: "Music videos",
|
||
builtin: true,
|
||
desc: "Continuous rotation, no fillers, direct stream.",
|
||
shuffle: true,
|
||
always: true,
|
||
sets: ["720p H.264", "HLS Direct", "Shuffle", "Always on", "No filler"]
|
||
}, {
|
||
id: "film",
|
||
name: "Movie night",
|
||
builtin: true,
|
||
desc: "Film-grain HEVC, mid-roll ad breaks.",
|
||
shuffle: false,
|
||
always: false,
|
||
sets: ["1080p HEVC", "MPEG-TS", "Sequential", "On-demand", "Mid-roll ads"]
|
||
}, {
|
||
id: "retro",
|
||
name: "Retro Saturday AM",
|
||
builtin: false,
|
||
desc: "Your saved cartoon-block template.",
|
||
shuffle: true,
|
||
always: true,
|
||
sets: ["1080p H.264", "HLS Segmenter", "Shuffle", "Always on", "Bumpers + ads"]
|
||
}];
|
||
|
||
// Advanced (~30-field) override set, grouped. Values shown as template-inherited.
|
||
const ADV = [{
|
||
group: "Streaming",
|
||
fields: [["Streaming mode", "HLS Segmenter"], ["FFmpeg profile", "1080p H.264"], ["Resolution", "1920×1080"], ["Video bitrate", "8000 kbps"], ["Audio bitrate", "192 kbps"], ["Buffer size", "16000 kb"]]
|
||
}, {
|
||
group: "Filler",
|
||
fields: [["Pre-roll", "Retro Bumpers"], ["Mid-roll", "Ad Break"], ["Post-roll", "Outro"], ["Tail filler", "None"], ["Fallback", "Test Pattern"], ["Filler kind", "Pad to :00"]]
|
||
}, {
|
||
group: "Playback",
|
||
fields: [["Interleave", "On"], ["Keep multi-part together", "On"], ["Watermark", "Channel logo"], ["Subtitle mode", "Any"], ["Preferred audio", "English"], ["Preferred subtitle", "None"]]
|
||
}, {
|
||
group: "Behavior",
|
||
fields: [["Guide mode default", "Normal"], ["Song video mode", "Off"], ["On-demand", "Off"], ["Idle behavior", "Offline image"], ["Transcode audio", "Normalize"], ["Number scheme", "Auto"]]
|
||
}];
|
||
|
||
// deterministic low-chroma poster hue from a title
|
||
function hueOf(s) {
|
||
let h = 0;
|
||
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) % 360;
|
||
return h;
|
||
}
|
||
function fmtDur(totalMin) {
|
||
const h = Math.floor(totalMin / 60),
|
||
m = totalMin % 60;
|
||
if (h && m) return h + "h " + String(m).padStart(2, "0") + "m";
|
||
if (h) return h + "h";
|
||
return m + "m";
|
||
}
|
||
|
||
// ---- Overlay scroll region — hides native bar (no reflow when it appears),
|
||
// shows a subtle thumb only while scrolling, fading out when idle. ------
|
||
function ScrollArea({
|
||
children,
|
||
innerStyle,
|
||
scrollerRef
|
||
}) {
|
||
const local = React.useRef(null);
|
||
const timer = React.useRef(null);
|
||
const [thumb, setThumb] = React.useState(null);
|
||
const setRef = n => {
|
||
local.current = n;
|
||
if (scrollerRef) scrollerRef.current = n;
|
||
};
|
||
const update = () => {
|
||
const el = local.current;
|
||
if (!el) return;
|
||
const {
|
||
scrollTop,
|
||
scrollHeight,
|
||
clientHeight
|
||
} = el;
|
||
if (scrollHeight <= clientHeight + 2) {
|
||
setThumb(null);
|
||
return;
|
||
}
|
||
const track = clientHeight - 8;
|
||
const h = Math.max(28, track * clientHeight / scrollHeight);
|
||
const top = 4 + (track - h) * (scrollTop / (scrollHeight - clientHeight));
|
||
setThumb({
|
||
top,
|
||
h
|
||
});
|
||
clearTimeout(timer.current);
|
||
timer.current = setTimeout(() => setThumb(t => t ? {
|
||
...t,
|
||
hidden: true
|
||
} : null), 850);
|
||
};
|
||
React.useEffect(() => {
|
||
const id = setTimeout(update, 60);
|
||
return () => {
|
||
clearTimeout(id);
|
||
clearTimeout(timer.current);
|
||
};
|
||
}, []);
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "relative",
|
||
flex: 1,
|
||
minHeight: 0,
|
||
minWidth: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
ref: setRef,
|
||
onScroll: update,
|
||
className: "ctv-hide-sb",
|
||
style: {
|
||
height: "100%",
|
||
overflowY: "auto",
|
||
overflowX: "hidden",
|
||
scrollbarWidth: "none",
|
||
scrollBehavior: "smooth",
|
||
...innerStyle
|
||
}
|
||
}, children), thumb && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "absolute",
|
||
top: thumb.top,
|
||
right: 2,
|
||
width: 4,
|
||
height: thumb.h,
|
||
borderRadius: 3,
|
||
background: "var(--text-secondary)",
|
||
opacity: thumb.hidden ? 0 : 0.5,
|
||
transition: "opacity 260ms var(--ease-standard, ease)",
|
||
pointerEvents: "none"
|
||
}
|
||
}));
|
||
}
|
||
|
||
// ---- Segmented control — iOS-style sliding active highlight ---------------
|
||
function Segmented({
|
||
value,
|
||
options,
|
||
onChange,
|
||
style
|
||
}) {
|
||
const idx = Math.max(0, options.findIndex(o => o.value === value));
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "relative",
|
||
display: "grid",
|
||
gridAutoFlow: "column",
|
||
gridAutoColumns: "1fr",
|
||
padding: 2,
|
||
borderRadius: "var(--radius-sm)",
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: "1px solid var(--border-hairline)",
|
||
...style
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "absolute",
|
||
top: 2,
|
||
bottom: 2,
|
||
left: 2,
|
||
width: `calc((100% - 4px) / ${options.length})`,
|
||
transform: `translateX(${idx * 100}%)`,
|
||
background: "var(--ctv-surface-3)",
|
||
borderRadius: "var(--radius-xs)",
|
||
boxShadow: "var(--shadow-sm)",
|
||
transition: "transform 300ms cubic-bezier(0.16,1,0.3,1)",
|
||
pointerEvents: "none"
|
||
}
|
||
}), options.map(o => {
|
||
const on = o.value === value;
|
||
return /*#__PURE__*/React.createElement("button", {
|
||
key: o.value,
|
||
type: "button",
|
||
title: o.title || o.label,
|
||
onClick: () => onChange(o.value),
|
||
style: {
|
||
position: "relative",
|
||
zIndex: 1,
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: 6,
|
||
padding: "5px 10px",
|
||
border: "none",
|
||
background: "transparent",
|
||
cursor: "pointer",
|
||
color: on ? "var(--text-primary)" : "var(--text-secondary)",
|
||
transition: "color 220ms cubic-bezier(0.16,1,0.3,1)",
|
||
font: "var(--weight-medium) var(--text-2xs)/1 var(--font-sans)"
|
||
}
|
||
}, o.icon && /*#__PURE__*/React.createElement(Ico, {
|
||
n: o.icon,
|
||
s: 13
|
||
}), o.label);
|
||
}));
|
||
}
|
||
|
||
// ---- Poster placeholder (striped, tinted, mono meta) ----------------------
|
||
function Poster({
|
||
item,
|
||
w,
|
||
h,
|
||
mini
|
||
}) {
|
||
const hue = hueOf(item.title);
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "relative",
|
||
width: w,
|
||
height: h,
|
||
flex: "0 0 auto",
|
||
overflow: "hidden",
|
||
borderRadius: "var(--radius-sm)",
|
||
border: "1px solid var(--border-hairline)",
|
||
background: `linear-gradient(160deg, hsl(${hue} 24% 17%), hsl(${hue} 26% 12%))`
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "absolute",
|
||
inset: 0,
|
||
backgroundImage: `repeating-linear-gradient(135deg, hsl(${hue} 22% 10% / .55) 0 1px, transparent 1px 9px)`
|
||
}
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "absolute",
|
||
top: mini ? 4 : 7,
|
||
left: mini ? 4 : 7,
|
||
display: "inline-flex",
|
||
color: `hsl(${hue} 30% 68%)`
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: TYPE_ICON[item.type] || "Film",
|
||
s: mini ? 12 : 15
|
||
})), !mini && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "absolute",
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
padding: "18px 8px 8px",
|
||
background: "linear-gradient(to top, rgba(11,13,17,.92), transparent)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-semibold) var(--text-xs)/1.15 var(--font-sans)",
|
||
color: "var(--text-primary)",
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap"
|
||
}
|
||
}, item.title), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 3,
|
||
...mono,
|
||
font: "var(--text-2xs)/1 var(--font-mono)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, item.sub)));
|
||
}
|
||
|
||
// ---- Library card ----------------------------------------------------------
|
||
function LibCard({
|
||
item,
|
||
added,
|
||
compact,
|
||
onAdd,
|
||
onDragStart,
|
||
onDragEnd
|
||
}) {
|
||
if (compact) {
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
draggable: true,
|
||
onDragStart: onDragStart,
|
||
onDragEnd: onDragEnd,
|
||
onDoubleClick: onAdd,
|
||
title: "Double-click or drag to add",
|
||
className: "ctv-press",
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 10,
|
||
padding: "6px 8px",
|
||
borderRadius: "var(--radius-sm)",
|
||
cursor: "grab",
|
||
background: added ? "color-mix(in srgb, var(--action-primary) 14%, transparent)" : "var(--surface-card)",
|
||
border: `1px solid ${added ? "color-mix(in srgb, var(--action-primary) 35%, transparent)" : "var(--border-hairline)"}`
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Poster, {
|
||
item: item,
|
||
w: 30,
|
||
h: 40,
|
||
mini: true
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-xs)/1.15 var(--font-sans)",
|
||
color: "var(--text-primary)",
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap"
|
||
}
|
||
}, item.title), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 2,
|
||
font: "var(--text-2xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, item.type, " \xB7 ", /*#__PURE__*/React.createElement("span", {
|
||
style: mono
|
||
}, item.sub))), /*#__PURE__*/React.createElement(IconButton, {
|
||
size: "sm",
|
||
title: added ? "Added" : "Add to lineup",
|
||
onClick: onAdd
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: added ? "Check" : "Plus",
|
||
s: 15,
|
||
color: added ? "var(--action-primary)" : undefined
|
||
})));
|
||
}
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
draggable: true,
|
||
onDragStart: onDragStart,
|
||
onDragEnd: onDragEnd,
|
||
onDoubleClick: onAdd,
|
||
title: "Double-click or drag to add",
|
||
className: "ctv-lift",
|
||
style: {
|
||
position: "relative",
|
||
cursor: "grab"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "relative",
|
||
borderRadius: "var(--radius-sm)",
|
||
boxShadow: added ? "0 0 0 2px var(--action-primary)" : "none"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Poster, {
|
||
item: item,
|
||
w: "100%",
|
||
h: 150
|
||
}), added && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "absolute",
|
||
top: 6,
|
||
right: 6,
|
||
width: 22,
|
||
height: 22,
|
||
borderRadius: "50%",
|
||
background: "var(--action-primary)",
|
||
color: "var(--text-on-accent)",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
boxShadow: "var(--shadow-sm)",
|
||
animation: "ctv-pop 260ms cubic-bezier(0.16,1,0.3,1)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Check",
|
||
s: 14
|
||
}))));
|
||
}
|
||
|
||
// ---- Lineup row ------------------------------------------------------------
|
||
function LineupRow({
|
||
item,
|
||
index,
|
||
shuffle,
|
||
dragging,
|
||
isOver,
|
||
onRemove,
|
||
dragProps
|
||
}) {
|
||
return /*#__PURE__*/React.createElement("div", _extends({}, dragProps, {
|
||
className: "ctv-lift",
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 11,
|
||
padding: "9px 10px",
|
||
borderRadius: "var(--radius-md)",
|
||
background: "var(--surface-card)",
|
||
border: "1px solid var(--border-hairline)",
|
||
boxShadow: isOver ? "0 -2px 0 var(--action-primary)" : "none",
|
||
opacity: dragging ? 0.35 : 1,
|
||
transition: "opacity var(--dur-fast)"
|
||
}
|
||
}), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
cursor: "grab",
|
||
color: "var(--text-faint)",
|
||
flex: "0 0 auto"
|
||
},
|
||
title: "Drag to reorder"
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "GripVertical",
|
||
s: 16
|
||
})), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
width: 22,
|
||
flex: "0 0 auto",
|
||
textAlign: "center"
|
||
}
|
||
}, shuffle ? /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Shuffle",
|
||
s: 13,
|
||
color: "var(--text-disabled)"
|
||
}) : /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
...mono,
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-mono)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, index + 1)), /*#__PURE__*/React.createElement(Poster, {
|
||
item: item,
|
||
w: 34,
|
||
h: 46,
|
||
mini: true
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-semibold) var(--text-sm)/1.2 var(--font-sans)",
|
||
color: "var(--text-primary)",
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap"
|
||
}
|
||
}, item.title), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 2,
|
||
font: "var(--text-2xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, item.type, " \xB7 ", item.library)), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
...mono,
|
||
font: "var(--text-2xs)/1 var(--font-mono)",
|
||
color: "var(--text-disabled)",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, fmtDur(item.min)), /*#__PURE__*/React.createElement(IconButton, {
|
||
size: "sm",
|
||
title: "Remove",
|
||
onClick: onRemove
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "X",
|
||
s: 15
|
||
})));
|
||
}
|
||
|
||
// ---- Channel image dropzone + live logo/bug preview -----------------------
|
||
function ImagePanel({
|
||
logoSrc,
|
||
setLogoSrc,
|
||
name
|
||
}) {
|
||
const [sep, setSep] = React.useState(false);
|
||
const [over, setOver] = React.useState(false);
|
||
const fileRef = React.useRef(null);
|
||
const read = file => {
|
||
if (!file) return;
|
||
const r = new FileReader();
|
||
r.onload = () => setLogoSrc(r.result);
|
||
r.readAsDataURL(file);
|
||
};
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 10
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
onDragOver: e => {
|
||
e.preventDefault();
|
||
setOver(true);
|
||
},
|
||
onDragLeave: () => setOver(false),
|
||
onDrop: e => {
|
||
e.preventDefault();
|
||
setOver(false);
|
||
read(e.dataTransfer.files && e.dataTransfer.files[0]);
|
||
},
|
||
onClick: () => fileRef.current && fileRef.current.click(),
|
||
style: {
|
||
display: "flex",
|
||
gap: 12,
|
||
padding: 12,
|
||
borderRadius: "var(--radius-sm)",
|
||
cursor: "pointer",
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: `1px dashed ${over ? "var(--ctv-accent)" : "var(--border-control)"}`
|
||
}
|
||
}, /*#__PURE__*/React.createElement(ChannelLogo, {
|
||
name: name || "New Channel",
|
||
src: logoSrc,
|
||
size: 52,
|
||
radius: "var(--radius-sm)"
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0,
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
justifyContent: "center",
|
||
gap: 3
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, logoSrc ? "Channel image set" : "Drop channel image"), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--text-2xs)/1.35 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, "Used as both the guide logo and the on-screen bug.")), /*#__PURE__*/React.createElement("input", {
|
||
ref: fileRef,
|
||
type: "file",
|
||
accept: "image/*",
|
||
style: {
|
||
display: "none"
|
||
},
|
||
onChange: e => read(e.target.files && e.target.files[0])
|
||
})), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "grid",
|
||
gridTemplateColumns: "1fr 1fr",
|
||
gap: 10
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
padding: 10,
|
||
borderRadius: "var(--radius-sm)",
|
||
background: "var(--surface-card)",
|
||
border: "1px solid var(--border-hairline)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
...eyebrow,
|
||
marginBottom: 8
|
||
}
|
||
}, "Guide logo"), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 9
|
||
}
|
||
}, /*#__PURE__*/React.createElement(ChannelLogo, {
|
||
name: name || "New Channel",
|
||
src: logoSrc,
|
||
size: 30,
|
||
radius: "var(--radius-xs)"
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
minWidth: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-2xs)/1.1 var(--font-sans)",
|
||
color: "var(--text-primary)",
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap"
|
||
}
|
||
}, name || "New Channel"), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
...mono,
|
||
font: "9px/1 var(--font-mono)",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, "listing")))), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
padding: 10,
|
||
borderRadius: "var(--radius-sm)",
|
||
background: "var(--surface-card)",
|
||
border: "1px solid var(--border-hairline)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
...eyebrow,
|
||
marginBottom: 8
|
||
}
|
||
}, "On-screen bug"), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "relative",
|
||
height: 42,
|
||
borderRadius: "var(--radius-xs)",
|
||
overflow: "hidden",
|
||
background: "linear-gradient(160deg,#1a1d24,#0e1014)",
|
||
backgroundImage: "repeating-linear-gradient(135deg, rgba(255,255,255,.03) 0 1px, transparent 1px 7px)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
position: "absolute",
|
||
bottom: 5,
|
||
right: 5,
|
||
opacity: 0.9
|
||
}
|
||
}, /*#__PURE__*/React.createElement(ChannelLogo, {
|
||
name: name || "New Channel",
|
||
src: logoSrc,
|
||
size: 22,
|
||
radius: "3px"
|
||
}))))), /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: () => setSep(s => !s),
|
||
style: {
|
||
alignSelf: "flex-start",
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: 5,
|
||
background: "none",
|
||
border: "none",
|
||
cursor: "pointer",
|
||
padding: "2px 0",
|
||
font: "var(--text-2xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: sep ? "ChevronDown" : "ChevronRight",
|
||
s: 13
|
||
}), "Set logo & bug separately"), sep && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "grid",
|
||
gridTemplateColumns: "1fr 1fr",
|
||
gap: 10,
|
||
padding: 10,
|
||
borderRadius: "var(--radius-sm)",
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: "1px solid var(--border-hairline)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Button, {
|
||
size: "sm",
|
||
variant: "secondary",
|
||
startIcon: /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Image",
|
||
s: 14
|
||
}),
|
||
onClick: () => fileRef.current && fileRef.current.click()
|
||
}, "Logo image"), /*#__PURE__*/React.createElement(Button, {
|
||
size: "sm",
|
||
variant: "secondary",
|
||
startIcon: /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Image",
|
||
s: 14
|
||
}),
|
||
onClick: () => fileRef.current && fileRef.current.click()
|
||
}, "Bug image")));
|
||
}
|
||
|
||
// ---- Advanced disclosure — one-click Override (edit) or View defaults ------
|
||
function Advanced({
|
||
templateName,
|
||
scrollerRef
|
||
}) {
|
||
const [mode, setMode] = React.useState("closed"); // closed | view | override
|
||
const [vals, setVals] = React.useState(() => {
|
||
const o = {};
|
||
ADV.forEach(g => g.fields.forEach(([k, v]) => {
|
||
o[k] = v;
|
||
}));
|
||
return o;
|
||
});
|
||
const override = mode === "override";
|
||
const count = ADV.reduce((n, g) => n + g.fields.length, 0);
|
||
const fieldsRef = React.useRef(null);
|
||
const rootRef = React.useRef(null);
|
||
const [maxH, setMaxH] = React.useState(0);
|
||
React.useLayoutEffect(() => {
|
||
if (mode === "closed") setMaxH(0);else if (fieldsRef.current) setMaxH(fieldsRef.current.scrollHeight);
|
||
}, [mode]);
|
||
const toggle = m => {
|
||
const next = mode === m ? "closed" : m;
|
||
setMode(next);
|
||
if (next !== "closed" && scrollerRef && scrollerRef.current && rootRef.current) {
|
||
const sc = scrollerRef.current;
|
||
requestAnimationFrame(() => {
|
||
const target = sc.scrollTop + rootRef.current.getBoundingClientRect().top - sc.getBoundingClientRect().top - 20;
|
||
sc.scrollTo({
|
||
top: Math.max(0, target),
|
||
behavior: "smooth"
|
||
});
|
||
});
|
||
}
|
||
};
|
||
const tabBtn = active => ({
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: 6,
|
||
padding: "8px 10px",
|
||
borderRadius: "var(--radius-sm)",
|
||
cursor: "pointer",
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
background: active ? "color-mix(in srgb, var(--action-primary) 14%, transparent)" : "var(--ctv-surface-2)",
|
||
color: active ? "var(--action-primary)" : "var(--text-secondary)",
|
||
border: `1px solid ${active ? "color-mix(in srgb, var(--action-primary) 38%, transparent)" : "var(--border-hairline)"}`,
|
||
transition: "background 200ms cubic-bezier(0.16,1,0.3,1), color 200ms cubic-bezier(0.16,1,0.3,1), border-color 200ms cubic-bezier(0.16,1,0.3,1)"
|
||
});
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
ref: rootRef,
|
||
style: {
|
||
borderTop: "1px solid var(--border-hairline)",
|
||
paddingTop: 14
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 9,
|
||
marginBottom: 10
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "SlidersHorizontal",
|
||
s: 16,
|
||
color: "var(--text-secondary)"
|
||
}), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-sm)/1 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, "Advanced"), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
flex: 1
|
||
}
|
||
}), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
...mono,
|
||
font: "var(--text-2xs)/1 var(--font-mono)",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, count, " fields \xB7 ", templateName)), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "grid",
|
||
gridTemplateColumns: "1fr 1fr",
|
||
gap: 8
|
||
}
|
||
}, /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: () => toggle("override"),
|
||
style: tabBtn(override)
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "SquarePen",
|
||
s: 14
|
||
}), override ? "Overriding" : "Override settings"), /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: () => toggle("view"),
|
||
style: tabBtn(mode === "view")
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Eye",
|
||
s: 14
|
||
}), "View defaults")), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
overflow: "hidden",
|
||
maxHeight: maxH,
|
||
transition: "max-height 340ms cubic-bezier(0.16,1,0.3,1)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
ref: fieldsRef,
|
||
style: {
|
||
marginTop: 12,
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 14
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 8,
|
||
font: "var(--text-2xs)/1.35 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Info",
|
||
s: 13,
|
||
color: override ? "var(--action-primary)" : "var(--text-disabled)"
|
||
}), override ? "Editing these overrides the template for this channel only." : `Read-only — inherited from the ${templateName} template. Turn on Override to edit.`), ADV.map(g => /*#__PURE__*/React.createElement("div", {
|
||
key: g.group
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
...eyebrow,
|
||
marginBottom: 8
|
||
}
|
||
}, g.group), override ? /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "grid",
|
||
gridTemplateColumns: "1fr 1fr",
|
||
gap: 8
|
||
}
|
||
}, g.fields.map(([k]) => /*#__PURE__*/React.createElement(Input, {
|
||
key: k,
|
||
size: "sm",
|
||
label: k,
|
||
value: vals[k],
|
||
onChange: e => setVals(s => ({
|
||
...s,
|
||
[k]: e.target.value
|
||
}))
|
||
}))) : /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "grid",
|
||
gridTemplateColumns: "1fr 1fr",
|
||
gap: 8
|
||
}
|
||
}, g.fields.map(([k, v]) => /*#__PURE__*/React.createElement("div", {
|
||
key: k,
|
||
style: {
|
||
padding: "7px 9px",
|
||
borderRadius: "var(--radius-xs)",
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: "1px solid var(--border-hairline)",
|
||
opacity: 0.72
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--text-2xs)/1 var(--font-sans)",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, k), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 3,
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
color: "var(--text-primary)",
|
||
...(/\d/.test(v) ? mono : {})
|
||
}
|
||
}, v)))))))));
|
||
}
|
||
|
||
// ---- Main ------------------------------------------------------------------
|
||
function ChannelBuilder() {
|
||
const [q, setQ] = React.useState("");
|
||
const [libFilter, setLibFilter] = React.useState("All");
|
||
const [compact, setCompact] = React.useState(false);
|
||
const [lineup, setLineup] = React.useState(() => ["s1", "s2", "s3", "s4"].map(id => LIB.find(x => x.id === id)));
|
||
const [name, setName] = React.useState("Retro Cartoons");
|
||
const [number, setNumber] = React.useState("13.1");
|
||
const [shuffle, setShuffle] = React.useState(false);
|
||
const [always, setAlways] = React.useState(true);
|
||
const [logoSrc, setLogoSrc] = React.useState(null);
|
||
const [tpl, setTpl] = React.useState("std");
|
||
const [tplOpen, setTplOpen] = React.useState(false);
|
||
const [source, setSource] = React.useState("library"); // library | collections
|
||
|
||
// drag state
|
||
const [dragLib, setDragLib] = React.useState(null); // library item id being dragged
|
||
const [dragRow, setDragRow] = React.useState(null); // lineup index being dragged
|
||
const [overRow, setOverRow] = React.useState(null);
|
||
const railScrollerRef = React.useRef(null);
|
||
const addedIds = new Set(lineup.map(i => i.id));
|
||
const pool = source === "collections" ? COLLECTIONS : LIB;
|
||
const filtered = pool.filter(i => (source === "collections" || libFilter === "All" || i.library === libFilter) && (!q || i.title.toLowerCase().includes(q.toLowerCase())));
|
||
const add = item => setLineup(l => l.some(x => x.id === item.id) ? l : [...l, item]);
|
||
const removeAt = idx => setLineup(l => l.filter((_, i) => i !== idx));
|
||
const totalMin = lineup.reduce((n, i) => n + i.min, 0);
|
||
const template = TEMPLATES.find(t => t.id === tpl);
|
||
const canCreate = name.trim() && lineup.length > 0;
|
||
|
||
// reorder / insert on drop at target index
|
||
const dropAt = targetIdx => {
|
||
if (dragRow != null) {
|
||
setLineup(l => {
|
||
const n = l.slice();
|
||
const [m] = n.splice(dragRow, 1);
|
||
n.splice(targetIdx, 0, m);
|
||
return n;
|
||
});
|
||
} else if (dragLib != null) {
|
||
const item = [...LIB, ...COLLECTIONS].find(x => x.id === dragLib);
|
||
if (item && !addedIds.has(item.id)) setLineup(l => {
|
||
const n = l.slice();
|
||
n.splice(targetIdx, 0, item);
|
||
return n;
|
||
});
|
||
}
|
||
setDragLib(null);
|
||
setDragRow(null);
|
||
setOverRow(null);
|
||
};
|
||
const dropEnd = () => {
|
||
if (dragLib != null) {
|
||
const item = [...LIB, ...COLLECTIONS].find(x => x.id === dragLib);
|
||
if (item) add(item);
|
||
}
|
||
setDragLib(null);
|
||
setDragRow(null);
|
||
setOverRow(null);
|
||
};
|
||
return /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
height: "100%"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 12,
|
||
padding: "12px 20px",
|
||
borderBottom: "1px solid var(--border-hairline)",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
width: 34,
|
||
height: 34,
|
||
borderRadius: "var(--radius-sm)",
|
||
background: "var(--ctv-accent-soft)",
|
||
color: "var(--ctv-accent)",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Plus",
|
||
s: 19
|
||
})), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--weight-semibold) var(--text-md)/1 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, "New Channel"), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 3,
|
||
font: "var(--text-xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, "Build from your library \u2014 ", /*#__PURE__*/React.createElement("span", {
|
||
style: mono
|
||
}, lineup.length), " in lineup \xB7 ", /*#__PURE__*/React.createElement("span", {
|
||
style: mono
|
||
}, fmtDur(totalMin)))), /*#__PURE__*/React.createElement(Button, {
|
||
variant: "ghost"
|
||
}, "Cancel"), /*#__PURE__*/React.createElement(Tooltip, {
|
||
placement: "bottom",
|
||
label: canCreate ? "Creates the channel, schedule & playout" : "Name the channel and add at least one item"
|
||
}, /*#__PURE__*/React.createElement(Button, {
|
||
variant: "primary",
|
||
disabled: !canCreate,
|
||
startIcon: /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Check",
|
||
s: 15
|
||
})
|
||
}, "Create Channel"))), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minHeight: 0,
|
||
display: "grid",
|
||
gridTemplateColumns: "minmax(0,1.2fr) minmax(0,1fr) 304px",
|
||
gap: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("section", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
minHeight: 0,
|
||
minWidth: 0,
|
||
overflow: "hidden",
|
||
borderRight: "1px solid var(--border-hairline)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
padding: "14px 16px 10px",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 10
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 10,
|
||
flexWrap: "wrap",
|
||
rowGap: 8
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Segmented, {
|
||
value: source,
|
||
onChange: setSource,
|
||
options: [{
|
||
value: "library",
|
||
label: "Library",
|
||
icon: "Library"
|
||
}, {
|
||
value: "collections",
|
||
label: "Collections",
|
||
icon: "FolderTree"
|
||
}]
|
||
}), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
flex: 1
|
||
}
|
||
}), /*#__PURE__*/React.createElement(Segmented, {
|
||
value: compact ? "list" : "grid",
|
||
onChange: v => setCompact(v === "list"),
|
||
options: [{
|
||
value: "grid",
|
||
icon: "LayoutGrid",
|
||
title: "Poster grid"
|
||
}, {
|
||
value: "list",
|
||
icon: "Rows3",
|
||
title: "Compact list"
|
||
}]
|
||
})), /*#__PURE__*/React.createElement(Input, {
|
||
size: "sm",
|
||
value: q,
|
||
onChange: e => setQ(e.target.value),
|
||
leadingIcon: /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Search",
|
||
s: 15
|
||
}),
|
||
placeholder: source === "collections" ? "Search collections\u2026" : "Search shows & movies\u2026"
|
||
}), source === "library" && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
gap: 6,
|
||
flexWrap: "wrap"
|
||
}
|
||
}, LIBRARIES.map(l => {
|
||
const on = libFilter === l;
|
||
return /*#__PURE__*/React.createElement("button", {
|
||
key: l,
|
||
type: "button",
|
||
onClick: () => setLibFilter(l),
|
||
className: "ctv-press",
|
||
style: {
|
||
padding: "4px 10px",
|
||
borderRadius: "var(--radius-pill)",
|
||
cursor: "pointer",
|
||
font: "var(--weight-medium) var(--text-2xs)/1.4 var(--font-sans)",
|
||
background: on ? "var(--ctv-accent-soft)" : "var(--ctv-surface-2)",
|
||
color: on ? "var(--ctv-accent)" : "var(--text-secondary)",
|
||
border: `1px solid ${on ? "rgba(224,138,60,.35)" : "var(--border-hairline)"}`
|
||
}
|
||
}, l);
|
||
}))), /*#__PURE__*/React.createElement(ScrollArea, {
|
||
innerStyle: {
|
||
padding: "4px 16px 16px"
|
||
}
|
||
}, filtered.length === 0 ? /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
padding: "40px 0",
|
||
textAlign: "center",
|
||
font: "var(--text-xs)/1.5 var(--font-sans)",
|
||
color: "var(--text-disabled)"
|
||
}
|
||
}, "No titles match.") : compact ? /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 6
|
||
}
|
||
}, filtered.map(item => /*#__PURE__*/React.createElement(LibCard, {
|
||
key: item.id,
|
||
item: item,
|
||
compact: true,
|
||
added: addedIds.has(item.id),
|
||
onAdd: () => add(item),
|
||
onDragStart: () => setDragLib(item.id),
|
||
onDragEnd: dropEnd
|
||
}))) : /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "grid",
|
||
gridTemplateColumns: "repeat(auto-fill, minmax(104px,1fr))",
|
||
gap: 10
|
||
}
|
||
}, filtered.map(item => /*#__PURE__*/React.createElement(LibCard, {
|
||
key: item.id,
|
||
item: item,
|
||
added: addedIds.has(item.id),
|
||
onAdd: () => add(item),
|
||
onDragStart: () => setDragLib(item.id),
|
||
onDragEnd: dropEnd
|
||
}))))), /*#__PURE__*/React.createElement("section", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
minHeight: 0,
|
||
minWidth: 0,
|
||
overflow: "hidden",
|
||
borderRight: "1px solid var(--border-hairline)"
|
||
},
|
||
onDragOver: e => {
|
||
if (dragLib != null) e.preventDefault();
|
||
},
|
||
onDrop: () => {
|
||
if (dragLib != null) dropEnd();
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
padding: "14px 16px 10px",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 10
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: eyebrow
|
||
}, "Lineup"), /*#__PURE__*/React.createElement(Badge, {
|
||
tone: shuffle ? "accent" : "neutral"
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: 4
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: shuffle ? "Shuffle" : "ListOrdered",
|
||
s: 11
|
||
}), shuffle ? "Shuffled" : "Sequential")), /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
flex: 1
|
||
}
|
||
}), lineup.length > 0 && /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: () => setLineup([]),
|
||
style: {
|
||
background: "none",
|
||
border: "none",
|
||
cursor: "pointer",
|
||
font: "var(--text-2xs)/1 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, "Clear")), /*#__PURE__*/React.createElement(ScrollArea, {
|
||
innerStyle: {
|
||
padding: "4px 16px 16px"
|
||
}
|
||
}, lineup.length === 0 ? /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
height: "100%",
|
||
minHeight: 220,
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: 12,
|
||
textAlign: "center",
|
||
border: `1px dashed ${dragLib != null ? "var(--ctv-accent)" : "var(--border-control)"}`,
|
||
borderRadius: "var(--radius-md)",
|
||
background: dragLib != null ? "var(--ctv-accent-soft)" : "transparent",
|
||
color: "var(--text-disabled)",
|
||
padding: 24
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "ListVideo",
|
||
s: 26
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
font: "var(--text-sm)/1.5 var(--font-sans)",
|
||
maxWidth: 240
|
||
}
|
||
}, "This channel has no content yet. Double-click or drag titles from the library to build the lineup.")) : /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 7
|
||
}
|
||
}, lineup.map((item, idx) => /*#__PURE__*/React.createElement(LineupRow, {
|
||
key: item.id + "-" + idx,
|
||
item: item,
|
||
index: idx,
|
||
shuffle: shuffle,
|
||
dragging: dragRow === idx,
|
||
isOver: overRow === idx && dragRow !== idx,
|
||
onRemove: () => removeAt(idx),
|
||
dragProps: {
|
||
draggable: true,
|
||
onDragStart: () => setDragRow(idx),
|
||
onDragEnd: () => {
|
||
setDragRow(null);
|
||
setOverRow(null);
|
||
},
|
||
onDragOver: e => {
|
||
e.preventDefault();
|
||
setOverRow(idx);
|
||
},
|
||
onDrop: e => {
|
||
e.stopPropagation();
|
||
dropAt(idx);
|
||
}
|
||
}
|
||
})), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 4,
|
||
padding: "10px 12px",
|
||
borderRadius: "var(--radius-md)",
|
||
border: "1px dashed var(--border-hairline)",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 8,
|
||
color: "var(--text-disabled)",
|
||
font: "var(--text-2xs)/1 var(--font-sans)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Plus",
|
||
s: 14
|
||
}), "Double-click or drag more titles to add")))), /*#__PURE__*/React.createElement("aside", {
|
||
style: {
|
||
position: "relative",
|
||
minWidth: 0,
|
||
overflow: "hidden",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
background: "var(--surface-card)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(ScrollArea, {
|
||
scrollerRef: railScrollerRef,
|
||
innerStyle: {
|
||
padding: "14px 16px 24px",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 16
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 10
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Input, {
|
||
label: "Channel name",
|
||
value: name,
|
||
onChange: e => setName(e.target.value),
|
||
placeholder: "e.g. Retro Cartoons"
|
||
}), /*#__PURE__*/React.createElement(Input, {
|
||
label: "Number",
|
||
value: number,
|
||
onChange: e => setNumber(e.target.value),
|
||
trailing: /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
...mono,
|
||
font: "9px/1 var(--font-mono)",
|
||
padding: "2px 5px",
|
||
borderRadius: "3px",
|
||
background: "var(--ctv-accent-soft)",
|
||
color: "var(--ctv-accent)"
|
||
}
|
||
}, "AUTO")
|
||
})), /*#__PURE__*/React.createElement(ImagePanel, {
|
||
logoSrc: logoSrc,
|
||
setLogoSrc: setLogoSrc,
|
||
name: name
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 4
|
||
}
|
||
}, /*#__PURE__*/React.createElement("label", {
|
||
onClick: () => setShuffle(s => !s),
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "flex-start",
|
||
gap: 11,
|
||
padding: "10px 0",
|
||
cursor: "pointer"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 7
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-sm)/1.2 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, "Shuffle"), shuffle !== template.shuffle && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--text-2xs)/1 var(--font-sans)",
|
||
color: "var(--text-disabled)",
|
||
padding: "2px 6px",
|
||
borderRadius: "var(--radius-xs)",
|
||
background: "var(--ctv-surface-2)",
|
||
border: "1px solid var(--border-hairline)"
|
||
}
|
||
}, "overrides template")), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 2,
|
||
font: "var(--text-2xs)/1.35 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, shuffle ? "Plays in a random order." : "Plays in sequence (chronological).")), /*#__PURE__*/React.createElement(Switch, {
|
||
checked: shuffle,
|
||
onChange: setShuffle
|
||
})), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
height: 1,
|
||
background: "var(--border-hairline)"
|
||
}
|
||
}), /*#__PURE__*/React.createElement("label", {
|
||
onClick: () => setAlways(s => !s),
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "flex-start",
|
||
gap: 10,
|
||
padding: "10px 11px",
|
||
borderRadius: "var(--radius-sm)",
|
||
cursor: "pointer",
|
||
background: always ? "var(--ctv-live-soft)" : "transparent",
|
||
border: `1px solid ${always ? "var(--ctv-live)" : "transparent"}`,
|
||
transition: "background 240ms cubic-bezier(0.16,1,0.3,1), border-color 240ms cubic-bezier(0.16,1,0.3,1)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
marginTop: 1,
|
||
color: "var(--ctv-live)",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Radio",
|
||
s: 16
|
||
})), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 7
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-sm)/1.2 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, "Always playing"), always !== template.always && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--text-2xs)/1 var(--font-sans)",
|
||
color: "var(--text-disabled)",
|
||
padding: "2px 6px",
|
||
borderRadius: "var(--radius-xs)",
|
||
background: "var(--ctv-surface-2)",
|
||
border: "1px solid var(--border-hairline)"
|
||
}
|
||
}, "overrides template"), always && /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: 4,
|
||
padding: "2px 7px",
|
||
borderRadius: "var(--radius-pill)",
|
||
background: "var(--ctv-live-soft)",
|
||
color: "var(--ctv-live)",
|
||
font: "var(--weight-semibold) 9px/1.4 var(--font-sans)",
|
||
letterSpacing: ".06em"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
width: 5,
|
||
height: 5,
|
||
borderRadius: "50%",
|
||
background: "var(--ctv-live)"
|
||
}
|
||
}), "LIVE")), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 3,
|
||
font: "var(--text-2xs)/1.35 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, "Like live TV \u2014 advances on schedule even when nobody is watching.")), /*#__PURE__*/React.createElement(Switch, {
|
||
checked: always,
|
||
onChange: setAlways
|
||
}))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
...eyebrow,
|
||
marginBottom: 8
|
||
}
|
||
}, "Channel Template"), /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
onClick: () => setTplOpen(o => !o),
|
||
className: "ctv-press",
|
||
style: {
|
||
width: "100%",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 10,
|
||
padding: "10px 11px",
|
||
borderRadius: "var(--radius-sm)",
|
||
cursor: "pointer",
|
||
textAlign: "left",
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: "1px solid var(--border-control)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
width: 30,
|
||
height: 30,
|
||
borderRadius: "var(--radius-xs)",
|
||
background: "var(--ctv-surface-3)",
|
||
color: "var(--ctv-accent)",
|
||
flex: "0 0 auto"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "SlidersHorizontal",
|
||
s: 15
|
||
})), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 6
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-sm)/1 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, template.name), !template.builtin && /*#__PURE__*/React.createElement(Badge, {
|
||
tone: "neutral"
|
||
}, "Custom")), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 3,
|
||
font: "var(--text-2xs)/1.3 var(--font-sans)",
|
||
color: "var(--text-secondary)",
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap"
|
||
}
|
||
}, template.desc)), /*#__PURE__*/React.createElement(Ico, {
|
||
n: tplOpen ? "ChevronUp" : "ChevronDown",
|
||
s: 15,
|
||
color: "var(--text-disabled)"
|
||
})), tplOpen && /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 6,
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 4,
|
||
padding: 4,
|
||
borderRadius: "var(--radius-sm)",
|
||
background: "var(--ctv-bg-sunken)",
|
||
border: "1px solid var(--border-hairline)"
|
||
}
|
||
}, TEMPLATES.map(t => {
|
||
const on = t.id === tpl;
|
||
return /*#__PURE__*/React.createElement("button", {
|
||
key: t.id,
|
||
type: "button",
|
||
className: "ctv-press",
|
||
onClick: () => {
|
||
setTpl(t.id);
|
||
setTplOpen(false);
|
||
setShuffle(t.shuffle);
|
||
setAlways(t.always);
|
||
},
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 9,
|
||
padding: "8px 9px",
|
||
borderRadius: "var(--radius-xs)",
|
||
cursor: "pointer",
|
||
textAlign: "left",
|
||
background: on ? "color-mix(in srgb, var(--action-primary) 14%, transparent)" : "transparent",
|
||
border: "none"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: on ? "CircleCheck" : "Circle",
|
||
s: 15,
|
||
color: on ? "var(--action-primary)" : "var(--text-disabled)"
|
||
}), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
flex: 1,
|
||
minWidth: 0
|
||
}
|
||
}, /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 6
|
||
}
|
||
}, /*#__PURE__*/React.createElement("span", {
|
||
style: {
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)",
|
||
color: "var(--text-primary)"
|
||
}
|
||
}, t.name), !t.builtin && /*#__PURE__*/React.createElement(Badge, {
|
||
tone: "neutral"
|
||
}, "Custom")), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 2,
|
||
font: "var(--text-2xs)/1.3 var(--font-sans)",
|
||
color: "var(--text-secondary)"
|
||
}
|
||
}, t.desc)));
|
||
}), /*#__PURE__*/React.createElement("button", {
|
||
type: "button",
|
||
style: {
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 7,
|
||
padding: "8px 9px",
|
||
borderRadius: "var(--radius-xs)",
|
||
cursor: "pointer",
|
||
background: "none",
|
||
border: "none",
|
||
color: "var(--text-secondary)",
|
||
font: "var(--weight-medium) var(--text-xs)/1 var(--font-sans)"
|
||
}
|
||
}, /*#__PURE__*/React.createElement(Ico, {
|
||
n: "Save",
|
||
s: 14
|
||
}), "Save current settings as template\u2026")), /*#__PURE__*/React.createElement("div", {
|
||
style: {
|
||
marginTop: 8,
|
||
display: "flex",
|
||
flexWrap: "wrap",
|
||
gap: 5
|
||
}
|
||
}, template.sets.map(s => /*#__PURE__*/React.createElement("span", {
|
||
key: s,
|
||
style: {
|
||
padding: "3px 8px",
|
||
borderRadius: "var(--radius-xs)",
|
||
background: "var(--ctv-surface-2)",
|
||
border: "1px solid var(--border-hairline)",
|
||
font: "var(--text-2xs)/1.4 var(--font-sans)",
|
||
color: "var(--text-secondary)",
|
||
...(/\d/.test(s) ? mono : {})
|
||
}
|
||
}, s)))), /*#__PURE__*/React.createElement(Advanced, {
|
||
templateName: template.name,
|
||
scrollerRef: railScrollerRef
|
||
})))));
|
||
}
|
||
window.CTVChannelBuilder = ChannelBuilder;
|
||
})();
|
||
})(); } catch (e) { __ds_ns.__errors.push({ path: "design_handoff_channel_builder/ChannelBuilder.jsx", error: String((e && e.message) || e) }); }
|
||
|
||
__ds_ns.Badge = __ds_scope.Badge;
|
||
|
||
__ds_ns.Card = __ds_scope.Card;
|
||
|
||
__ds_ns.ChannelLogo = __ds_scope.ChannelLogo;
|
||
|
||
__ds_ns.ProgressBar = __ds_scope.ProgressBar;
|
||
|
||
__ds_ns.Stat = __ds_scope.Stat;
|
||
|
||
__ds_ns.StatusDot = __ds_scope.StatusDot;
|
||
|
||
__ds_ns.Tag = __ds_scope.Tag;
|
||
|
||
__ds_ns.Spinner = __ds_scope.Spinner;
|
||
|
||
__ds_ns.Toast = __ds_scope.Toast;
|
||
|
||
__ds_ns.Tooltip = __ds_scope.Tooltip;
|
||
|
||
__ds_ns.Button = __ds_scope.Button;
|
||
|
||
__ds_ns.Checkbox = __ds_scope.Checkbox;
|
||
|
||
__ds_ns.IconButton = __ds_scope.IconButton;
|
||
|
||
__ds_ns.Input = __ds_scope.Input;
|
||
|
||
__ds_ns.Select = __ds_scope.Select;
|
||
|
||
__ds_ns.Switch = __ds_scope.Switch;
|
||
|
||
__ds_ns.NavItem = __ds_scope.NavItem;
|
||
|
||
__ds_ns.NavSection = __ds_scope.NavSection;
|
||
|
||
__ds_ns.Tabs = __ds_scope.Tabs;
|
||
|
||
})();
|