// MIZO UX layer — global search & quick actions, help, notifications, product tour, mobile "More" sheet, form helpers.
function Field({ label, hint, error, id, children, span }) {
  return (
    <div className={'field' + (span ? ' span-2' : '') + (error ? ' has-err' : '')}>
      <label htmlFor={id}>{label}</label>
      {children}
      {error ? <div className="field-err" role="alert"><Icon n="warning-circle" />{error}</div> : hint ? <div className="field-hint">{hint}</div> : null}
    </div>
  );
}
const vReq = (v, L) => (String(v || '').trim() ? null : L('Please fill this in.', 'Veuillez remplir ce champ.'));
const vEmail = (v, L) => (!v || /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v) ? null : L('This doesn’t look like an email address — check for a missing @ or dot.', 'Ce courriel semble incomplet — vérifiez le @ ou le point.'));
const vPhone = (v, L) => (!v || String(v).replace(/\D/g, '').length === 10 || String(v).replace(/\D/g, '').length === 11 ? null : L('Enter 10 digits, for example 514-555-0100.', 'Entrez 10 chiffres, par exemple 514-555-0100.'));

function EmptyState({ ic, title, body, action }) {
  return <div className="empty-st"><span className="empty-ic"><Icon n={ic || 'tray'} /></span><b>{title}</b>{body ? <p className="muted">{body}</p> : null}{action || null}</div>;
}
function Skeleton({ rows = 3 }) { return <div className="skel" aria-busy="true" aria-label="Loading">{Array.from({ length: rows }).map((_, i) => <i key={i} style={{ width: 90 - i * 14 + '%' }}></i>)}</div>; }

