// Scent Alchemist — App: core game loop & state
// Globals: React, INGREDIENTS, RECIPES, CUSTOMER_TYPES, UPGRADES, ingredientById, recipeById
//          computeAccuracy, tierFor, pickCustomer, loadGame, saveGame, DEFAULT_STATE
//          Hud, CustomerPanel, FlaskView, IngredientRow, Floaters,
//          RecipeBook, Restock, Upgrades, ResultDialog, Tutorial

const { useState: useS, useEffect: useE } = React;

const PALETTE = {
  bg: '#F3EBDB', panel: '#FAF3E3', ink: '#2D1B3D', accent: '#C89B3C', mark: '#8E3D52',
};

function App() {
  const [state, setState] = useS(() => loadGame());
  useE(() => { saveGame(state); }, [state]);

  const [mix, setMix] = useS({});
  const [recipeOpen, setRecipeOpen] = useS(false);
  const [restockOpen, setRestockOpen] = useS(false);
  const [upgradeOpen, setUpgradeOpen] = useS(false);
  const [settingsOpen, setSettingsOpen] = useS(false);

  // Persisted player settings
  const DEFAULT_SETTINGS = { hardMode: false, soundOn: true, musicOn: true, devMode: false };
  const [settings, setSettings] = useS(() => {
    try {
      const s = JSON.parse(localStorage.getItem('scent-alchemist-settings-v1'));
      return { ...DEFAULT_SETTINGS, ...(s || {}) };
    } catch (e) { return { ...DEFAULT_SETTINGS }; }
  });
  useE(() => {
    try { localStorage.setItem('scent-alchemist-settings-v1', JSON.stringify(settings)); }
    catch (e) {}
  }, [settings]);

  // Start-menu screen gating: 'menu' on load, 'game' after Continue/New Game.
  const hasSavedGame = () => {
    try { return localStorage.getItem('scent-alchemist-save-v1') !== null; }
    catch (e) { return false; }
  };
  const [screen, setScreen] = useS('menu');

  const [customer, setCustomer] = useS(null);
  const [timeLeft, setTimeLeft] = useS(0);
  const [totalTime, setTotalTime] = useS(1);
  const [custAnim, setCustAnim] = useS('idle');

  const [result, setResult] = useS(null);
  const [floaters, setFloaters] = useS([]);
  const [bouncing, setBouncing] = useS(null);
  const [shake, setShake] = useS(false);
  const [lastTap, setLastTap] = useS(null);

  // Serving sequence: null | 'pour' | 'seal' | 'age' | 'break'
  const [servePhase, setServePhase] = useS(null);
  const [pendingResult, setPendingResult] = useS(null);
  const [sceneColor, setSceneColor] = useS('#C89B3C');

  // Rescue + day report
  const [rescueOpen, setRescueOpen] = useS(false);
  const EMPTY_DAILY = { served: 0, perfect: 0, failed: 0, payEarned: 0, tipsEarned: 0, repChange: 0, recipeSales: {}, bottlesUsed: 0, rescueUsed: false };
  const [dailyStats, setDailyStats] = useS(() => ({ ...EMPTY_DAILY }));
  const [dayReport, setDayReport] = useS(null);
  const prevDayRef = React.useRef(null);
  React.useEffect(() => {
    if (prevDayRef.current === null) { prevDayRef.current = state.day; return; }
    if (state.day !== prevDayRef.current) {
      setDayReport({ day: prevDayRef.current, ...dailyStats });
      setDailyStats({ ...EMPTY_DAILY });
      prevDayRef.current = state.day;
    }
  }, [state.day]);

  const [tutStep, setTutStep] = useS(() => {
    try { return localStorage.getItem('scent-alchemist-tut-done') ? null : 0; }
    catch (e) { return 0; }
  });

  const capacity = state.ownedUpgrades.storage ? 60 : 40;
  const patienceBonus = state.ownedUpgrades.mixer ? 1.15 : 1.0;
  const patienceFlat = (state.ownedUpgrades.cushion ? 10 : 0) + (state.ownedUpgrades.counter ? 3 : 0);
  const idleRate = state.ownedUpgrades.assistant ? 2 : 0;
  const hasFunnel = !!state.ownedUpgrades.funnel;
  const hasDecanter = !!state.ownedUpgrades.decanter;
  // Brass Pipette — each tap adds half a drop (finer ratio control)
  const pourSize = state.ownedUpgrades.cup ? 0.5 : 1.0;
  // Decor multipliers
  const registerCoinMul = state.ownedUpgrades.register ? 1.10 : 1.0;
  const neonTipMul      = state.ownedUpgrades.neon     ? 1.15 : 1.0;
  const shelfForgive    = state.ownedUpgrades.shelf    ? 5    : 0;   // minQuality threshold relaxed

  const lowStock = INGREDIENTS.some(i => state.stock[i.id] <= 3);

  function spawnCustomer() {
    const c = pickCustomer(state.reputation, state.day, state.ownedUpgrades);
    const pat = Math.max(15, Math.round(c.patience * patienceBonus) + patienceFlat);
    setCustomer(c);
    setTimeLeft(pat);
    setTotalTime(pat);
    setMix({});
    setResult(null);
    setCustAnim('enter');
    setTimeout(() => setCustAnim('idle'), 700);
  }
  useE(() => {
    if (screen !== 'game') return;
    if (!customer && !result && tutStep === null) spawnCustomer();
  }, [customer, result, tutStep, screen]);

  useE(() => {
    // Patience freezes while a serving scene is playing — the result is locked in.
    if (screen !== 'game') return;
    if (!customer || result || tutStep !== null || servePhase !== null) return;
    const id = setInterval(() => {
      setTimeLeft(tl => {
        if (tl <= 0.2) { clearInterval(id); onTimeOut(); return 0; }
        return tl - 0.2;
      });
    }, 200);
    return () => clearInterval(id);
  }, [customer, result, tutStep, servePhase]);

  useE(() => {
    if (!idleRate) return;
    const id = setInterval(() => {
      setState(s => ({ ...s, coins: s.coins + idleRate, idleBank: s.idleBank + idleRate }));
      pushFloater('+' + idleRate + ' idle', 'coin', 60, 80, '#C89B3C');
    }, 6000);
    return () => clearInterval(id);
  }, [idleRate]);

  function pushFloater(text, kind, x, y, color) {
    const id = Math.random().toString(36).slice(2);
    setFloaters(fs => [...fs, { id, text, kind, x, y, color }]);
    setTimeout(() => setFloaters(fs => fs.filter(f => f.id !== id)), 1400);
  }

  function onTapIngredient(id) {
    if (!customer || result || servePhase !== null) return;
    if ((state.stock[id] || 0) < pourSize) return;
    // Flask is full — don't burn stock just for taps.
    if (totalUnits(mix) + pourSize > MAX_DROPS) {
      pushFloater('Flask is full!', 'warn', 70, 260, '#A35B3D');
      setShake(true); setTimeout(() => setShake(false), 280);
      return;
    }
    const ing = ingredientById(id);
    setMix(m => ({ ...m, [id]: (m[id] || 0) + pourSize }));
    setState(s => ({ ...s, stock: { ...s.stock, [id]: Math.max(0, (s.stock[id] || 0) - pourSize) } }));
    setBouncing(id);
    setLastTap({ id, color: ing.color });
    setTimeout(() => setBouncing(null), 220);
    setTimeout(() => setLastTap(null), 320);
  }

  // Refund half the used essences back to stock (rounded down per ingredient).
  function refundMix(currentMix) {
    if (!hasFunnel) return;
    setState(s => {
      const ns = { ...s, stock: { ...s.stock } };
      for (const [k, v] of Object.entries(currentMix)) {
        const back = Math.floor(v * 0.5);
        if (back > 0) ns.stock[k] = Math.min(capacity, (ns.stock[k] || 0) + back);
      }
      return ns;
    });
    pushFloater('+½ essences refunded', 'warn', 80, 300, '#6E8E3E');
  }

  function onServe() {
    if (!customer || servePhase !== null) return;
    const recipe = customer.recipe;
    const total = totalUnits(mix);
    if (total < 6) {
      pushFloater('Add more drops!', 'warn', 160, 360, '#A35B3D');
      setShake(true); setTimeout(() => setShake(false), 350);
      return;
    }
    if (state.stock.bottle <= 0) {
      pushFloater('Need a bottle!', 'warn', 160, 360, '#A35B3D');
      setShake(true); setTimeout(() => setShake(false), 350);
      return;
    }
    const score = computeAccuracy(mix, recipe);
    const effectiveMinQuality = Math.max(40, customer.minQuality - shelfForgive);
    const minOk = score >= effectiveMinQuality;
    let tierInfo = tierFor(score);
    let reason = null;

    if (!minOk && score >= 50) {
      reason = `Below customer's standard (needs ${effectiveMinQuality}%)`;
      tierInfo = { ...tierInfo, payMul: tierInfo.payMul * 0.5, tipMul: 0, repGain: Math.min(0, tierInfo.repGain - 1) };
    }

    const hardMul = settings.hardMode ? 1.2 : 1.0;
    const decanterTipMul = (hasDecanter && tierInfo.tier === 'Perfect Blend') ? 1.5 : 1.0;

    const payOut = Math.round(customer.pay * tierInfo.payMul * hardMul * registerCoinMul);
    const tipOut = Math.round(customer.tip * tierInfo.tipMul * hardMul * decanterTipMul * neonTipMul);
    const repDelta = tierInfo.repGain;

    const review = pickReview(tierInfo.tier, customer.type.id, customer.recipe.name);

    if (tierInfo.tier === 'Failed') refundMix(mix);

    // Apply economy & stock right away
    setState(s => ({
      ...s,
      coins: s.coins + payOut + tipOut,
      reputation: Math.max(0, s.reputation + repDelta),
      stock: { ...s.stock, bottle: Math.max(0, s.stock.bottle - 1) },
      orderNumber: s.orderNumber + 1,
      day: s.orderNumber % 5 === 0 ? s.day + 1 : s.day,
    }));

    // Stash the result; the ServingScene will reveal it when its animation completes.
    const r = { tier: tierInfo.tier, score, payOut, tipOut, repDelta,
                color: tierInfo.color, glow: tierInfo.glow, reason, review,
                blindReveal: customer.blind ? customer.recipe.name : null };
    setPendingResult(r);

    // Day-end stats
    setDailyStats(d => ({
      ...d,
      served: d.served + 1,
      perfect: d.perfect + (tierInfo.tier === 'Perfect Blend' ? 1 : 0),
      failed: d.failed + (tierInfo.tier === 'Failed' ? 1 : 0),
      payEarned: d.payEarned + payOut,
      tipsEarned: d.tipsEarned + tipOut,
      repChange: d.repChange + repDelta,
      bottlesUsed: d.bottlesUsed + 1,
      recipeSales: { ...d.recipeSales, [customer.recipe.id]: (d.recipeSales[customer.recipe.id] || 0) + 1 },
    }));
    const c = mixColor(mix);
    setSceneColor(`rgb(${Math.round(c.r)}, ${Math.round(c.g)}, ${Math.round(c.b)})`);
    setServePhase(tierInfo.tier === 'Failed' ? 'break' : 'pour');
    setCustAnim(payOut > 0 ? (tierInfo.tier === 'Perfect Blend' ? 'happy' : 'idle') :
                              tierInfo.tier === 'Failed' ? 'angry' : 'sad');
  }

  function onSceneAdvance() {
    setServePhase(p => p === 'pour' ? 'seal' : p === 'seal' ? 'age' : p);
  }
  function onSceneDone() {
    const r = pendingResult;
    setServePhase(null);
    setPendingResult(null);
    setResult(r);
    if (!r) return;
    if (r.payOut > 0) pushFloater(`+✦ ${r.payOut + r.tipOut}`, 'coin', 70, 80, '#C89B3C');
    if (r.tier === 'Perfect Blend') pushFloater('Perfect Blend ✦', 'perfect', 160, 200, '#2B7A4B');
    if (r.tier === 'Failed') { setShake(true); setTimeout(() => setShake(false), 400); }
  }

  function onTimeOut() {
    if (!customer || result) return;
    refundMix(mix); // funnel saves what we can
    setResult({ tier: 'Failed', score: 0, payOut: 0, tipOut: 0, repDelta: -1,
                color: '#7A2D2D', glow: '#D49B9B', reason: 'Customer ran out of patience',
                review: pickReview('Failed', customer.type.id, customer.recipe.name),
                blindReveal: customer.blind ? customer.recipe.name : null });
    setCustAnim('angry');
    setState(s => ({
      ...s,
      reputation: Math.max(0, s.reputation - 1),
      orderNumber: s.orderNumber + 1,
      day: s.orderNumber % 5 === 0 ? s.day + 1 : s.day,
    }));
    setDailyStats(d => ({ ...d, failed: d.failed + 1, repChange: d.repChange - 1 }));
    setShake(true); setTimeout(() => setShake(false), 400);
  }

  // Pour back one drop of a given ingredient — counts as the day's rescue.
  function onPourBack(ingId) {
    if (!customer || result || servePhase !== null) return;
    if (dailyStats.rescueUsed) {
      pushFloater('rescue used today', 'warn', 60, 320, '#A35B3D');
      return;
    }
    if (!mix[ingId] || mix[ingId] <= 0) return;
    const refund = Math.min(mix[ingId], pourSize); // match pour granularity
    setMix(m => {
      const next = { ...m };
      next[ingId] = Math.max(0, (next[ingId] || 0) - refund);
      if (next[ingId] < 0.001) delete next[ingId];
      return next;
    });
    setState(s => ({ ...s, stock: { ...s.stock, [ingId]: Math.min(capacity, (s.stock[ingId] || 0) + refund) } }));
    setDailyStats(d => ({ ...d, rescueUsed: true }));
    pushFloater('−1 drop · rescue used', 'warn', 60, 280, '#6E8E3E');
    setRescueOpen(false);
  }

  // Sell whatever you have as a budget perfume — counts as the day's rescue.
  function onSellBudget() {
    if (!customer || result || servePhase !== null) return;
    if (dailyStats.rescueUsed) {
      pushFloater('rescue used today', 'warn', 60, 320, '#A35B3D');
      return;
    }
    if (totalUnits(mix) < 4) {
      pushFloater('not enough to bottle', 'warn', 80, 360, '#A35B3D');
      return;
    }
    if (state.stock.bottle <= 0) {
      pushFloater('Need a bottle!', 'warn', 80, 360, '#A35B3D');
      return;
    }
    const payOut = Math.round(customer.recipe.base * 0.4);
    setState(s => ({
      ...s,
      coins: s.coins + payOut,
      stock: { ...s.stock, bottle: Math.max(0, s.stock.bottle - 1) },
      orderNumber: s.orderNumber + 1,
      day: s.orderNumber % 5 === 0 ? s.day + 1 : s.day,
    }));
    setDailyStats(d => ({
      ...d,
      served: d.served + 1,
      payEarned: d.payEarned + payOut,
      bottlesUsed: d.bottlesUsed + 1,
      rescueUsed: true,
      recipeSales: { ...d.recipeSales, [customer.recipe.id]: (d.recipeSales[customer.recipe.id] || 0) + 1 },
    }));
    setRescueOpen(false);
    setResult({
      tier: 'Budget Sale', score: 0, payOut, tipOut: 0, repDelta: 0,
      color: '#8A6A2E', glow: '#E8C99D',
      reason: 'sold as a budget perfume — no reputation gained',
      review: pickReview('Budget Sale', customer.type.id, customer.recipe.name),
      blindReveal: customer.blind ? customer.recipe.name : null,
    });
    setCustAnim('idle');
  }

  function onReset() {
    // A reset means dumping the partly-made perfume — the bottle goes with it.
    if (state.stock.bottle > 0) {
      setState(s => ({ ...s, stock: { ...s.stock, bottle: Math.max(0, s.stock.bottle - 1) } }));
      pushFloater('mixture & bottle discarded', 'warn', 80, 360, '#7A6A4C');
    } else {
      pushFloater('mixture cleared (no bottle to lose)', 'warn', 60, 360, '#7A6A4C');
    }
    setMix({});
  }

  function onNextCustomer() {
    setCustomer(null);
    setResult(null);
    setMix({});
  }

  function onBuy(ingId, qty) {
    const ing = ingredientById(ingId);
    const cost = ing.price * qty;
    if (state.coins < cost) return;
    if (state.stock[ingId] + qty > capacity) qty = capacity - state.stock[ingId];
    if (qty <= 0) return;
    setState(s => ({
      ...s,
      coins: s.coins - ing.price * qty,
      stock: { ...s.stock, [ingId]: Math.min(capacity, s.stock[ingId] + qty) },
    }));
  }
  function onBundle() {
    const totalCost = Math.round(Object.entries(RESTOCK_BUNDLE).reduce(
      (sum, [k, v]) => sum + v * ingredientById(k).price * 0.85, 0));
    if (state.coins < totalCost) return;
    setState(s => {
      const ns = { ...s, coins: s.coins - totalCost, stock: { ...s.stock } };
      for (const [k, v] of Object.entries(RESTOCK_BUNDLE)) {
        ns.stock[k] = Math.min(capacity, ns.stock[k] + v);
      }
      return ns;
    });
  }

  // ── Dev tools (testing-only shortcuts; gated by settings.devMode) ─
  function devGiveCoins() { setState(s => ({ ...s, coins: s.coins + 1000 })); pushFloater('+✦ 1000 (dev)', 'coin', 60, 80, '#C89B3C'); }
  function devGiveRep()   { setState(s => ({ ...s, reputation: s.reputation + 1 })); pushFloater('+◆ 1 (dev)', 'coin', 60, 110, '#8E3D52'); }

  // ── Menu actions ─────────────────────────────────────────
  function onMenuContinue() {
    setScreen('game');
  }
  function onNewGame() {
    try { localStorage.removeItem('scent-alchemist-save-v1'); } catch (e) {}
    try { localStorage.removeItem('scent-alchemist-tut-done'); } catch (e) {}
    setState({ ...DEFAULT_STATE });
    setCustomer(null);
    setResult(null);
    setMix({});
    setDailyStats({ ...EMPTY_DAILY });
    setDayReport(null);
    setTutStep(0);
    setScreen('game');
  }
  function onBackToMenu() {
    setScreen('menu');
  }

  function onBuyUpgrade(id) {
    const u = UPGRADES.find(x => x.id === id);
    if (!u || state.ownedUpgrades[id] || state.coins < u.cost) return;
    setState(s => {
      const ns = { ...s, coins: s.coins - u.cost, ownedUpgrades: { ...s.ownedUpgrades, [id]: true } };
      if (id === 'book') ns.unlockedRecipes = { ...ns.unlockedRecipes, mirage: true, lumen: true };
      return ns;
    });
  }

  function dismissTutorial() {
    setTutStep(null);
    try { localStorage.setItem('scent-alchemist-tut-done', '1'); } catch (e) {}
  }

  const cssVars = {
    '--bg': PALETTE.bg,
    '--panel': PALETTE.panel,
    '--ink': PALETTE.ink,
    '--accent': PALETTE.accent,
    '--mark': PALETTE.mark,
    '--serif': `'Cormorant Garamond', Georgia, serif`,
  };

  if (screen === 'menu') {
    return (
      <div className="sa-root" style={cssVars}>
        <StartScreen
          hasSave={hasSavedGame()}
          onContinue={onMenuContinue}
          onNewGame={onNewGame}
          onSettings={() => setSettingsOpen(true)}
        />
        {settingsOpen && (
          <Settings
            settings={settings}
            onChange={setSettings}
            onClose={() => setSettingsOpen(false)}
            onResetSave={() => {
              try { localStorage.removeItem('scent-alchemist-save-v1'); } catch (e) {}
              try { localStorage.removeItem('scent-alchemist-tut-done'); } catch (e) {}
              setState({ ...DEFAULT_STATE });
              setCustomer(null); setResult(null); setMix({});
              setTutStep(0);
              setSettingsOpen(false);
            }}
          />
        )}
      </div>
    );
  }

  return (
    <div className="sa-root" style={cssVars}>
      <div className="bg-warm" />
      <Hud state={state} lowStock={lowStock} hardMode={settings.hardMode}
           onWarn={() => setRestockOpen(true)}
           onSettings={() => setSettingsOpen(true)}
           onMenu={onBackToMenu} />

      <CustomerPanel customer={customer} timeLeft={timeLeft} totalTime={totalTime}
                     animState={custAnim} ownedUpgrades={state.ownedUpgrades} />

      <div className="workshop">
        <div className="workshop-shelf" aria-hidden>
          <div className="shelf-bar" />
          <div className="shelf-jars">
            <span className="jar" style={{ background: '#E8C99D' }} />
            <span className="jar" style={{ background: '#E89BB4' }} />
            <span className="jar" style={{ background: '#5D3A6B' }} />
            <span className="jar" style={{ background: '#8B5A3C' }} />
            <span className="jar" style={{ background: '#F5C242' }} />
          </div>
          <div className="shelf-bar" />
        </div>

        <FlaskView mix={mix} target={customer?.recipe}
                   recipeOpen={!settings.hardMode && !customer?.blind}
                   lastTap={lastTap} shake={shake} />
      </div>

      <IngredientRow stock={state.stock} onTap={onTapIngredient} bouncing={bouncing}
                     dim={!customer || !!result} />

      <div className="action-bar action-bar-6">
        <ActionBtn icon="❡" label="Book"    onClick={() => setRecipeOpen(true)} />
        <ActionBtn icon="⛑" label={dailyStats.rescueUsed ? 'Used' : 'Rescue'}
                   onClick={() => setRescueOpen(true)}
                   disabled={!customer || result || servePhase !== null || totalUnits(mix) === 0 || dailyStats.rescueUsed} />
        <ActionBtn icon="↻" label="Reset"   onClick={onReset} disabled={totalUnits(mix) === 0} />
        <ActionBtn icon="✦" label="Serve"   primary onClick={onServe} disabled={!customer || result || servePhase !== null || totalUnits(mix) < 6}
                   badge={state.stock.bottle} badgeLow={state.stock.bottle <= 3} badgeOut={state.stock.bottle <= 0} />
        <ActionBtn icon="▤" label="Stock"   onClick={() => setRestockOpen(true)} alert={lowStock} />
        <ActionBtn icon="⌬" label="Upgrade" onClick={() => setUpgradeOpen(true)} />
      </div>

      <Floaters items={floaters} />

      {recipeOpen && (
        <RecipeBook unlocked={state.unlockedRecipes} reputation={state.reputation}
                    activeId={customer?.blind ? null : customer?.recipe.id}
                    onClose={() => setRecipeOpen(false)} />
      )}
      {restockOpen && (
        <Restock stock={state.stock} coins={state.coins} capacity={capacity}
                 onClose={() => setRestockOpen(false)} onBuy={onBuy} onBundle={onBundle} />
      )}
      {upgradeOpen && (
        <Upgrades owned={state.ownedUpgrades} coins={state.coins}
                  onClose={() => setUpgradeOpen(false)} onBuy={onBuyUpgrade}
                  onDev={settings.devMode ? { coins: devGiveCoins, rep: devGiveRep } : null} />
      )}
      {rescueOpen && (
        <Rescue mix={mix} onClose={() => setRescueOpen(false)}
                onPourBack={onPourBack} onSellBudget={onSellBudget}
                budgetPay={customer ? Math.round(customer.recipe.base * 0.4) : 0}
                canSellBudget={!!customer && totalUnits(mix) >= 4 && state.stock.bottle > 0 && !dailyStats.rescueUsed}
                rescueUsed={dailyStats.rescueUsed} />
      )}
      {dayReport && (
        <DayReport report={dayReport}
                   onClose={() => setDayReport(null)}
                   onRestock={() => { setDayReport(null); setRestockOpen(true); }}
                   onUpgrades={() => { setDayReport(null); setUpgradeOpen(true); }} />
      )}
      {servePhase && (
        <ServingScene phase={servePhase} color={sceneColor}
                      onAdvance={onSceneAdvance} onDone={onSceneDone} />
      )}
      {settingsOpen && (
        <Settings
          settings={settings}
          onChange={setSettings}
          onClose={() => setSettingsOpen(false)}
          onResetSave={() => {
            try { localStorage.removeItem('scent-alchemist-save-v1'); } catch (e) {}
            try { localStorage.removeItem('scent-alchemist-tut-done'); } catch (e) {}
            setState({ ...DEFAULT_STATE });
            setCustomer(null); setResult(null); setMix({});
            setTutStep(0);
            setSettingsOpen(false);
          }}
        />
      )}
      {result && <ResultDialog result={result} onContinue={onNextCustomer} />}
      {tutStep !== null && (
        <Tutorial step={tutStep}
                  onNext={() => {
                    if (tutStep + 1 >= 4) dismissTutorial();
                    else setTutStep(tutStep + 1);
                  }}
                  onSkip={dismissTutorial} />
      )}
    </div>
  );
}

function ActionBtn({ icon, label, onClick, primary, disabled, alert, badge, badgeLow, badgeOut }) {
  return (
    <button className={`act ${primary ? 'act-primary' : ''} ${alert ? 'act-alert' : ''}`}
            onClick={onClick} disabled={disabled}>
      <span className="act-ico">{icon}</span>
      <span className="act-lbl">{label}</span>
      {badge !== undefined && (
        <span className={`act-badge ${badgeOut ? 'out' : badgeLow ? 'low' : ''}`}
              title={`${badge} bottle${badge === 1 ? '' : 's'} in stock`}>
          🍶 {badge}
        </span>
      )}
    </button>
  );
}

window.App = App;
