/* Inline SVG icons + i18n strings */ const Icon = ({ name, size = 16, ...rest }) => { const paths = { dashboard: <>, machine: <>, inventory: <>, transaction: <>, bell: <>, bottle: <>, users: <>, settings: <>, camera: <>, chart: <>, search: <>, chevdown: , chevright: , chevleft: , close: <>, plus: <>, download: <>, refresh: <>, filter: <>, lock: <>, unlock: <>, warning: <>, check: , pause: <>, play: , eye: <>, door: <>, map: <>, grid: <>, list: <>, sliders: <>, sun: <>, moon: , info: <>, dot: , arrowup: <>, arrowdown: <>, }; return ( {paths[name]} ); }; const I18N = { no: { nav_overview: 'Oversikt', nav_machines: 'Automater', nav_transactions: 'Transaksjoner', nav_events: 'Hendelser', nav_bottles: 'Flasker & priser', nav_users: 'Brukere', nav_settings: 'Innstillinger', overview_title: 'Oversikt', overview_sub: 'Sanntid på tvers av alle automater i tenanten', kpi_today_rev: 'Omsetning i dag', kpi_month_rev: 'Omsetning denne mnd', kpi_full_total: 'Fulle totalt', kpi_empty: 'Tomme – klar for henting', kpi_vacant: 'Ledige hyller', kpi_offline: 'Offline / med feil', alarms: 'Aktive varsler', alarms_sub: 'Alle åpne hendelser på tvers av automater', all_machines: 'Alle automater', view_map: 'Kart', view_grid: 'Kort', view_list: 'Liste', add_machine: 'Legg til automat', full: 'Fulle', empty: 'Tomme', vacant: 'Ledige', today: 'I dag', month: 'Denne mnd', online: 'Online', offline: 'Offline', warn: 'Varsler', live: 'LIVE', tab_statistics: 'Statistikk', tab_inventory: 'Sanntidsbeholdning', tab_shelves: 'Hyller', tab_cameras: 'Kameraer', tab_transactions: 'Transaksjoner', tab_events: 'Hendelser', tab_settings: 'Innstillinger', overall: 'Totalt', swap: 'Bytte', buy: 'Kjøp', avg_basket: 'Snitt per transaksjon', avg_dwell: 'Snitt-tid per kunde', refill_freq: 'Snitt mellom påfyll', classification_conf: 'Bilde-klassifisering snitt-konfidens', cabinet_front: 'Skap A (front)', cabinet_back: 'Skap B (bakside)', cabinet_sub: '12 hyller · 4 kameraer', state_full: 'Full', state_empty: 'Tomflaske', state_vacant: 'Ledig', state_disabled: 'Deaktivert', state_error: 'Feil', shelf_actions: 'Hyllestyring', btn_unlock: 'Åpne lås', btn_lock: 'Lås', btn_disable: 'Deaktiver', btn_enable: 'Aktiver', btn_force_empty: 'Marker som tom', btn_force_full: 'Marker som full', btn_capture: 'Ta nytt bilde', last_capture: 'Siste kamerabilde', capture_taken: 'Tatt', last_event: 'Siste hendelse', calibrate_title: 'Kamerakalibrering', calibrate_sub: 'Marker utsnittet til hver hylle (2×2) i kamerabildet. Verdiene lagres som ROI per hylle.', cam: 'Kamera', coverage: 'Dekker', new_capture: 'Nytt bilde', save_rois: 'Lagre ROI', auto_detect: 'AI-forslag', snap_grid: 'Snap 2×2', reset_rois: 'Tilbakestill', bottles_title: 'Flasker & priser', bottles_sub: 'Definer flasketyper, bytte- og kjøpspriser', type: 'Type', name: 'Navn', weight: 'Vekt', swap_price: 'Bytte-pris', new_price: 'Kjøp-pris (med flaske)', inventory_total: 'På lager (alle automater)', edit: 'Endre', users_title: 'Brukere & tilganger', users_sub: 'Inviter teammedlemmer og styr roller per tenant', invite: 'Inviter bruker', role: 'Rolle', last_seen: 'Sist aktiv', search_placeholder: 'Søk automat (navn eller ID)…', refresh: 'Oppdater', export: 'Eksporter', settings: 'Innstillinger', tx_id: 'Transaksjon', tx_time: 'Tid', tx_type: 'Type', tx_bottle: 'Flaske', tx_shelf_out: 'Hylle ut', tx_shelf_in: 'Hylle inn', tx_amount: 'Beløp', tx_pay: 'Betaling', tx_duration: 'Varighet', ev_time: 'Tid', ev_sev: 'Alvorlighet', ev_msg: 'Melding', ev_machine: 'Automat', ev_shelf: 'Hylle', ev_camera: 'Kamera', sev_danger: 'Kritisk', sev_warn: 'Varsel', sev_info: 'Info', minutes_ago: 'min siden', hours_ago: 't siden', days_ago: 'd siden', last_seen_label: 'Sist sett', firmware: 'Firmware', last_service: 'Siste service', open: 'Åpne', close: 'Lukk', revenue: 'Omsetning', transactions: 'Transaksjoner', sales_split: 'Salgsfordeling', top_bottles: 'Mest solgte flasker', busy_hours: 'Travleste tider', last7: 'Siste 7 dager', last30: 'Siste 30 dager', last90: 'Siste 90 dager', machine_details: 'Automat-detaljer', }, en: { nav_overview: 'Overview', nav_machines: 'Machines', nav_transactions: 'Transactions', nav_events: 'Events', nav_bottles: 'Bottles & pricing', nav_users: 'Users', nav_settings: 'Settings', overview_title: 'Overview', overview_sub: 'Realtime across all machines in the tenant', kpi_today_rev: 'Revenue today', kpi_month_rev: 'Revenue this month', kpi_full_total: 'Full total', kpi_empty: 'Empty – ready for pickup', kpi_vacant: 'Vacant shelves', kpi_offline: 'Offline / with errors', alarms: 'Active alerts', alarms_sub: 'All open events across machines', all_machines: 'All machines', view_map: 'Map', view_grid: 'Cards', view_list: 'List', add_machine: 'Add machine', full: 'Full', empty: 'Empty', vacant: 'Vacant', today: 'Today', month: 'This month', online: 'Online', offline: 'Offline', warn: 'Alerts', live: 'LIVE', tab_statistics: 'Statistics', tab_inventory: 'Realtime inventory', tab_shelves: 'Shelves', tab_cameras: 'Cameras', tab_transactions: 'Transactions', tab_events: 'Events', tab_settings: 'Settings', overall: 'Total', swap: 'Swap', buy: 'Buy', avg_basket: 'Avg per transaction', avg_dwell: 'Avg customer time', refill_freq: 'Avg between refills', classification_conf: 'Image-class. avg confidence', cabinet_front: 'Cabinet A (front)', cabinet_back: 'Cabinet B (back)', cabinet_sub: '12 shelves · 4 cameras', state_full: 'Full', state_empty: 'Empty bottle', state_vacant: 'Vacant', state_disabled: 'Disabled', state_error: 'Error', shelf_actions: 'Shelf control', btn_unlock: 'Unlock', btn_lock: 'Lock', btn_disable: 'Disable', btn_enable: 'Enable', btn_force_empty: 'Mark as empty', btn_force_full: 'Mark as full', btn_capture: 'Capture image', last_capture: 'Last camera image', capture_taken: 'Taken', last_event: 'Last event', calibrate_title: 'Camera calibration', calibrate_sub: 'Drag rectangles to mark each shelf (2×2) in the camera frame. Stored as ROI per shelf.', cam: 'Camera', coverage: 'Covers', new_capture: 'New capture', save_rois: 'Save ROIs', auto_detect: 'AI suggest', snap_grid: 'Snap 2×2', reset_rois: 'Reset', bottles_title: 'Bottles & pricing', bottles_sub: 'Define bottle types, swap and new-bottle prices', type: 'Type', name: 'Name', weight: 'Weight', swap_price: 'Swap price', new_price: 'New-bottle price', inventory_total: 'In stock (all machines)', edit: 'Edit', users_title: 'Users & access', users_sub: 'Invite teammates and manage roles per tenant', invite: 'Invite user', role: 'Role', last_seen: 'Last seen', search_placeholder: 'Search machine (name or ID)…', refresh: 'Refresh', export: 'Export', settings: 'Settings', tx_id: 'Transaction', tx_time: 'Time', tx_type: 'Type', tx_bottle: 'Bottle', tx_shelf_out: 'Shelf out', tx_shelf_in: 'Shelf in', tx_amount: 'Amount', tx_pay: 'Payment', tx_duration: 'Duration', ev_time: 'Time', ev_sev: 'Severity', ev_msg: 'Message', ev_machine: 'Machine', ev_shelf: 'Shelf', ev_camera: 'Camera', sev_danger: 'Critical', sev_warn: 'Warning', sev_info: 'Info', minutes_ago: 'min ago', hours_ago: 'h ago', days_ago: 'd ago', last_seen_label: 'Last seen', firmware: 'Firmware', last_service: 'Last service', open: 'Open', close: 'Close', revenue: 'Revenue', transactions: 'Transactions', sales_split: 'Sales split', top_bottles: 'Top bottles', busy_hours: 'Busiest hours', last7: 'Last 7 days', last30: 'Last 30 days', last90: 'Last 90 days', machine_details: 'Machine details', }, }; const fmtKr = (n, lang = 'no') => { if (n == null) return '–'; const s = Math.round(n).toLocaleString(lang === 'no' ? 'nb-NO' : 'en-US'); return lang === 'no' ? `${s} kr` : `NOK ${s}`; }; const fmtNum = (n, lang = 'no') => (n == null ? '–' : n.toLocaleString(lang === 'no' ? 'nb-NO' : 'en-US')); Object.assign(window, { Icon, I18N, fmtKr, fmtNum });