const HELP = {
  overview: { en: ['How your restaurant is doing today and this month, plus anything that needs your attention.', [['Change the dates', 'Use the buttons at the top right to pick today, this week or this month.'], ['See how a number is calculated', 'Click any number card. A panel opens with the full calculation.'], ['Deal with alerts', 'Each alert has a button that takes you to the right place.']]], fr: ['L’état du restaurant aujourd’hui et ce mois-ci, et ce qui demande votre attention.', [['Changer les dates', 'Utilisez les boutons en haut à droite : aujourd’hui, cette semaine ou ce mois.'], ['Voir le calcul d’un chiffre', 'Cliquez sur une carte. Un panneau montre le calcul complet.'], ['Traiter les alertes', 'Chaque alerte a un bouton qui mène au bon endroit.']]], terms: ['food', 'prime', 'op'] },
  inventory: { en: ['Everything on your shelves: how much you have, what it’s worth and what to reorder.', [['Count your stock', 'Open “Counts”, then tap Start count. Enter what you see on the shelf.'], ['Record waste', 'Open “Waste” and add what was thrown out and why.'], ['Reorder', 'Open “Reorder” — items below their minimum are listed with a suggested amount.']]], fr: ['Tout ce qui est en stock : quantités, valeur et quoi recommander.', [['Compter le stock', 'Ouvrez « Inventaires », puis Commencer. Entrez ce que vous voyez.'], ['Noter les pertes', 'Ouvrez « Pertes » et indiquez ce qui a été jeté et pourquoi.'], ['Recommander', 'Ouvrez « À commander » — les articles sous le minimum sont listés.']]], terms: ['par', 'variance'] },
  menu: { en: ['Your dishes, what each one costs to make, and what it earns.', [['Change a recipe', 'Open “Recipe builder”, pick a dish and adjust the amounts.'], ['Test a new price', 'Open “Price simulator” and move the slider.'], ['Find your best dishes', 'Open “Menu engineering” — dishes are grouped by popularity and profit.']]], fr: ['Vos plats, ce que chacun coûte et rapporte.', [['Modifier une recette', 'Ouvrez « Recettes et coûts », choisissez un plat et ajustez.'], ['Tester un prix', 'Ouvrez « Simulateur de prix » et déplacez le curseur.'], ['Trouver vos meilleurs plats', 'Ouvrez « Ingénierie du menu ».']]], terms: ['food', 'theo'] },
  purchasing: { en: ['Ordering from suppliers, receiving deliveries and paying invoices.', [['Order from a supplier', 'Open “Orders” and tap New order. Nothing is sent until you approve it.'], ['Check a delivery', 'Open “Receiving” when the truck arrives and tick off what came in.'], ['Scan an invoice', 'Open “Invoice scan” and take a photo — MIZO reads the lines for you.']]], fr: ['Commandes aux fournisseurs, réceptions et factures.', [['Commander', 'Ouvrez « Commandes » puis Nouvelle commande. Rien n’est envoyé sans votre accord.'], ['Vérifier une livraison', 'Ouvrez « Réception » et cochez ce qui est arrivé.'], ['Numériser une facture', 'Ouvrez « Numérisation » et prenez une photo.']]], terms: ['po'] },
  sales: { en: ['What you sold, through which channel, and whether the bank deposits match.', [['See today’s sales', 'The Summary tab shows today and this week.'], ['Check deposits', 'Open “Reconciliation” — days that don’t match the bank are marked.'], ['Delivery apps', 'Open “Delivery apps” to see fees and refunds from Uber Eats and DoorDash.']]], fr: ['Ce que vous avez vendu, par quel canal, et si les dépôts concordent.', [['Ventes du jour', 'L’onglet Sommaire montre aujourd’hui et la semaine.'], ['Vérifier les dépôts', 'Ouvrez « Rapprochement » — les jours non concordants sont signalés.'], ['Applis de livraison', 'Ouvrez « Livraison » pour les frais et remboursements.']]], terms: ['recon'] },
  team: { en: ['Schedules, time clock, payroll and kitchen routines.', [['Approve hours', 'Open “Timesheets” and fix any missing punches, then approve.'], ['Run payroll', 'Open “Payroll” — MIZO calculates pay, deductions and vacation.'], ['Daily checklists', 'Open “Checklists” for opening, closing and food-safety tasks.']]], fr: ['Horaires, pointage, paie et routines de cuisine.', [['Approuver les heures', 'Ouvrez « Feuilles de temps », corrigez les pointages manquants, puis approuvez.'], ['Faire la paie', 'Ouvrez « Paie » — MIZO calcule salaires, retenues et vacances.'], ['Listes quotidiennes', 'Ouvrez « Listes » pour l’ouverture, la fermeture et la salubrité.']]], terms: ['labour'] },
  finance: { en: ['Your profit and loss, costs and cash, with every number explained.', [['Read your P&L', 'Open “Profit & loss”. Each line can be clicked to see where it comes from.'], ['Compare periods', 'Use “Compare with” at the top to compare with last month, last year or your budget.'], ['Know what’s missing', 'Labels show which numbers are verified, estimated or not entered yet.']]], fr: ['Résultats, coûts et liquidités, chaque chiffre expliqué.', [['Lire l’état des résultats', 'Ouvrez « État des résultats ». Cliquez sur une ligne pour sa source.'], ['Comparer', 'Utilisez « Comparer avec » en haut.'], ['Savoir ce qui manque', 'Les étiquettes indiquent vérifié, estimé ou manquant.']]], terms: ['gross', 'op', 'cogs', 'prime'] },
  ai: { en: ['MIZO looks across all your data to find money you’re losing and suggest what to do.', [['Ask a question', 'Open “Assistant” and type a question in your own words.'], ['Find lost money', 'Open “Profit leaks” — each one shows the evidence and what to do.'], ['Try “what if”', 'Open “Simulator” to see what a price or cost change would do.']]], fr: ['MIZO croise vos données pour trouver l’argent perdu et suggérer quoi faire.', [['Poser une question', 'Ouvrez « Assistant » et écrivez votre question.'], ['Trouver l’argent perdu', 'Ouvrez « Fuites de profit » — preuves et actions pour chacune.'], ['Essayer « et si »', 'Ouvrez « Simulateur ».']]], terms: ['op', 'theo'] },
  settings: { en: ['Your restaurant details, display options, people and connections.', [['Make text bigger', 'Under Display, choose Large or Extra large text.'], ['Invite a manager', 'Open “Users & roles” and tap Invite someone.'], ['Connect your POS', 'Open “Connections” and tap Prepare next to your system.']]], fr: ['Détails du restaurant, affichage, personnes et connexions.', [['Agrandir le texte', 'Sous Affichage, choisissez Grand ou Très grand.'], ['Inviter un gérant', 'Ouvrez « Utilisateurs et rôles » puis Inviter.'], ['Connecter le PDV', 'Ouvrez « Connexions » et cliquez Préparer.']]], terms: [] },
};
const GLOSS = {
  food: [['Food cost %', 'What your ingredients cost, as a share of what you sold. 30% means $30 of ingredients for every $100 of food sales.'], ['Coût alimentaire %', 'Le coût des ingrédients en part des ventes. 30 % = 30 $ d’ingrédients pour 100 $ de ventes.']],
  prime: [['Prime cost', 'Ingredients plus staff — your two biggest costs. Most restaurants aim for about 60% of sales.'], ['Coût principal', 'Ingrédients plus personnel — vos deux plus gros coûts. Visez environ 60 % des ventes.']],
  op: [['Operating profit (estimate)', 'Sales minus ingredients, staff, rent, utilities and fees. It is not net profit: depreciation, interest, insurance, owner pay and tax still come off.'], ['Profit d’exploitation (estimé)', 'Ventes moins ingrédients, personnel, loyer, énergie et frais. Ce n’est pas le profit net : amortissement, intérêts, assurances, rémunération et impôt restent à déduire.']],
  gross: [['Gross profit', 'Sales minus the cost of ingredients and packaging.'], ['Profit brut', 'Ventes moins le coût des ingrédients et emballages.']],
  cogs: [['Cost of goods', 'What the food, drinks and packaging you used cost: opening stock + purchases − closing stock.'], ['Coût des marchandises', 'Ce qu’ont coûté aliments, boissons et emballages utilisés : stock initial + achats − stock final.']],
  par: [['Par level', 'The amount you like to have on the shelf. Below it, MIZO suggests reordering.'], ['Niveau cible', 'La quantité souhaitée en stock. En dessous, MIZO suggère de commander.']],
  variance: [['Variance', 'The difference between what the count says and what should be there, based on sales and recipes.'], ['Écart', 'La différence entre l’inventaire compté et ce qui devrait rester selon ventes et recettes.']],
  theo: [['Theoretical vs. actual', 'Theoretical is what recipes say you should have used; actual is what left the shelf. The gap is waste, portions or loss.'], ['Théorique vs réel', 'Théorique : ce que les recettes prévoient; réel : ce qui est sorti. L’écart = pertes, portions ou vol.']],
  po: [['Purchase order (PO)', 'A list of what you’re ordering from a supplier, sent after you approve it.'], ['Bon de commande (BC)', 'La liste de ce que vous commandez, envoyée après votre approbation.']],
  recon: [['Reconciliation', 'Checking that what the till says matches what reached the bank.'], ['Rapprochement', 'Vérifier que la caisse concorde avec ce qui arrive à la banque.']],
  labour: [['Labour %', 'Wages plus employer costs, as a share of sales.'], ['Main-d’œuvre %', 'Salaires plus charges, en part des ventes.']],
};

