/* @jsx React.createElement */ const { useState, useEffect, useRef, useMemo } = React; /* ============================================================ Yappari — SIGNAGE (POS screen: fullscreen player + kiosk layout) ============================================================ */ function fmtDur(s) { if (!s) return '\u2014'; const m = Math.floor(s / 60), ss = s % 60; return m > 0 ? m + ':' + String(ss).padStart(2, '0') : ss + ' dtk'; } // drawn poster art for a media item (placeholder creative) function MediaArt({ media, big }) { const known = ['bottle', 'can', 'carton', 'cupnoodle', 'bag', 'bar']; let inner; if (media.glyph === 'blossom') inner = React.createElement(PlumBlossom, { size: big ? 120 : 52, color: '#fff' }); else if (media.glyph === 'qr') { const cells = []; const pat = [1,1,1,0,1,0,1, 1,0,1,1,0,1,1, 1,1,0,0,1,1,0, 0,1,1,1,0,0,1, 1,0,0,1,1,1,0, 0,1,1,0,1,0,1, 1,0,1,1,0,1,1]; for (let i = 0; i < 49; i++) cells.push(React.createElement('div', { key: i, style: { background: pat[i] ? '#fff' : 'transparent', borderRadius: 1 } })); inner = React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', gap: 2, width: big ? 96 : 40, height: big ? 96 : 40 } }, cells); } else if (known.includes(media.glyph)) inner = React.createElement(ProductGlyph, { glyph: media.glyph, color: '#fff', size: big ? 120 : 46, radius: 12 }); else inner = React.createElement('div', { style: { fontFamily: 'var(--font-display)', fontSize: big ? 44 : 18, color: '#fff', letterSpacing: '.04em' } }, 'PROMO'); return React.createElement('div', { style: { position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', background: 'linear-gradient(150deg,' + media.color + ',' + media.color + 'cc 60%, rgba(0,0,0,.5))' } }, React.createElement('div', { style: { filter: 'drop-shadow(0 4px 12px rgba(0,0,0,.3))' } }, inner)); } // ---- POS product grid (touchscreen buy UI) ---- function PosGrid({ scale }) { const Y = window.YAPPARI; const prods = Y.PRODUCTS.slice(0, 8); const [sel, setSel] = useState(null); const s = scale || 1; return React.createElement('div', { style: { position: 'absolute', inset: 0, background: 'var(--bg)', display: 'flex', flexDirection: 'column' } }, React.createElement('div', { style: { padding: 6 * s + 'px ' + 8 * s + 'px', display: 'flex', alignItems: 'center', gap: 6 * s, borderBottom: '1px solid var(--border-subtle)' } }, React.createElement(PlumBlossom, { size: 14 * s, color: '#004080' }), React.createElement('span', { style: { fontWeight: 800, fontSize: 10 * s, color: 'var(--fg-1)' } }, 'Pilih Produk'), React.createElement('span', { className: 'jp', style: { fontSize: 8 * s, color: 'var(--fg-3)', marginLeft: 'auto' } }, '\u3084\u3063\u3071\u308a!')), React.createElement('div', { style: { flex: 1, overflow: 'hidden', padding: 6 * s, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 5 * s, alignContent: 'start' } }, prods.map(p => { const on = sel === p.id; return React.createElement('button', { key: p.id, onClick: () => setSel(p.id), style: { border: '1px solid ' + (on ? 'var(--accent)' : 'var(--border-subtle)'), borderRadius: 6 * s, background: on ? 'var(--st-warn-bg)' : 'var(--bg-elev)', padding: 4 * s, display: 'flex', alignItems: 'center', gap: 5 * s, cursor: 'pointer', boxShadow: on ? '0 0 0 2px rgba(240,64,0,.2)' : 'none', overflow: 'hidden' } }, React.createElement(ProductGlyph, { glyph: p.glyph, color: p.color, size: 22 * s, radius: 4 }), React.createElement('div', { style: { minWidth: 0, textAlign: 'left' } }, React.createElement('div', { style: { fontSize: 8.5 * s, fontWeight: 700, color: 'var(--fg-1)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, p.name), React.createElement('div', { style: { fontSize: 9 * s, fontWeight: 800, color: 'var(--accent)' } }, fmtRpShort(p.price)))); })), ); } // ---- composed kiosk screen: header (16:9) + POS + footer strip ---- function KioskScreen({ headerMedia, footerMedia }) { const w = 248, h = 432; const headerH = Math.round(w * 9 / 16); const footerH = Math.round(w / 6); return React.createElement('div', { className: 'screen-bezel', style: { width: w + 26 } }, React.createElement('div', { className: 'screen-glass', style: { width: w, height: h, display: 'flex', flexDirection: 'column' } }, React.createElement('div', { style: { position: 'relative', height: headerH, flexShrink: 0, overflow: 'hidden', borderBottom: '2px solid rgba(0,0,0,.3)' } }, headerMedia ? React.createElement('div', { key: headerMedia.id, style: { position: 'absolute', inset: 0, animation: 'fadeIn .5s var(--ease-out)' } }, React.createElement(MediaArt, { media: headerMedia, big: true })) : null, React.createElement('span', { style: { position: 'absolute', top: 5, left: 6, fontSize: 9, fontWeight: 800, color: 'rgba(255,255,255,.85)', letterSpacing: '.1em', textShadow: '0 1px 2px rgba(0,0,0,.4)' } }, 'IKLAN 16:9')), React.createElement('div', { style: { position: 'relative', flex: 1, overflow: 'hidden' } }, React.createElement(PosGrid, { scale: 1 })), React.createElement('div', { style: { position: 'relative', height: footerH, flexShrink: 0, overflow: 'hidden', borderTop: '2px solid rgba(0,0,0,.3)' } }, footerMedia ? React.createElement('div', { key: footerMedia.id, style: { position: 'absolute', inset: 0, animation: 'fadeIn .5s var(--ease-out)' } }, React.createElement(MediaArt, { media: footerMedia })) : null, React.createElement('span', { style: { position: 'absolute', top: '50%', left: 8, transform: 'translateY(-50%)', fontSize: 8.5, fontWeight: 800, color: 'rgba(255,255,255,.9)', letterSpacing: '.08em' } }, 'FOOTER')), ), ); } function ScreenPreview({ media, playing, prog, setPlaying, orient, settings, idx, total, onPrev, onNext }) { const portrait = orient === 'portrait'; const w = portrait ? 224 : 360, h = portrait ? 398 : 202; return React.createElement('div', { className: 'screen-bezel', style: { width: w + 28 } }, React.createElement('div', { className: 'screen-glass', style: { width: w, height: h, margin: '0 auto' } }, media ? React.createElement(React.Fragment, null, React.createElement('div', { key: media.id, style: { position: 'absolute', inset: 0, animation: 'fadeIn .5s var(--ease-out)' } }, React.createElement(MediaArt, { media, big: true })), React.createElement('div', { style: { position: 'absolute', top: 0, left: 0, right: 0, padding: '10px 12px', background: 'linear-gradient(180deg,rgba(0,0,0,.5),transparent)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, React.createElement('span', { className: 'row gap-1', style: { color: '#fff', fontSize: 11, fontWeight: 700 } }, React.createElement('span', { style: { width: 7, height: 7, borderRadius: '50%', background: media.type === 'video' ? '#FF5A5A' : '#4ADE80' } }), media.type === 'video' ? 'VIDEO' : 'GAMBAR'), React.createElement('span', { style: { color: 'rgba(255,255,255,.85)', fontSize: 11, fontWeight: 700, fontFamily: 'var(--font-mono)' } }, (idx + 1) + '/' + total)), settings.priceOverlay && React.createElement('div', { style: { position: 'absolute', bottom: 26, left: 0, right: 0, textAlign: 'center' } }, React.createElement('div', { style: { display: 'inline-block', background: 'rgba(0,20,38,.55)', backdropFilter: 'blur(4px)', color: '#fff', padding: '6px 14px', borderRadius: 999, fontWeight: 800, fontSize: 13 } }, media.name.split(' \u2014 ')[0])), React.createElement('div', { style: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 5, background: 'rgba(255,255,255,.25)' } }, React.createElement('div', { style: { height: '100%', width: (prog * 100) + '%', background: '#fff' } })), ) : React.createElement('div', { style: { position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', color: 'rgba(255,255,255,.5)', fontSize: 13 } }, 'Playlist kosong'), ), ); } function SignageView({ machines, selectedMachine }) { const Y = window.YAPPARI; const mediaMap = Object.fromEntries(Y.MEDIA.map(m => [m.id, m])); const [layoutMode, setLayoutMode] = useState('fullscreen'); // fullscreen | kiosk const [playlist, setPlaylist] = useState(Y.DEFAULT_PLAYLIST.slice()); const [enabled, setEnabled] = useState(() => Object.fromEntries(Y.MEDIA.map(m => [m.id, Y.DEFAULT_PLAYLIST.includes(m.id)]))); const [headerList, setHeaderList] = useState(['md07', 'md03', 'md05']); const [footerList, setFooterList] = useState(['md12', 'md05']); const [zone, setZone] = useState('header'); const [settings, setSettings] = useState({ orient: 'portrait', transition: 'fade', imgDur: 8, loop: true, mute: true, priceOverlay: true, start: '07:00', end: '22:00' }); const [query, setQuery] = useState(''); const [typeF, setTypeF] = useState('all'); const setS = (k, v) => setSettings(s => ({ ...s, [k]: v })); // fullscreen player const seq = playlist.filter(id => enabled[id]); const total = seq.length || 1; const [playing, setPlaying] = useState(true); const [idx, setIdx] = useState(0); const [prog, setProg] = useState(0); const curId = seq[idx % total]; const cur = curId ? mediaMap[curId] : null; useEffect(() => { if (layoutMode !== 'fullscreen' || !playing || !cur) return; const secs = Math.max(3, Math.min(8, (cur.dur || settings.imgDur) * 0.4)) * 1000; let raf, start; const step = (t) => { if (!start) start = t; const p = Math.min(1, (t - start) / secs); setProg(p); if (p < 1) raf = requestAnimationFrame(step); else { setProg(0); setIdx(i => (i + 1) % total); } }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [playing, curId, total, layoutMode]); // kiosk header/footer cyclers const [hIdx, setHIdx] = useState(0), [fIdx, setFIdx] = useState(0); useEffect(() => { if (layoutMode !== 'kiosk') return; const id = setInterval(() => setHIdx(i => i + 1), 4000); return () => clearInterval(id); }, [layoutMode, headerList.length]); useEffect(() => { if (layoutMode !== 'kiosk') return; const id = setInterval(() => setFIdx(i => i + 1), 6000); return () => clearInterval(id); }, [layoutMode, footerList.length]); const headerCur = headerList.length ? mediaMap[headerList[hIdx % headerList.length]] : null; const footerCur = footerList.length ? mediaMap[footerList[fIdx % footerList.length]] : null; // active list helpers (which list the library adds to) const target = layoutMode === 'fullscreen' ? 'full' : zone; const listFor = (t) => t === 'full' ? playlist : t === 'header' ? headerList : footerList; const setListFor = (t, fn) => { if (t === 'full') setPlaylist(fn); else if (t === 'header') setHeaderList(fn); else setFooterList(fn); }; const move = (t, id, dir) => setListFor(t, pl => { const i = pl.indexOf(id), j = i + dir; if (j < 0 || j >= pl.length) return pl; const n = pl.slice(); [n[i], n[j]] = [n[j], n[i]]; return n; }); const removeFrom = (t, id) => { setListFor(t, pl => pl.filter(x => x !== id)); if (t === 'full') setEnabled(e => ({ ...e, [id]: false })); }; const addTo = (t, id) => { setListFor(t, pl => pl.includes(id) ? pl : [...pl, id]); if (t === 'full') setEnabled(e => ({ ...e, [id]: true })); }; const totalDur = seq.reduce((a, id) => a + (mediaMap[id].dur || settings.imgDur), 0); const lib = Y.MEDIA.filter(m => (typeF === 'all' || m.type === typeF) && m.name.toLowerCase().includes(query.toLowerCase())); const mObj = machines.find(m => m.id === selectedMachine); // generic small list row const listRow = (t, id, i, list) => { const m = mediaMap[id]; const on = t === 'full' ? enabled[id] : true; const isNow = (t === 'full' && curId === id && on) || (t === 'header' && headerCur && headerCur.id === id) || (t === 'footer' && footerCur && footerCur.id === id); return React.createElement('div', { key: id, className: 'playlist-row' + (isNow ? ' playing' : ''), style: { opacity: on ? 1 : 0.5 } }, React.createElement('div', { className: 'col', style: { gap: 0 } }, React.createElement('button', { className: 'drag-handle', style: { background: 'none', border: 'none', padding: 0, height: 16 }, disabled: i === 0, onClick: () => move(t, id, -1) }, React.createElement(Icon, { name: 'chevronUp', size: 14 })), React.createElement('button', { className: 'drag-handle', style: { background: 'none', border: 'none', padding: 0, height: 16 }, disabled: i === list.length - 1, onClick: () => move(t, id, 1) }, React.createElement(Icon, { name: 'chevronDown', size: 14 }))), React.createElement('div', { style: { position: 'relative', width: 46, height: 46, borderRadius: 8, overflow: 'hidden', flexShrink: 0 } }, React.createElement(MediaArt, { media: m })), React.createElement('div', { style: { flex: 1, minWidth: 0 } }, React.createElement('div', { className: 'row gap-2' }, isNow && React.createElement('span', { style: { width: 7, height: 7, borderRadius: '50%', background: 'var(--st-online)', flexShrink: 0 } }), React.createElement('span', { style: { fontWeight: 700, fontSize: 13.5, color: 'var(--fg-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, m.name)), React.createElement('div', { className: 'row gap-2', style: { marginTop: 3 } }, React.createElement('span', { className: 'badge ' + (m.type === 'video' ? 'badge-error' : 'badge-info'), style: { padding: '1px 7px', fontSize: 10.5 } }, m.type === 'video' ? 'Video' : 'Gambar'), React.createElement('span', { className: 'muted', style: { fontSize: 12 } }, fmtDur(m.dur || settings.imgDur) + ' \u00b7 ' + m.orient))), t === 'full' && React.createElement('button', { className: 'toggle ' + (on ? 'on' : ''), onClick: () => setEnabled(e => ({ ...e, [id]: !e[id] })) }), React.createElement('button', { className: 'icon-btn', style: { width: 32, height: 32, color: 'var(--st-error)' }, onClick: () => removeFrom(t, id) }, React.createElement(Icon, { name: 'trash', size: 15 }))); }; const curList = listFor(target); return React.createElement('div', { className: 'view' }, React.createElement('div', { className: 'l-split l-plano' }, // ---- MAIN ---- React.createElement('div', { className: 'col gap-5' }, // mode selector banner React.createElement('div', { className: 'card card-pad', style: { display: 'flex', alignItems: 'center', gap: 'var(--sp-4)', flexWrap: 'wrap' } }, React.createElement('div', { className: 'row gap-2', style: { fontWeight: 700, fontSize: 13, color: 'var(--fg-2)' } }, React.createElement(Icon, { name: 'screen', size: 16 }), 'Mode tampilan layar'), React.createElement('div', { className: 'row gap-2' }, React.createElement('button', { className: 'chip ' + (layoutMode === 'fullscreen' ? 'active' : ''), onClick: () => setLayoutMode('fullscreen') }, React.createElement(Icon, { name: 'play', size: 13 }), 'Fullscreen'), React.createElement('button', { className: 'chip ' + (layoutMode === 'kiosk' ? 'active' : ''), onClick: () => setLayoutMode('kiosk') }, React.createElement(Icon, { name: 'layers', size: 13 }), 'Kiosk (Header + POS + Footer)')), React.createElement('span', { className: 'muted', style: { fontSize: 12.5, marginLeft: 'auto', maxWidth: 360 } }, layoutMode === 'fullscreen' ? 'Satu media menutupi seluruh layar.' : 'Header iklan 16:9, grid pilih produk di tengah, footer strip di bawah.')), layoutMode === 'fullscreen' ? React.createElement(SectionCard, { title: 'Playlist fullscreen', sub: seq.length + ' media \u00b7 durasi loop ' + fmtDur(totalDur), motif: true, right: React.createElement('span', { className: 'badge ' + (playing ? 'badge-online' : 'badge-soft') }, playing && React.createElement('span', { className: 'b-dot', style: { animation: 'ping 2s infinite' } }), playing ? 'Tayang' : 'Jeda'), bodyStyle: { padding: 'var(--sp-4)' }, }, React.createElement('div', { className: 'col gap-2' }, playlist.length === 0 && React.createElement('div', { style: { padding: 24, textAlign: 'center', color: 'var(--fg-3)' } }, 'Belum ada media. Tambahkan dari pustaka.'), playlist.map((id, i) => listRow('full', id, i, playlist)))) : React.createElement(SectionCard, { title: 'Media per zona', sub: 'Atur media tiap bagian layar kiosk', motif: true, right: React.createElement('div', { className: 'row gap-2' }, React.createElement('button', { className: 'chip ' + (zone === 'header' ? 'active' : ''), onClick: () => setZone('header') }, 'Header 16:9'), React.createElement('button', { className: 'chip ' + (zone === 'footer' ? 'active' : ''), onClick: () => setZone('footer') }, 'Footer')), bodyStyle: { padding: 'var(--sp-4)' }, }, React.createElement('div', { style: { padding: '0 0 10px', fontSize: 12.5, color: 'var(--fg-3)' } }, zone === 'header' ? 'Banner iklan rasio 16:9 di atas \u2014 disarankan media landscape.' : 'Strip tipis di bawah \u2014 cocok untuk brand bar / promo singkat.'), React.createElement('div', { className: 'col gap-2' }, curList.length === 0 && React.createElement('div', { style: { padding: 24, textAlign: 'center', color: 'var(--fg-3)' } }, 'Belum ada media untuk zona ini.'), curList.map((id, i) => listRow(zone, id, i, curList))), React.createElement('div', { style: { marginTop: 12, padding: '10px 12px', borderRadius: 'var(--r-2)', background: 'var(--st-maint-bg)', color: 'var(--fg-2)', fontSize: 12.5, display: 'flex', gap: 8, alignItems: 'center' } }, React.createElement(Icon, { name: 'cube', size: 15, style: { color: 'var(--st-maint)' } }), 'Area POS di tengah otomatis menampilkan grid produk aktif mesin.')), // library (shared) React.createElement(SectionCard, { title: 'Pustaka media', sub: lib.length + ' aset \u00b7 klik untuk tambah ke ' + (target === 'full' ? 'playlist' : target === 'header' ? 'header' : 'footer'), right: React.createElement('div', { className: 'row gap-2' }, React.createElement('button', { className: 'btn btn-ghost btn-sm' }, React.createElement(Icon, { name: 'upload', size: 15 }), 'Unggah'), React.createElement('button', { className: 'btn btn-primary btn-sm' }, React.createElement(Icon, { name: 'plus', size: 15 }), 'Tambah')), bodyStyle: { padding: 'var(--sp-5)' }, }, React.createElement('div', { className: 'row gap-3', style: { marginBottom: 16, flexWrap: 'wrap' } }, React.createElement('div', { style: { position: 'relative', flex: 1, minWidth: 180 } }, React.createElement(Icon, { name: 'search', size: 16, style: { position: 'absolute', left: 11, top: 10, color: 'var(--fg-3)' } }), React.createElement('input', { className: 'input', style: { paddingLeft: 36 }, placeholder: 'Cari media\u2026', value: query, onChange: e => setQuery(e.target.value) })), React.createElement('div', { className: 'row gap-2' }, ['all', 'video', 'image'].map(t => React.createElement('button', { key: t, className: 'chip ' + (typeF === t ? 'active' : ''), onClick: () => setTypeF(t) }, t === 'all' ? 'Semua' : t === 'video' ? 'Video' : 'Gambar')))), React.createElement('div', { className: 'media-grid' }, lib.map(m => { const inList = curList.includes(m.id); return React.createElement('button', { key: m.id, className: 'media-tile', onClick: () => addTo(target, m.id) }, React.createElement('div', { className: 'media-thumb', style: { aspectRatio: m.orient === 'portrait' ? '3/4' : '16/9' } }, React.createElement(MediaArt, { media: m }), m.type === 'video' && React.createElement('div', { className: 'media-play' }, React.createElement(Icon, { name: 'play', size: 18 })), inList && React.createElement('span', { style: { position: 'absolute', top: 6, right: 6, width: 22, height: 22, borderRadius: '50%', background: 'var(--st-online)', color: '#fff', display: 'grid', placeItems: 'center' } }, React.createElement(Icon, { name: 'check', size: 13 }))), React.createElement('div', { style: { padding: '8px 10px' } }, React.createElement('div', { style: { fontWeight: 700, fontSize: 12.5, color: 'var(--fg-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, m.name), React.createElement('div', { className: 'row', style: { justifyContent: 'space-between', marginTop: 3 } }, React.createElement('span', { className: 'muted', style: { fontSize: 11 } }, m.w + '\u00d7' + m.h), React.createElement('span', { className: 'muted', style: { fontSize: 11, fontWeight: 700 } }, fmtDur(m.dur))))); })), ), ), // ---- ASIDE: preview + settings ---- React.createElement('div', { className: 'col gap-5', style: { position: 'sticky', top: 0 } }, React.createElement(SectionCard, { title: 'Pratinjau layar', sub: (layoutMode === 'kiosk' ? 'Mode kiosk \u00b7 ' : 'Mode fullscreen \u00b7 ') + (mObj ? mObj.name : 'semua mesin'), motif: true, bodyStyle: { padding: 'var(--sp-5)' } }, React.createElement('div', { className: 'col', style: { alignItems: 'center', gap: 14 } }, layoutMode === 'kiosk' ? React.createElement(KioskScreen, { headerMedia: headerCur, footerMedia: footerCur }) : React.createElement(ScreenPreview, { media: cur, playing, prog, setPlaying, orient: settings.orient, settings, idx: idx % total, total, onPrev: () => { setIdx(i => (i - 1 + total) % total); setProg(0); }, onNext: () => { setIdx(i => (i + 1) % total); setProg(0); } }), layoutMode === 'fullscreen' && React.createElement('div', { className: 'row gap-2' }, React.createElement('button', { className: 'icon-btn', onClick: () => { setIdx(i => (i - 1 + total) % total); setProg(0); } }, React.createElement(Icon, { name: 'chevronLeft', size: 18 })), React.createElement('button', { className: 'btn btn-primary', style: { width: 110, justifyContent: 'center' }, onClick: () => setPlaying(p => !p) }, React.createElement(Icon, { name: playing ? 'power' : 'play', size: 16 }), playing ? 'Jeda' : 'Putar'), React.createElement('button', { className: 'icon-btn', onClick: () => { setIdx(i => (i + 1) % total); setProg(0); } }, React.createElement(Icon, { name: 'chevronRight', size: 18 }))), )), React.createElement(SectionCard, { title: 'Pengaturan player', sub: 'Mode tayang & slideshow', bodyStyle: { padding: 'var(--sp-5)' } }, React.createElement('div', { className: 'col gap-5' }, React.createElement('div', null, React.createElement('label', { className: 'field-label', style: { marginBottom: 8 } }, 'Orientasi layar'), React.createElement('div', { className: 'row gap-2' }, [['portrait', 'Portrait'], ['landscape', 'Landscape']].map(([v, l]) => React.createElement('button', { key: v, className: 'chip ' + (settings.orient === v ? 'active' : ''), style: { flex: 1, justifyContent: 'center' }, onClick: () => setS('orient', v) }, l)))), React.createElement('div', null, React.createElement('label', { className: 'field-label', style: { marginBottom: 8 } }, 'Transisi antar media'), React.createElement('div', { className: 'row gap-2' }, [['fade', 'Fade'], ['slide', 'Slide'], ['none', 'Tanpa']].map(([v, l]) => React.createElement('button', { key: v, className: 'chip ' + (settings.transition === v ? 'active' : ''), style: { flex: 1, justifyContent: 'center' }, onClick: () => setS('transition', v) }, l)))), React.createElement('div', null, React.createElement('div', { className: 'row', style: { justifyContent: 'space-between', marginBottom: 6 } }, React.createElement('label', { className: 'field-label', style: { margin: 0 } }, 'Durasi default gambar'), React.createElement('span', { style: { fontFamily: 'var(--font-display)', fontSize: 20, color: 'var(--st-maint)' } }, settings.imgDur + ' dtk')), React.createElement('input', { type: 'range', className: 'range', min: 3, max: 30, value: settings.imgDur, onChange: e => setS('imgDur', +e.target.value) })), [['loop', 'Putar berulang (loop)', 'Ulangi playlist tanpa henti'], ['mute', 'Bisukan audio', 'Tanpa suara di layar'], ['priceOverlay', 'Overlay nama/harga', 'Tampilkan label produk di layar']].map(([k, label, sub]) => React.createElement('div', { key: k, className: 'row', style: { justifyContent: 'space-between', paddingTop: 4, borderTop: '1px solid var(--border-subtle)' } }, React.createElement('div', { style: { paddingTop: 8 } }, React.createElement('div', { style: { fontWeight: 700, fontSize: 14 } }, label), React.createElement('div', { style: { fontSize: 12, color: 'var(--fg-3)' } }, sub)), React.createElement('button', { className: 'toggle ' + (settings[k] ? 'on' : ''), style: { marginTop: 10 }, onClick: () => setS(k, !settings[k]) }))), React.createElement('div', { style: { paddingTop: 4, borderTop: '1px solid var(--border-subtle)' } }, React.createElement('label', { className: 'field-label', style: { marginBottom: 8, marginTop: 8 } }, 'Jam tayang'), React.createElement('div', { className: 'row gap-3' }, React.createElement('input', { type: 'time', className: 'input', value: settings.start, onChange: e => setS('start', e.target.value) }), React.createElement('span', { className: 'muted' }, '\u2013'), React.createElement('input', { type: 'time', className: 'input', value: settings.end, onChange: e => setS('end', e.target.value) }))), React.createElement('button', { className: 'btn btn-primary btn-stamp', style: { justifyContent: 'center', marginTop: 4 } }, React.createElement(Icon, { name: 'check', size: 16 }), 'Simpan & sinkron ke mesin'), )), ), ), ); } Object.assign(window, { SignageView, MediaArt, ScreenPreview, KioskScreen, PosGrid, fmtDur });