// group.jsx — Flat 2BHK
//   · group identity + your balance hero (₹ amount + 14d sparkline) + 3 group stats
//   · expandable settings drawer (tap card to open)
//   · activity rows with split avatars + swipe-left to reveal edit/delete
//   · action mode tweak: 'buttons' (current) or 'gestures' (swipe-up to add, drag-down to settle)

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "actionMode": "buttons"
}/*EDITMODE-END*/;
// actionMode: 'buttons' | 'gestures' | 'radial'

// ─────────────────────────────────────────────────────────────
// Data
// ─────────────────────────────────────────────────────────────
const ME = 'Aarav';
const MEMBERS = [
  { key: 'A', name: 'Aarav', tone: { bg: '#D6EAE2', fg: '#0A5F4F' }, role: 'admin', isYou: true },
  { key: 'R', name: 'Rohan', tone: { bg: '#FADED4', fg: '#8B3722' }, role: 'member' },
  { key: 'P', name: 'Priya', tone: { bg: '#F5E4A8', fg: '#6B5208' }, role: 'member' },
];
const toneOf = (name) => MEMBERS.find(m => m.name === name)?.tone || { bg: '#E8DEC5', fg: '#4A443D' };
const memberOf = (name) => MEMBERS.find(m => m.name === name);

const GROUP = {
  emoji: '⌂',
  name: 'Flat 2BHK',
  thisMonth: 4000,
  splits: 5,
  trendPct: 12,            // ↑ vs last month
  age: '8 mo',
  nextUp: { label: 'Rent', emoji: '🏠', dueInDays: 3, amount: 1800 },
  topCat: { label: 'groceries', emoji: '🛒', amount: 1600 },
};

// Your 14-day balance trajectory in THIS group (in ₹, signed: +owed, −owe).
// Shape tells the story: started ahead → maintenance + brunch dragged it negative →
// internet bill bumped it briefly → today's groceries pulled it back to −860.
const SPARK_DATA = [120, 180, 200, 160, 50, -180, -260, -510, -600, -780, 20, -340, -640, -860];

const TXNS = [
  { day: 'today', items: [
    { id: 'gro', title: 'Groceries — Big Basket', cat: '🛒',
      payer: 'Rohan', participants: ['Aarav', 'Rohan', 'Priya'],
      gross: 1240, myShare: { side: 'coral', amt: 413 } },
  ]},
  { day: 'yesterday', items: [
    { id: 'net', title: 'Internet bill — Airtel', cat: '📶',
      payer: 'Aarav', participants: ['Aarav', 'Rohan', 'Priya'],
      gross: 999, myShare: { side: 'teal', amt: 666 } },
  ]},
  { day: '3 days ago', items: [
    { id: 'mnt', title: 'Building maintenance', cat: '🔧',
      payer: 'Priya', participants: ['Aarav', 'Rohan', 'Priya'],
      gross: 600, myShare: { side: 'coral', amt: 200 } },
    { id: 'sun', title: 'Sunday brunch', cat: '🥞',
      payer: 'Rohan', participants: ['Aarav', 'Rohan', 'Priya'],
      gross: 1800, myShare: { side: 'coral', amt: 600 } },
  ]},
  { day: 'last week', items: [
    { id: 'chai', title: 'Morning chai run', cat: '🍵',
      payer: 'Aarav', participants: ['Aarav', 'Rohan', 'Priya'],
      gross: 120, myShare: { side: 'teal', amt: 80 } },
    { id: 'dish', title: 'Dishwasher liquid', cat: '🧴',
      payer: 'Rohan', participants: ['Aarav', 'Rohan'],
      gross: 240, myShare: { side: 'coral', amt: 120 } },
  ]},
];

const FLOWS = [
  { id: 'a-r', from: 'Aarav', to: 'Rohan', amount: 680, side: 'coral', note: '2 expenses' },
  { id: 'a-p', from: 'Aarav', to: 'Priya', amount: 180, side: 'coral', note: 'net of internet' },
];