function HelpPanel({ secId, onClose, onTour, onSetup }) {
  const { L, lang } = useMizo();
  const h = HELP[secId] || HELP.overview, c = h[lang === 'fr' ? 'fr' : 'en'], sec = SECTIONS.find((s) => s.id === secId);
  return (
    <div className="dr">
      <div className="dr-head"><div><div className="card-kicker">{L('Help', 'Aide')}</div><h2>{sec ? L(...sec.t) : 'MIZO'}</h2></div><button className="btn btn-icon btn-secondary" onClick={onClose} aria-label={L('Close', 'Fermer')}><Icon n="x" /></button></div>
      <div className="dr-sec"><p className="help-lede">{c[0]}</p></div>
      <div className="dr-sec"><div className="dr-label">{L('How do I…', 'Comment…')}</div>{c[1].map(([t, d], i) => <div key={i} className="help-task"><span className="help-n">{i + 1}</span><div><b>{t}</b><div className="muted">{d}</div></div></div>)}</div>
      {h.terms.length ? <div className="dr-sec"><div className="dr-label">{L('Words used on this page', 'Termes de cette page')}</div>{h.terms.map((k) => { const g = GLOSS[k][lang === 'fr' ? 1 : 0]; return <details key={k} className="help-term"><summary>{g[0]}</summary><p className="muted">{g[1]}</p></details>; })}</div> : null}
      <div className="dr-sec help-acts">
        <button className="btn btn-primary" onClick={onTour}><Icon n="compass" />{L('Take the 2-minute tour', 'Visite guidée de 2 minutes')}</button>
        <button className="btn btn-secondary" onClick={onSetup}><Icon n="list-checks" />{L('Open the setup guide', 'Ouvrir le guide de configuration')}</button>
        <div className="help-contact"><Icon n="phone" /><div><b>{L('Talk to a person', 'Parler à quelqu’un')}</b><div className="muted">1-855-555-0199 · {L('Mon–Sat, 8 am–8 pm', 'lun.–sam., 8 h–20 h')} · {L('English and French', 'français et anglais')}</div></div></div>
      </div>
    </div>
  );
}

function CommandPalette({ open, onClose, actions }) {
  const { L, lang, D, inv, recipes, invoices, go } = useMizo();
  const [q, setQ] = React.useState(''); const [i, setI] = React.useState(0); const inp = React.useRef(null);
  React.useEffect(() => { if (open) { setQ(''); setI(0); setTimeout(() => inp.current && inp.current.focus(), 30); } }, [open]);
  const norm = (s) => s.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  const all = React.useMemo(() => [
    ...actions.map((a) => ({ ...a, g: L('Quick actions', 'Actions rapides') })),
    ...SECTIONS.flatMap((s) => s.tabs.map(([t, en, fr]) => ({ ic: s.ic, t: `${L(...s.s)} › ${L(en, fr)}`, g: L('Pages', 'Pages'), run: () => go(s.id, { tab: t }) }))),
    ...inv.map((x) => ({ ic: 'package', t: x.name[lang], sub: `${x.onHand} ${x.unit} ${L('on hand', 'en stock')}`, g: L('Ingredients', 'Ingrédients'), run: () => go('inventory', { item: x.id }) })),
    ...recipes.map((r) => ({ ic: 'cooking-pot', t: r.name[lang], sub: L('Recipe', 'Recette'), g: L('Dishes', 'Plats'), run: () => go('menu', { recipe: r.id }) })),
    ...D.suppliers.map((s) => ({ ic: 'truck', t: s.name, sub: s.contact + ' · ' + s.phone, g: L('Suppliers', 'Fournisseurs'), run: () => go('purchasing', { tab: 'suppliers' }) })),
    ...invoices.map((v) => ({ ic: 'receipt', t: v.id, sub: D.suppliers.find((s) => s.id === v.sup).name, g: L('Invoices', 'Factures'), run: () => go('purchasing', { tab: 'invoices' }) })),
  ], [lang, inv, recipes, invoices]);
  const res = q.trim() ? all.filter((x) => norm(x.t + ' ' + (x.sub || '')).includes(norm(q.trim()))).slice(0, 40) : all.filter((x) => x.g === L('Quick actions', 'Actions rapides'));
  const run = (x) => { onClose(); setTimeout(x.run, 10); };
  const key = (e) => { if (e.key === 'ArrowDown') { e.preventDefault(); setI(Math.min(res.length - 1, i + 1)); } if (e.key === 'ArrowUp') { e.preventDefault(); setI(Math.max(0, i - 1)); } if (e.key === 'Enter' && res[i]) run(res[i]); if (e.key === 'Escape') onClose(); };
  React.useEffect(() => { const el = document.querySelector('.cmdk-item.on'); if (el && el.parentNode) { const p = el.closest('.cmdk-list'); if (el.offsetTop < p.scrollTop) p.scrollTop = el.offsetTop - 40; else if (el.offsetTop + el.offsetHeight > p.scrollTop + p.clientHeight) p.scrollTop = el.offsetTop + el.offsetHeight - p.clientHeight; } }, [i]);
  if (!open) return null;
  let lastG = null;
  return ReactDOM.createPortal(
    <div className="dialog-backdrop mdl-backdrop cmdk-wrap" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
      <div className="cmdk" role="dialog" aria-modal="true" aria-label={L('Search MIZO', 'Rechercher dans MIZO')}>
        <div className="cmdk-in"><Icon n="magnifying-glass" /><input ref={inp} value={q} onChange={(e) => { setQ(e.target.value); setI(0); }} onKeyDown={key} placeholder={L('Search pages, ingredients, dishes, suppliers… or pick an action', 'Rechercher pages, ingrédients, plats, fournisseurs… ou choisir une action')} aria-label={L('Search', 'Rechercher')} /><kbd>Esc</kbd></div>
        <div className="cmdk-list" role="listbox">
          {res.map((x, j) => { const head = x.g !== lastG ? <div className="cmdk-g" key={'g' + j}>{x.g}</div> : null; lastG = x.g; return <React.Fragment key={j}>{head}<button role="option" aria-selected={i === j} className={'cmdk-item' + (i === j ? ' on' : '')} onMouseMove={() => setI(j)} onClick={() => run(x)}><Icon n={x.ic} /><span><span>{x.t}</span>{x.sub ? <span className="muted sm">{x.sub}</span> : null}</span>{x.k ? <kbd>{x.k}</kbd> : <Icon n="arrow-right" className="cmdk-go" />}</button></React.Fragment>; })}
          {!res.length ? <EmptyState ic="magnifying-glass" title={L(`Nothing found for “${q}”`, `Rien trouvé pour « ${q} »`)} body={L('Try a shorter word, or check the spelling.', 'Essayez un mot plus court ou vérifiez l’orthographe.')} /> : null}
        </div>
        <div className="cmdk-foot muted sm"><span><kbd>↑</kbd><kbd>↓</kbd> {L('to move', 'pour naviguer')}</span><span><kbd>Enter</kbd> {L('to open', 'pour ouvrir')}</span><span>{L('Open this anytime with', 'Ouvrir à tout moment avec')} <kbd>Ctrl</kbd> <kbd>K</kbd></span></div>
      </div>
    </div>, document.body);
}