// ─────────────────────────────────────────────────────────────
// Top nav
// ─────────────────────────────────────────────────────────────
function TopNav() {
  return (
    <div className="topnav">
      <a className="nav-icon" href="Home.html" aria-label="back to home">
        <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
          <path d="M10.5 13L5.5 8L10.5 3" stroke="currentColor" strokeWidth="1.6"
            strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </a>
      <div className="nav-title">{GROUP.name}</div>
      <button className="nav-icon" aria-label="more">
        <span className="dots" aria-hidden="true"><span /><span /><span /></span>
      </button>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Smooth path through points (catmull-rom → cubic bezier)
// ─────────────────────────────────────────────────────────────
function smoothPath(points) {
  if (!points.length) return '';
  let d = `M ${points[0].x.toFixed(2)},${points[0].y.toFixed(2)}`;
  for (let i = 1; i < points.length; i++) {
    const p0 = points[Math.max(0, i - 2)];
    const p1 = points[i - 1];
    const p2 = points[i];
    const p3 = points[Math.min(points.length - 1, i + 1)];
    const c1x = p1.x + (p2.x - p0.x) / 6;
    const c1y = p1.y + (p2.y - p0.y) / 6;
    const c2x = p2.x - (p3.x - p1.x) / 6;
    const c2y = p2.y - (p3.y - p1.y) / 6;
    d += ` C ${c1x.toFixed(2)},${c1y.toFixed(2)} ${c2x.toFixed(2)},${c2y.toFixed(2)} ${p2.x.toFixed(2)},${p2.y.toFixed(2)}`;
  }
  return d;
}

// ─────────────────────────────────────────────────────────────
// BalanceSparkline — your balance trajectory in this group
// Bigger, bolder, with HTML-overlay labels so they stay readable
// (the SVG itself uses preserveAspectRatio="none" so the curve fills
// the container — but text inside would get stretched, hence overlay).
// ─────────────────────────────────────────────────────────────
function BalanceSparkline({ data, currentSide }) {
  const W = 320, H = 72, pad = 10;
  const max = Math.max(...data, 0);
  const min = Math.min(...data, 0);
  const range = (max - min) || 1;
  const yOf = (v) => pad + ((max - v) / range) * (H - pad * 2);
  const zeroY = yOf(0);
  const pts = data.map((v, i) => ({
    x: (i / (data.length - 1)) * W,
    y: yOf(v),
  }));
  const line = smoothPath(pts);
  const last = pts[pts.length - 1];
  const area = `${line} L ${last.x},${zeroY} L 0,${zeroY} Z`;
  const stroke = currentSide === 'teal' ? 'var(--teal)' : 'var(--coral)';
  // overlay label positions (as % of H so they survive any container scaling)
  const zeroPct = (zeroY / H) * 100;

  return (
    <div className="spark-wrap" aria-hidden="true">
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none">
        <defs>
          <clipPath id="sparkAbove" clipPathUnits="userSpaceOnUse">
            <rect x="0" y="0" width={W} height={zeroY} />
          </clipPath>
          <clipPath id="sparkBelow" clipPathUnits="userSpaceOnUse">
            <rect x="0" y={zeroY} width={W} height={H - zeroY} />
          </clipPath>
        </defs>
        {/* area fills — split at zero line */}
        <path d={area} fill="var(--teal-pale)"  opacity="0.7" clipPath="url(#sparkAbove)" />
        <path d={area} fill="var(--coral-pale)" opacity="0.6" clipPath="url(#sparkBelow)" />
        {/* zero reference */}
        <line x1="0" y1={zeroY} x2={W} y2={zeroY}
          stroke="var(--ink-mute)" strokeDasharray="3 4" strokeWidth="0.8" opacity="0.55"
          vectorEffect="non-scaling-stroke" />
        {/* the trajectory */}
        <path className="spark-line" d={line} fill="none" stroke={stroke}
          strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
          vectorEffect="non-scaling-stroke" />
        {/* end dot — paper-ringed circle with a quiet pulse halo */}
        <circle className="spark-end-pulse" cx={last.x} cy={last.y} r="6" fill={stroke}
          vectorEffect="non-scaling-stroke" />
        <g className="spark-end" style={{ transformOrigin: `${last.x}px ${last.y}px`, transformBox: 'fill-box' }}>
          <circle cx={last.x} cy={last.y} r="5" fill={stroke}
            stroke="var(--paper)" strokeWidth="2.5"
            vectorEffect="non-scaling-stroke" />
        </g>
      </svg>

      {/* HTML-overlay labels (don't stretch with the SVG) */}
      <div className="spark-labels">
        <span className="spark-zero" style={{ top: `${zeroPct}%` }}>₹0</span>
        {max > 0 && <span className="spark-extreme hi">+₹{max.toLocaleString('en-IN')}</span>}
        {min < 0 && <span className="spark-extreme lo">−₹{Math.abs(min).toLocaleString('en-IN')}</span>}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// MonthPlateViz — who paid this month (slide 1 of viz carousel)
// Horizontal ribbon segmented by payer; widths = share. Neutral framing—
// shows the fairness story of the group, not your owe/owed position.
// ─────────────────────────────────────────────────────────────
function MonthPlateViz() {
  const byPayer = TXNS.flatMap(d => d.items).reduce((acc, it) => {
    acc[it.payer] = (acc[it.payer] || 0) + it.gross;
    return acc;
  }, {});
  const total = Object.values(byPayer).reduce((s, v) => s + v, 0) || 1;
  const ordered = [
    ...MEMBERS.filter(m => m.isYou),
    ...MEMBERS.filter(m => !m.isYou).sort((a, b) => (byPayer[b.name] || 0) - (byPayer[a.name] || 0)),
  ].map(m => ({
    ...m,
    paid: byPayer[m.name] || 0,
    pct:  (byPayer[m.name] || 0) / total,
  }));

  return (
    <div className="month-plate">
      <div className="plate" aria-label="who paid this month">
        {ordered.map((m, i) => (
          <div
            key={m.key}
            className="plate-seg"
            style={{
              flex: m.pct,
              background: m.tone.bg,
              color: m.tone.fg,
              animationDelay: `${i * 80}ms`,
            }}
            title={`${m.name} paid ₹${m.paid.toLocaleString('en-IN')} (${Math.round(m.pct * 100)}%)`}
          >
            <span className="pinit">{m.key}</span>
            <span className="pshare">{Math.round(m.pct * 100)}%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// VizCarousel — swipeable between plate (slide 0) and sparkline (slide 1)
// ─────────────────────────────────────────────────────────────
function VizCarousel({ owedSide }) {
  const [idx, setIdx] = React.useState(0);
  const [drag, setDrag] = React.useState(null);
  const ref = React.useRef(null);

  const onDown = (e) => {
    if (e.target.closest('button')) return;
    e.currentTarget.setPointerCapture?.(e.pointerId);
    setDrag({ startX: e.clientX, startY: e.clientY, dx: 0, dy: 0, locked: null, pid: e.pointerId });
  };
  const onMove = (e) => {
    if (!drag || drag.pid !== e.pointerId) return;
    const dx = e.clientX - drag.startX;
    const dy = e.clientY - drag.startY;
    let locked = drag.locked;
    if (!locked && (Math.abs(dx) > 6 || Math.abs(dy) > 6)) {
      locked = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
    }
    setDrag({ ...drag, dx, dy, locked });
  };
  const onUp = () => {
    if (!drag) return;
    const w = ref.current?.clientWidth || 1;
    if (drag.locked === 'x') {
      const threshold = w * 0.18;
      if (drag.dx < -threshold && idx === 0) setIdx(1);
      else if (drag.dx > threshold && idx === 1) setIdx(0);
    }
    setDrag(null);
  };

  const w = ref.current?.clientWidth || 1;
  const dragPct = drag?.locked === 'x' ? (drag.dx / w) * 50 : 0;
  const clampedDrag = idx === 0 ? Math.min(0, dragPct) : Math.max(0, dragPct);
  const tx = -idx * 50 + clampedDrag;

  return (
    <div
      ref={ref}
      className="viz-carousel"
      onPointerDown={onDown}
      onPointerMove={onMove}
      onPointerUp={onUp}
      onPointerCancel={onUp}
    >
      <div className="viz-dots">
        {[0, 1].map((i) => (
          <button
            key={i}
            className={`viz-dot ${i === idx ? 'on' : ''}`}
            onClick={(e) => { e.stopPropagation(); setIdx(i); }}
            aria-label={`visualization ${i + 1}`}
          />
        ))}
      </div>
      <div
        className={`viz-track ${drag?.locked === 'x' ? '' : 'snap'}`}
        style={{ transform: `translateX(${tx}%)` }}
      >
        <div className="viz-slide">
          <MonthPlateViz />
        </div>
        <div className="viz-slide">
          <BalanceSparkline data={SPARK_DATA} currentSide={owedSide} />
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// StatsRow — 3 group insights, flat typographic style
// ─────────────────────────────────────────────────────────────
function StatsRow() {
  const trendCls = GROUP.trendPct >= 0 ? 'up' : 'down';
  return (
    <div className="stats-row">
      <div className="stat">
        <div className="eb">this mo</div>
        <div className="val">
          <span className="num">₹{(GROUP.thisMonth / 1000).toFixed(1)}k</span>
          <span className={`trend ${trendCls}`}>
            {GROUP.trendPct >= 0 ? '↑' : '↓'}{Math.abs(GROUP.trendPct)}%
          </span>
        </div>
      </div>
      <div className="stat">
        <div className="eb">next up</div>
        <div className="val">
          <span className="ico">{GROUP.nextUp.emoji}</span>
          <span>{GROUP.nextUp.label} · {GROUP.nextUp.dueInDays}d</span>
        </div>
      </div>
      <div className="stat">
        <div className="eb">top cat</div>
        <div className="val">
          <span className="ico">{GROUP.topCat.emoji}</span>
          <span className="num">₹{(GROUP.topCat.amount / 1000).toFixed(1)}k</span>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// HEADER CARD — identity + balance hero + stats + expandable settings
// ─────────────────────────────────────────────────────────────
function HeaderCard({ view, setView, expanded, setExpanded, onSettle }) {
  const youOwe = 860;
  const owedSide = 'coral';
  const nextSettle = FLOWS[0];

  const onCardClick = (e) => {
    if (e.target.closest(
      '.group-settings, .seg, button, input, .viz-carousel, .bh-pay-pill'
    )) return;
    setExpanded(!expanded);
  };

  return (
    <div className={`header-wrap reveal r1 ${expanded ? 'expanded' : ''}`}>
      <div
        className={`header-card ${expanded ? 'expanded' : ''}`}
        onClick={onCardClick}
        role="button"
        aria-expanded={expanded}
      >
        {/* identity row — just emoji + name + chevron, no meta line */}
        <div className="group-id">
          <div className="group-emoji">{GROUP.emoji}</div>
          <div className="group-id-text">
            <div className="group-name">{GROUP.name}</div>
          </div>
          <div className="group-expand" aria-hidden="true">
            <svg viewBox="0 0 12 12" fill="none">
              <path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.5"
                strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </div>
        </div>

        {/* balance hero — amount + tappable "pay X" pill (also commits settle) */}
        <div className="balance-hero">
          <div className="bh-row">
            <div className="bh-left">
              <div className="bh-eyebrow">
                {owedSide === 'coral' ? "you owe this group" : "you're owed in this group"}
              </div>
              <span className={`bh-amount ${owedSide}`}>
                <span className="cur">₹</span>{youOwe.toLocaleString('en-IN')}
              </span>
              {nextSettle && (() => {
                const t = toneOf(nextSettle.to);
                return (
                  <button
                    className="bh-pay-pill"
                    onClick={(e) => {
                      e.stopPropagation();
                      onSettle(`settle ₹${nextSettle.amount.toLocaleString('en-IN')} → ${nextSettle.to}`);
                    }}
                  >
                    <span className="bh-pp-av" style={{ background: t.bg, color: t.fg }}>
                      {nextSettle.to[0]}
                    </span>
                    <span>pay {nextSettle.to}</span>
                    <span className="amt">₹{nextSettle.amount.toLocaleString('en-IN')}</span>
                    <span className="arr">→</span>
                  </button>
                );
              })()}
            </div>
          </div>
          <VizCarousel owedSide={owedSide} />
        </div>

        {/* expandable settings drawer */}
        <div className="group-settings" aria-hidden={!expanded}>
          <div className="gs-inner">
            <div>
              <div className="gs-eyebrow">group name</div>
              <div className="gs-field">
                <input defaultValue={GROUP.name} />
                <button className="gs-edit">rename</button>
              </div>
            </div>
            <div>
              <div className="gs-eyebrow">group icon</div>
              <div className="gs-field">
                <span className="gs-emoji-pill">{GROUP.emoji}</span>
                <button className="gs-edit" style={{ flex: 1, textAlign: 'left' }}>change</button>
              </div>
            </div>
            <div>
              <div className="gs-eyebrow">members ({MEMBERS.length})</div>
              <div className="gs-members">
                {MEMBERS.map((m) => (
                  <div className="gs-member" key={m.key}>
                    <span className="av" style={{ background: m.tone.bg, color: m.tone.fg }}>{m.key}</span>
                    <span className="name">{m.name}{m.isYou ? ' (you)' : ''}</span>
                    <span className="role">{m.role}</span>
                  </div>
                ))}
                <button className="gs-add">
                  <span className="plus">＋</span>
                  invite to group
                </button>
              </div>
            </div>
            <div className="gs-row-actions">
              <button className="gs-action">archive</button>
              <button className="gs-action danger">leave group</button>
            </div>
          </div>
        </div>
      </div>

      {/* docked segmented toggle (hidden when expanded) */}
      <div className="seg-dock">
        <SegmentedToggle view={view} setView={setView} />
      </div>
    </div>
  );
}

function SegmentedToggle({ view, setView }) {
  const idx = view === 'balances' ? 1 : 0;
  return (
    <div className="seg" role="tablist" aria-label="view">
      <span className="seg-thumb" style={{ transform: `translateX(${idx * 100}%)` }} />
      <button role="tab" aria-selected={idx === 0}
        className={`seg-btn ${idx === 0 ? 'active' : ''}`}
        onClick={(e) => { e.stopPropagation(); setView('transactions'); }}
      >transactions</button>
      <button role="tab" aria-selected={idx === 1}
        className={`seg-btn ${idx === 1 ? 'active' : ''}`}
        onClick={(e) => { e.stopPropagation(); setView('balances'); }}
      >balances</button>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Panes — horizontal swipe between transactions / balances
// ─────────────────────────────────────────────────────────────
function Panes({ view, setView, children }) {
  const ref = React.useRef(null);
  const [drag, setDrag] = React.useState(null);
  const idx = view === 'balances' ? 1 : 0;

  const onDown = (e) => {
    if (e.target.closest('button, a, input, textarea, [role="tab"], .tx-shell')) return;
    e.currentTarget.setPointerCapture?.(e.pointerId);
    setDrag({ startX: e.clientX, startY: e.clientY, dx: 0, dy: 0, locked: null, pid: e.pointerId });
  };
  const onMove = (e) => {
    if (!drag || drag.pid !== e.pointerId) return;
    const dx = e.clientX - drag.startX;
    const dy = e.clientY - drag.startY;
    let locked = drag.locked;
    if (!locked && (Math.abs(dx) > 6 || Math.abs(dy) > 6)) {
      locked = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
    }
    setDrag({ ...drag, dx, dy, locked });
  };
  const onUp = () => {
    if (!drag) return;
    const w = ref.current?.clientWidth || 1;
    if (drag.locked === 'x') {
      const threshold = w * 0.18;
      if (drag.dx < -threshold && idx === 0) setView('balances');
      else if (drag.dx > threshold && idx === 1) setView('transactions');
    }
    setDrag(null);
  };

  const w = ref.current?.clientWidth || 1;
  const dragPct = drag?.locked === 'x' ? (drag.dx / w) * 50 : 0;
  const clampedDrag = idx === 0 ? Math.min(0, dragPct) : Math.max(0, dragPct);
  const tx = -idx * 50 + clampedDrag;

  return (
    <div ref={ref} className="panes-wrap"
      onPointerDown={onDown} onPointerMove={onMove}
      onPointerUp={onUp} onPointerCancel={onUp}
      style={{ touchAction: 'pan-y' }}
    >
      <div className={`panes ${drag?.locked === 'x' ? 'dragging' : ''}`}
        style={{ transform: `translateX(${tx}%)` }}
      >{children}</div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Transactions
// ─────────────────────────────────────────────────────────────
function TransactionsPane() {
  const [openId, setOpenId] = React.useState(null);
  return (
    <div className="pane">
      {TXNS.map((d) => {
        const total = d.items.reduce((s, it) => s + it.gross, 0);
        return (
          <div className="day" key={d.day}>
            <div className="day-head">
              <span className="day-label">{d.day}</span>
              <span className="day-total">₹{total.toLocaleString('en-IN')}</span>
            </div>
            {d.items.map((it) => (
              <SwipeableRow key={it.id}
                isOpen={openId === it.id}
                onOpen={(v) => setOpenId(v ? it.id : null)}>
                <TxRow it={it} />
              </SwipeableRow>
            ))}
          </div>
        );
      })}
      <div style={{ height: 20 }} />
    </div>
  );
}

function SwipeableRow({ children, isOpen, onOpen }) {
  const REVEAL = 128;
  const [drag, setDrag] = React.useState(null);
  const rowRef = React.useRef(null);

  const onDown = (e) => {
    if (e.target.closest('.tx-actions')) return;
    e.currentTarget.setPointerCapture?.(e.pointerId);
    setDrag({ startX: e.clientX, startY: e.clientY, dx: 0, locked: null, pid: e.pointerId, base: isOpen ? -REVEAL : 0 });
  };
  const onMove = (e) => {
    if (!drag || drag.pid !== e.pointerId) return;
    const dx = e.clientX - drag.startX;
    const dy = e.clientY - drag.startY;
    let locked = drag.locked;
    if (!locked && (Math.abs(dx) > 5 || Math.abs(dy) > 5)) {
      locked = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
    }
    setDrag({ ...drag, dx, dy, locked });
  };
  const onUp = () => {
    if (!drag) return;
    if (drag.locked === 'x') {
      const final = drag.base + drag.dx;
      if (final < -REVEAL * 0.45) onOpen(true);
      else onOpen(false);
    }
    setDrag(null);
  };

  React.useEffect(() => {
    if (!isOpen) return;
    const close = (e) => {
      if (!rowRef.current) return;
      if (!rowRef.current.contains(e.target)) onOpen(false);
    };
    document.addEventListener('pointerdown', close, true);
    return () => document.removeEventListener('pointerdown', close, true);
  }, [isOpen, onOpen]);

  let offset = isOpen ? -REVEAL : 0;
  if (drag?.locked === 'x') {
    offset = Math.max(-REVEAL - 24, Math.min(0, drag.base + drag.dx));
  }
  const isDragging = drag?.locked === 'x';

  return (
    <div className="tx-shell" ref={rowRef}>
      <div className="tx-actions" aria-hidden={!isOpen}>
        <button className="edit"   onClick={() => onOpen(false)}>edit</button>
        <button className="delete" onClick={() => onOpen(false)}>delete</button>
      </div>
      <div className={`tx ${isDragging ? 'dragging' : ''}`}
        style={{ transform: `translateX(${offset}px)` }}
        onPointerDown={onDown} onPointerMove={onMove}
        onPointerUp={onUp} onPointerCancel={onUp}
      >{children}</div>
    </div>
  );
}

function TxRow({ it }) {
  const tone = toneOf(it.payer);
  const youPaid = it.payer === ME;
  const myShare = it.myShare;
  return (
    <>
      <div className="who-av" style={{ background: tone.bg, color: tone.fg }}>
        {it.payer[0]}
        {it.cat && <span className="cat-dot">{it.cat}</span>}
      </div>
      <div className="body">
        <span className="title">{it.title}</span>
        <span className="submeta">
          <span className="payer">{youPaid ? 'you paid' : `${it.payer} paid`}</span>
          <span className="split-avs" aria-label={`${it.participants.length} in split`}>
            {it.participants.map((name) => {
              const m = memberOf(name);
              if (!m) return null;
              return (
                <span key={name}
                  className={`mav ${m.isYou ? 'you' : ''}`}
                  style={{ background: m.tone.bg, color: m.tone.fg }}
                  title={name}
                >{m.key}</span>
              );
            })}
          </span>
        </span>
      </div>
      <div className="price">
        <span className="gross">₹{it.gross.toLocaleString('en-IN')}</span>
        <span className={`share ${myShare.side}`}>
          {myShare.side === 'teal' ? '+' : '−'}₹{myShare.amt.toLocaleString('en-IN')}
        </span>
      </div>
    </>
  );
}

// ─────────────────────────────────────────────────────────────
// Balances pane
// ─────────────────────────────────────────────────────────────
function BalancesPane({ owe, owed }) {
  const net = owed - owe;
  return (
    <div className="pane">
      <p className="bal-intro">
        simplified to the fewest payments — settling these clears everyone.
      </p>
      <div className="bal-list">
        {FLOWS.map((f) => <FlowRow key={f.id} f={f} />)}
      </div>
      <div className="bal-foot">
        <span className="lbl">your net position</span>
        <span className="val" style={{ color: net >= 0 ? 'var(--teal)' : 'var(--ink)' }}>
          {net >= 0 ? '+' : '−'}₹{Math.abs(net).toLocaleString('en-IN')}
        </span>
      </div>
      <div style={{ height: 12 }} />
    </div>
  );
}

function FlowRow({ f }) {
  const youAre = f.from === ME ? 'from' : (f.to === ME ? 'to' : null);
  const fromTone = toneOf(f.from);
  const toTone   = toneOf(f.to);
  const youOwe = youAre === 'from';
  const youGet = youAre === 'to';
  return (
    <div className="flow">
      <div className="endpoints">
        <span className="ep" style={{ background: fromTone.bg, color: fromTone.fg }}>{f.from[0]}</span>
        <span className="arrow" />
        <span className="ep" style={{ background: toTone.bg, color: toTone.fg }}>{f.to[0]}</span>
      </div>
      <div className="summary">
        <span className="line">
          {youOwe ? (
            <>you pay <strong>{f.to}</strong> <span className={`amt ${f.side}`}>₹{f.amount.toLocaleString('en-IN')}</span></>
          ) : youGet ? (
            <><strong>{f.from}</strong> pays you <span className={`amt ${f.side}`}>₹{f.amount.toLocaleString('en-IN')}</span></>
          ) : (
            <><strong>{f.from}</strong> pays <strong>{f.to}</strong> <span className={`amt ${f.side}`}>₹{f.amount.toLocaleString('en-IN')}</span></>
          )}
        </span>
        <span className="note">{f.note}</span>
      </div>
      {youOwe ? (
        <button className="pill solid">settle</button>
      ) : youGet ? (
        <button className="pill ghost">remind</button>
      ) : (
        <button className="pill ghost">note</button>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Action bar (buttons mode)
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// FloatingFab (buttons mode) — single floating + button at bottom-center
// ─────────────────────────────────────────────────────────────
function FloatingFab({ onAdd }) {
  return (
    <div className="fab-float">
      <button
        className="btn"
        aria-label="add expense"
        onClick={() => onAdd('＋ add expense')}
      >
        <span className="plus">＋</span>
      </button>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Action bar (legacy — unused in new layout)
// ─────────────────────────────────────────────────────────────
function ActionBar({ onAdd, onSettle }) {
  return (
    <div className="actionbar">
      <button className="ab-btn add" onClick={() => onAdd('+ add expense')}>
        <span className="icon">＋</span>
        <span>add expense</span>
      </button>
      <button className="ab-btn settle" onClick={() => onSettle('settle ₹680 → Rohan')}>
        <span className="icon" aria-hidden="true">
          <svg viewBox="0 0 14 14" fill="none">
            <path d="M2 7h10M9 4l3 3-3 3" stroke="currentColor" strokeWidth="1.6"
              strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </span>
        <span>settle up</span>
      </button>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Gesture dock — bidirectional: drag UP = add, drag DOWN = settle
// ─────────────────────────────────────────────────────────────
function GestureDock({ onAdd, onSettle, nextSettle }) {
  const [drag, setDrag] = React.useState(null);
  const UP_THRESHOLD = 50;
  const DOWN_THRESHOLD = 30;

  const onDown = (e) => {
    e.currentTarget.setPointerCapture?.(e.pointerId);
    setDrag({ startY: e.clientY, dy: 0, pid: e.pointerId });
  };
  const onMove = (e) => {
    if (!drag || drag.pid !== e.pointerId) return;
    const dy = e.clientY - drag.startY;
    setDrag({ ...drag, dy });
  };
  const onUp = () => {
    if (!drag) return;
    if (drag.dy < -UP_THRESHOLD) onAdd('＋ add expense');
    else if (drag.dy > DOWN_THRESHOLD) {
      onSettle(`settle ₹${nextSettle.amount.toLocaleString('en-IN')} → ${nextSettle.to}`);
    }
    setDrag(null);
  };

  const dy = drag?.dy || 0;
  const direction = dy < -4 ? 'up' : (dy > 4 ? 'down' : null);
  const upLift   = direction === 'up'   ? Math.min(100, -dy) : 0;
  const downPull = direction === 'down' ? Math.min(60,   dy) : 0;
  const upProgress   = Math.min(1, upLift   / UP_THRESHOLD);
  const downProgress = Math.min(1, downPull / DOWN_THRESHOLD);
  const armed = (direction === 'up'   && upProgress   >= 1)
             || (direction === 'down' && downProgress >= 1);

  // handle nudges in the direction of the finger
  const handleNudge = (direction === 'up'
    ? -upLift * 0.32
    : direction === 'down'
      ? downPull * 0.6
      : 0);

  return (
    <div className={`gesture-dock ${drag ? 'dragging' : ''} ${direction === 'up' ? 'drag-up' : ''} ${direction === 'down' ? 'drag-down' : ''}`}
      onPointerDown={onDown} onPointerMove={onMove}
      onPointerUp={onUp} onPointerCancel={onUp}
    >
      {/* preview pill (lives above the handle in both cases — the
          direction is conveyed by color + content, not position) */}
      {direction && (
        <div
          className={`gesture-preview ${direction === 'up' ? 'add' : 'settle'} ${armed ? 'armed' : ''}`}
          style={{
            opacity: direction === 'up' ? upProgress : downProgress,
            transform: `translateX(-50%) translateY(${direction === 'up' ? -upLift * 0.4 : -8}px) scale(${0.6 + (direction === 'up' ? upProgress : downProgress) * 0.5})`,
          }}
        >
          {direction === 'up' ? (
            <>
              <span className="plus">＋</span>
              <span>add expense</span>
            </>
          ) : (
            <>
              <span className="arrow" aria-hidden="true">
                <svg viewBox="0 0 14 14" fill="none">
                  <path d="M2 7h10M9 4l3 3-3 3" stroke="currentColor"
                    strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
              </span>
              <span>settle ₹{nextSettle.amount.toLocaleString('en-IN')} → {nextSettle.to}</span>
            </>
          )}
        </div>
      )}
      <div className="surface">
        <div className="gesture-hint-top">↑ swipe to add</div>
        <div className="gesture-handle"
          style={{ transform: `translateY(${handleNudge}px)` }} />
        <div className="gesture-hint-bottom">↓ swipe to settle</div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Radial FAB — long-press to fan out 4 options on an arc above the FAB.
// Backdrop blurs + darkens while the menu is open. Each option has a
// scale-up hot state when the pointer enters it. Release while hovering
// an item commits it.
// ─────────────────────────────────────────────────────────────
function RadialFab({ onCommit, nextSettle }) {
  const [open, setOpen] = React.useState(false);
  const [hot, setHot] = React.useState(null); // hovered item id
  const longPressTimer = React.useRef(null);
  const pointerActive = React.useRef(false);

  // 4 items on an arc above the fab; angles in degrees, -90 = straight up.
  // distance is how far each tile sits from the fab center.
  const items = React.useMemo(() => ([
    { id: 'add',     label: 'add',     angle: -150, dist: 100,
      icon: (<span style={{ fontWeight: 500, fontSize: 26, lineHeight: 1 }}>＋</span>) },
    { id: 'settle',  label: 'settle',  angle:  -110, dist: 116,
      icon: (<svg viewBox="0 0 22 22" fill="none"><path d="M4 11h14M14 7l4 4-4 4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>) },
    { id: 'scan',    label: 'scan',    angle:  -70,  dist: 116,
      icon: (<svg viewBox="0 0 22 22" fill="none"><rect x="3.5" y="5.5" width="15" height="12" rx="2" stroke="currentColor" strokeWidth="1.6"/><circle cx="11" cy="11.5" r="2.6" stroke="currentColor" strokeWidth="1.6"/><path d="M8 5.5l1-1.5h4l1 1.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>) },
    { id: 'note',    label: 'note',    angle:  -30,  dist: 100,
      icon: (<svg viewBox="0 0 22 22" fill="none"><path d="M5 4h9l3 3v11H5z" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round"/><path d="M8 11h6M8 14h4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>) },
  ]), []);

  const COMMIT = {
    add:    '＋ add expense',
    settle: `settle ₹${nextSettle.amount.toLocaleString('en-IN')} → ${nextSettle.to}`,
    scan:   '📷 scan receipt',
    note:   '✎ note added',
  };

  const closeMenu = () => {
    setOpen(false);
    setHot(null);
    pointerActive.current = false;
    if (longPressTimer.current) {
      clearTimeout(longPressTimer.current);
      longPressTimer.current = null;
    }
  };

  // hit-test items by position relative to the menu center (fab center)
  const itemAt = (clientX, clientY, fabCenter) => {
    const dx = clientX - fabCenter.x;
    const dy = clientY - fabCenter.y;
    for (const it of items) {
      const rad = (it.angle * Math.PI) / 180;
      const ix = Math.cos(rad) * it.dist;
      const iy = Math.sin(rad) * it.dist;
      const d = Math.hypot(dx - ix, dy - iy);
      if (d < 34) return it.id;
    }
    return null;
  };

  const fabRef = React.useRef(null);

  const onPointerDown = (e) => {
    pointerActive.current = true;
    try { e.currentTarget.setPointerCapture?.(e.pointerId); } catch {}
    // long-press → open menu
    longPressTimer.current = setTimeout(() => {
      if (pointerActive.current) setOpen(true);
      longPressTimer.current = null;
    }, 320);
  };
  const onPointerMove = (e) => {
    if (!open || !fabRef.current) return;
    const r = fabRef.current.getBoundingClientRect();
    const center = { x: r.left + r.width / 2, y: r.top + r.height / 2 };
    const next = itemAt(e.clientX, e.clientY, center);
    if (next !== hot) setHot(next);
  };
  const onPointerUp = (e) => {
    pointerActive.current = false;
    if (longPressTimer.current) {
      clearTimeout(longPressTimer.current);
      longPressTimer.current = null;
      // tap (no long-press) — quick add
      if (!open) {
        onCommit(COMMIT.add);
        return;
      }
    }
    if (open) {
      if (hot) onCommit(COMMIT[hot]);
      closeMenu();
    }
  };
  const onPointerCancel = () => closeMenu();

  // close on Escape
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') closeMenu(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [open]);

  return (
    <>
      {/* backdrop sits in the page, covers the whole screen when open */}
      <div
        className={`radial-backdrop ${open ? 'show' : ''}`}
        onPointerDown={(e) => { e.stopPropagation(); closeMenu(); }}
      />
      <div className={`radial-dock ${open ? 'open' : ''}`}>
        <span className="radial-hint">tap to add · hold for more</span>
        <button
          ref={fabRef}
          className={`radial-fab ${open ? 'armed' : ''}`}
          aria-label="actions"
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onPointerCancel={onPointerCancel}
        >
          <span className="plus">＋</span>
        </button>

        {/* menu items live INSIDE the dock, anchored at the FAB center.
            Items fan UPWARD because all angles are in the upper hemisphere
            (sin < 0 in screen coords). */}
        <div className="radial-menu" aria-hidden={!open}>
          {items.map((it, i) => {
            const rad = (it.angle * Math.PI) / 180;
            const tx = open ? Math.cos(rad) * it.dist : 0;
            const ty = open ? Math.sin(rad) * it.dist : 0;
            return (
              <div
                key={it.id}
                className={`radial-item ${hot === it.id ? 'hot' : ''} ${it.id === 'settle' ? 'danger' : ''}`}
                style={{
                  transform: `translate(calc(-50% + ${tx}px), calc(-50% + ${ty}px))`,
                  opacity: open ? 1 : 0,
                  transitionDelay: open ? `${i * 30}ms` : '0ms',
                }}
              >
                <div className="icon">{it.icon}</div>
                <div className="label">{it.label}</div>
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

// ─────────────────────────────────────────────────────────────
// Commit toast — fly-in confirmation when a gesture commits
// ─────────────────────────────────────────────────────────────
function CommitToast({ message, onDone }) {
  React.useEffect(() => {
    if (!message) return;
    const t = setTimeout(onDone, 1200);
    return () => clearTimeout(t);
  }, [message, onDone]);
  return (
    <div className={`settle-preview ${message ? 'show' : ''}`}>
      {message && (
        <>
          <svg viewBox="0 0 16 16" fill="none">
            <path d="M3 8.5L6.5 12L13 5" stroke="currentColor" strokeWidth="2"
              strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          <span>{message}</span>
        </>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Tweaks
// ─────────────────────────────────────────────────────────────
function Tweaks({ t, setTweak }) {
  return (
    <window.TweaksPanel title="Tweaks">
      <window.TweakSection label="action mode">
        <window.TweakRadio
          label="how to add / settle"
          value={t.actionMode}
          options={[
            { label: 'buttons',  value: 'buttons'  },
            { label: 'gestures', value: 'gestures' },
            { label: 'radial',   value: 'radial'   },
          ]}
          onChange={(v) => setTweak('actionMode', v)}
        />
      </window.TweakSection>
    </window.TweaksPanel>
  );
}

// ─────────────────────────────────────────────────────────────
// Root
// ─────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
  const [view, setView] = React.useState('transactions');
  const [expanded, setExpanded] = React.useState(false);
  const [toast, setToast] = React.useState(null);

  const owed = 0;
  const owe  = 860;
  const nextSettle = FLOWS[0];

  const fireCommit = (msg) => setToast(msg);

  return (
    <>
      <window.IOSDevice width={390} height={844}>
        <div className="screen">
          <div className="page">
            <TopNav />
            <HeaderCard
              view={view} setView={setView}
              expanded={expanded} setExpanded={setExpanded}
              onSettle={fireCommit}
            />
            <Panes view={view} setView={setView}>
              <TransactionsPane />
              <BalancesPane owe={owe} owed={owed} />
            </Panes>
            {t.actionMode === 'gestures'
              ? <GestureDock
                  onAdd={fireCommit}
                  onSettle={fireCommit}
                  nextSettle={nextSettle}
                />
              : t.actionMode === 'radial'
              ? <RadialFab
                  onCommit={fireCommit}
                  nextSettle={nextSettle}
                />
              : <FloatingFab onAdd={fireCommit} />
            }
          </div>
          <CommitToast message={toast} onDone={() => setToast(null)} />
        </div>
      </window.IOSDevice>
      <Tweaks t={t} setTweak={setTweak} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