function NotifPanel({ onClose }) {
  const { L } = useMizo();
  const alerts = useAlerts();
  const [read, setRead] = React.useState(() => { try { return JSON.parse(localStorage.getItem('mizo.read')) || []; } catch (e) { return []; } });
  const mark = (ids) => { const n = [...new Set([...read, ...ids])]; setRead(n); localStorage.setItem('mizo.read', JSON.stringify(n)); };
  const tone = { neg: L('Urgent', 'Urgent'), warn: L('Soon', 'Bientôt') };
  return (
    <div className="dr">
      <div className="dr-head"><div><div className="card-kicker">{L('Notifications', 'Notifications')}</div><h2>{L('What needs you', 'Ce qui vous attend')}</h2></div><button className="btn btn-icon btn-secondary" onClick={onClose} aria-label={L('Close', 'Fermer')}><Icon n="x" /></button></div>
      <div className="dr-sec ntf-head"><span className="muted">{alerts.filter((a) => !read.includes(a.text)).length} {L('unread', 'non lues')}</span><button className="btn btn-ghost" onClick={() => mark(alerts.map((a) => a.text))}><Icon n="checks" />{L('Mark all as read', 'Tout marquer comme lu')}</button></div>
      <ul className="ntf-list">{alerts.map((a, i) => <li key={i} className={'ntf' + (read.includes(a.text) ? ' read' : '')}>
        <span className={'alert-ic ' + a.tone}><Icon n={a.ic} /></span>
        <div className="ntf-t"><div>{a.text}</div><div className="muted sm">{tone[a.tone] ? <b className={'txt-' + a.tone}>{tone[a.tone]} · </b> : null}{a.sub}</div></div>
        <button className="btn btn-secondary" onClick={() => { mark([a.text]); onClose(); a.to(); }}>{a.act}</button>
      </li>)}</ul>
      {!alerts.length ? <EmptyState ic="bell-simple" title={L('You’re all caught up', 'Tout est à jour')} body={L('MIZO will let you know when something needs you.', 'MIZO vous préviendra au besoin.')} /> : null}
    </div>
  );
}

const TOUR = [
  ['.rail', ['Your main sections', 'Vos sections principales'], ['Everything MIZO does is grouped here: stock, menu, ordering, sales, team, money and Intelligence. Tap one to open it.', 'Tout MIZO est regroupé ici : stock, menu, achats, ventes, équipe, finances et Intelligence. Touchez pour ouvrir.']],
  ['.sec-head .subnav', ['Pages inside a section', 'Pages d’une section'], ['Each section has a few pages along the top. The one you’re on is highlighted.', 'Chaque section a quelques pages en haut. Celle où vous êtes est en surbrillance.']],
  ['.tsearch', ['Find anything, do anything', 'Tout trouver, tout faire'], ['Search for an ingredient, dish, supplier or invoice, or start common tasks like counting stock. Shortcut: Ctrl + K.', 'Cherchez un ingrédient, un plat, un fournisseur ou une facture, ou lancez une tâche. Raccourci : Ctrl + K.']],
  ['.fin-picker', ['Pick your dates', 'Choisir les dates'], ['Choose today, this week or this month, and what to compare with. Every number on the page updates.', 'Choisissez aujourd’hui, la semaine ou le mois, et la comparaison. Tous les chiffres suivent.']],
  ['.kpis .metric', ['Every number explains itself', 'Chaque chiffre s’explique'], ['Click any number card to see exactly how it was calculated, and whether it is verified or an estimate.', 'Cliquez sur une carte pour voir le calcul exact, et s’il est vérifié ou estimé.']],
  ['.alerts', ['What needs your attention', 'Ce qui demande votre attention'], ['Low stock, overdue bills and missing punches show up here, each with a button to fix it.', 'Stock bas, factures en retard et pointages manquants apparaissent ici, avec un bouton pour régler.']],
  ['.rail-item[data-sec="ai"]', ['MIZO Intelligence', 'MIZO Intelligence'], ['Ask questions in plain words and see where you’re losing money, with the evidence.', 'Posez vos questions et voyez où vous perdez de l’argent, preuves à l’appui.']],
  ['.thelp', ['Help is always here', 'L’aide est toujours là'], ['Stuck? This button explains the page you’re on, in plain words, and lists who to call.', 'Bloqué? Ce bouton explique la page en mots simples et indique qui appeler.']],
];
function Tour({ open, onClose }) {
  const { L } = useMizo();
  const [i, setI] = React.useState(0), [r, setR] = React.useState(null);
  React.useEffect(() => { if (open) setI(0); }, [open]);
  React.useEffect(() => {
    if (!open) return;
    const upd = () => { const all = [...document.querySelectorAll(TOUR[i][0])].filter((e) => e.offsetParent || e.getClientRects().length); const el = all[0]; if (!el) { setR(null); return; } const b = el.getBoundingClientRect(); if (b.top < 70 || b.bottom > window.innerHeight) { window.scrollTo({ top: Math.max(0, window.scrollY + b.top - 140) }); } const c = el.getBoundingClientRect(); setR({ x: c.left, y: c.top, w: c.width, h: Math.min(c.height, window.innerHeight - c.top - 12) }); };
    const t = setTimeout(upd, 60); window.addEventListener('resize', upd); window.addEventListener('scroll', upd, true);
    const k = (e) => { if (e.key === 'Escape') onClose(); if (e.key === 'ArrowRight') setI((x) => Math.min(TOUR.length - 1, x + 1)); if (e.key === 'ArrowLeft') setI((x) => Math.max(0, x - 1)); };
    window.addEventListener('keydown', k);
    return () => { clearTimeout(t); window.removeEventListener('resize', upd); window.removeEventListener('scroll', upd, true); window.removeEventListener('keydown', k); };
  }, [open, i]);
  if (!open) return null;
  const s = TOUR[i], pad = 8, W = 340, vw = window.innerWidth, vh = window.innerHeight;
  let pos = { left: vw / 2 - W / 2, top: vh / 2 - 100 };
  if (r) { const right = r.x + r.w + pad + 16; if (r.h > vh * 0.5 && right + W < vw) pos = { left: right, top: Math.min(Math.max(16, r.y + 40), vh - 260) }; else { const below = r.y + r.h + pad + 14; pos = { left: Math.min(Math.max(16, r.x), vw - W - 16), top: below + 230 < vh ? below : Math.max(16, r.y - pad - 240) }; } }
  return ReactDOM.createPortal(
    <div className="tour" role="dialog" aria-modal="true" aria-label={L('Product tour', 'Visite guidée')}>
      {r ? <div className="tour-hole" style={{ left: r.x - pad, top: r.y - pad, width: r.w + pad * 2, height: r.h + pad * 2 }}></div> : <div className="tour-dim"></div>}
      <div className="tour-pop" style={{ left: pos.left, top: pos.top, width: Math.min(W, vw - 32) }}>
        <div className="tour-step muted sm">{L(`Step ${i + 1} of ${TOUR.length}`, `Étape ${i + 1} sur ${TOUR.length}`)}</div>
        <h3>{L(...s[1])}</h3>
        <p>{L(...s[2])}</p>
        <div className="tour-dots">{TOUR.map((_, j) => <i key={j} className={j === i ? 'on' : j < i ? 'done' : ''}></i>)}</div>
        <div className="tour-acts">
          <button className="btn btn-ghost" onClick={onClose}>{L('End tour', 'Terminer')}</button>
          <span style={{ flex: 1 }}></span>
          {i > 0 ? <button className="btn btn-secondary" onClick={() => setI(i - 1)}>{L('Back', 'Retour')}</button> : null}
          <button className="btn btn-primary" autoFocus onClick={() => (i < TOUR.length - 1 ? setI(i + 1) : onClose(true))}>{i < TOUR.length - 1 ? L('Next', 'Suivant') : L('Finish', 'Terminer')}<Icon n={i < TOUR.length - 1 ? 'arrow-right' : 'check'} /></button>
        </div>
      </div>
    </div>, document.body);
}

function MoreSheet({ open, onClose, page, badges }) {
  const { L, go } = useMizo();
  if (!open) return null;
  return ReactDOM.createPortal(
    <div className="dialog-backdrop mdl-backdrop more-wrap" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
      <div className="more-sheet" role="dialog" aria-modal="true" aria-label={L('All sections', 'Toutes les sections')}>
        <div className="more-head"><b>{L('All sections', 'Toutes les sections')}</b><button className="btn btn-icon btn-secondary" onClick={onClose} aria-label={L('Close', 'Fermer')}><Icon n="x" /></button></div>
        <div className="more-grid">{SECTIONS.map((s) => <button key={s.id} className={'more-item' + (page === s.id ? ' on' : '')} onClick={() => { onClose(); go(s.id); }}><Icon n={s.ic} /><span>{L(...s.t)}</span>{badges[s.id] ? <span className="badge">{badges[s.id]}</span> : null}</button>)}</div>
      </div>
    </div>, document.body);
}

const SEE_ALSO = {
  reorder: ['ai', 'buying', 'sparkle', ['Want amounts based on the sales forecast and shelf life?', 'Des quantités selon la prévision et la conservation?'], ['Smart ordering', 'Commande intelligente']],
  buying: ['inventory', 'reorder', 'list-checks', ['Just need a list of items below their minimum?', 'Seulement la liste sous le minimum?'], ['Reorder list', 'Réapprovisionnement']],
  teamLabour: ['finance', 'labour', 'chart-line-up', ['Labour as a share of sales, by month and vs. budget:', 'Main-d’œuvre en part des ventes, par mois et vs budget :'], ['Finance › Labour', 'Finances › Main-d’œuvre']],
  finLabour: ['team', 'labour', 'calendar', ['To change hours, use the schedule:', 'Pour modifier les heures, utilisez l’horaire :'], ['Team › Labour cost', 'Équipe › Coût de main-d’œuvre']],
  menuEng: ['finance', 'menu', 'chart-line-up', ['Profit by dish in dollars, over time:', 'Profit par plat en dollars, dans le temps :'], ['Finance › Menu profitability', 'Finances › Rentabilité du menu']],
  finMenu: ['menu', 'engineering', 'cooking-pot', ['Popularity vs. margin, with what to do for each dish:', 'Popularité vs marge, avec quoi faire pour chaque plat :'], ['Menu engineering', 'Ingénierie du menu']],
  priceSim: ['ai', 'simulate', 'sliders-horizontal', ['See the effect on the whole month, with labour and fees:', 'Voir l’effet sur tout le mois, main-d’œuvre et frais compris :'], ['Financial simulator', 'Simulateur financier']],
  sim: ['menu', 'sim', 'tag', ['Test a new price on one dish:', 'Tester un prix sur un plat :'], ['Menu › Price simulator', 'Menu › Simulateur de prix']],
  aiFc: ['finance', 'forecast', 'chart-line-up', ['Month-by-month sales forecast and budget:', 'Prévision mensuelle et budget :'], ['Finance › Forecast', 'Finances › Prévisions']],
  finFc: ['ai', 'forecast', 'sparkle', ['Next 7 days, with prep and ingredient needs:', '7 prochains jours, avec préparation et ingrédients :'], ['Intelligence › Forecasts', 'Intelligence › Prévisions']],
};
function SeeAlso({ k }) {
  const { L, go } = useMizo(); const s = SEE_ALSO[k];
  return <div className="see-also"><Icon n={s[2]} /><span>{L(...s[3])}</span><button className="btn btn-ghost" onClick={() => go(s[0], { tab: s[1] })}>{L(...s[4])}<Icon n="arrow-right" /></button></div>;
}
Object.assign(window, { SeeAlso, SEE_ALSO, Field, vReq, vEmail, vPhone, EmptyState, Skeleton, HELP, GLOSS, HelpPanel, CommandPalette, NotifPanel, TOUR, Tour, MoreSheet });
