<script lang="ts">
  import { onMount } from 'svelte';

  let activeView = $state<'transactions' | 'budget'>('transactions');
  let activeTab = $state<'all' | 'uncategorized' | 'categorized'>('all');
  let accountsOpen = $state(false);
  let selected = $state<Set<string>>(new Set());
  let lastCategory = $state('');
  let bulkCategoryOpen = $state(false);
  let loading = $state(true);
  let saving = $state(false);

  let budgetGroups = $state<{ name: string; categories: { name: string; budgeted: number; spent: number; available: number }[] }[]>([]);
  let categories = $state<string[]>([]);
  let categoryMap = $state<Record<string, string>>({});
  let accounts = $state<{ name: string; balance: number; positive: boolean; id?: string }[]>([]);
  let offBudgetAccounts = $state<{ name: string; balance: number; positive: boolean; id?: string }[]>([]);
  let suggestedTransfers = $state<any[]>([]);
  let transactions = $state<{ id: string; date: string; payee: string; note: string; account: string; category: string; categoryType: string; amount: number; categoryId?: string; accountId?: string }[]>([]);

  let hasMore = $state(true);
  let loadingMore = $state(false);
  let activeAccountId = $state<string | null>(null);
  const PAGE_SIZE = 100;

  let headerSpending = $state('...');
  let headerIncome = $state('...');
  let currentMonthLabel = $state('');
  let totalUncatCount = $state(0);

  const selectedAccountName = $derived(
    accounts.find((a) => a.id === activeAccountId)?.name
      || offBudgetAccounts.find((a) => a.id === activeAccountId)?.name
      || 'All accounts'
  );
  const onBudgetTotal = $derived(accounts.reduce((sum, account) => sum + account.balance, 0));
  const offBudgetTotal = $derived(offBudgetAccounts.reduce((sum, account) => sum + account.balance, 0));
  const uncatCount = $derived(activeAccountId
    ? transactions.filter((t) => t.categoryType === 'uncat').length
    : totalUncatCount
  );
  const sortedCategories = $derived.by(() => {
    if (!lastCategory) return categories;
    const rest = categories.filter((category) => category !== lastCategory);
    return [lastCategory, ...rest];
  });
  const canTransfer = $derived(selected.size === 2);
  const filteredTransactions = $derived.by(() => {
    if (activeTab === 'uncategorized') return transactions.filter((t) => t.categoryType === 'uncat');
    if (activeTab === 'categorized') return transactions.filter((t) => t.categoryType !== 'uncat');
    return transactions;
  });
  const spendMagnitude = $derived.by(() => {
    const value = Number(headerSpending.replace(/[$,]/g, '')) || 0;
    return '$' + value.toLocaleString('en-US');
  });
  const incomeMagnitude = $derived.by(() => {
    const value = Number(headerIncome.replace(/[$,]/g, '')) || 0;
    return '$' + value.toLocaleString('en-US');
  });

  async function api(path: string, opts: RequestInit = {}) {
    const res = await fetch(`/api/budget${path}`, { credentials: 'include', ...opts });
    if (!res.ok) throw new Error(`${res.status}`);
    return res.json();
  }

  async function loadAccounts() {
    try {
      const data = await api('/accounts');
      const onBudget: typeof accounts = [];
      const offBudget: typeof offBudgetAccounts = [];
      for (const a of data) {
        if (a.closed) continue;
        const entry = {
          name: a.name,
          balance: Math.round(a.balanceDollars),
          positive: a.balanceDollars >= 0,
          id: a.id
        };
        if (a.offbudget) offBudget.push(entry);
        else onBudget.push(entry);
      }
      accounts = onBudget;
      offBudgetAccounts = offBudget;
    } catch {
      // keep route stable
    }
  }

  async function loadCategories() {
    try {
      const data = await api('/categories');
      const allCats: string[] = [];
      const map: Record<string, string> = {};
      for (const group of data) {
        for (const cat of group.categories) {
          if (cat.name !== 'Starting Balances') {
            allCats.push(cat.name);
            map[cat.name] = cat.id;
          }
        }
      }
      categories = allCats;
      categoryMap = map;
      if (!lastCategory && allCats.length > 0) lastCategory = allCats[0];
    } catch {
      // keep route stable
    }
  }

  function mapTransaction(t: any) {
    return {
      id: t.id,
      date: formatDateShort(t.date),
      payee: t.payeeName || t.payee || '',
      note: t.notes || '',
      account: t.accountName || '',
      accountId: t.accountId || '',
      category: t.categoryName || '',
      categoryType: t.transfer_id ? 'transfer' : (t.categoryName ? 'normal' : 'uncat'),
      amount: t.amountDollars || 0,
      categoryId: t.categoryId || ''
    };
  }

  async function loadTransactions(append = false) {
    if (loadingMore) return;
    if (!append) {
      loading = true;
      transactions = [];
      hasMore = true;
    } else {
      loadingMore = true;
    }

    try {
      let data: any[];
      if (activeAccountId) {
        const offset = append ? transactions.length : 0;
        const resp = await api(`/transactions?accountId=${activeAccountId}&limit=${PAGE_SIZE}&offset=${offset}`);
        data = resp.transactions || resp || [];
      } else {
        const limit = activeTab === 'uncategorized'
          ? Math.max(500, (append ? transactions.length + PAGE_SIZE : PAGE_SIZE))
          : (append ? transactions.length + PAGE_SIZE : PAGE_SIZE);
        data = await api(`/transactions/recent?limit=${limit}`);
        if (append) {
          const existingIds = new Set(transactions.map((t) => t.id));
          data = data.filter((t: any) => !existingIds.has(t.id));
        }
      }

      const mapped = (Array.isArray(data) ? data : []).map(mapTransaction);
      transactions = append ? [...transactions, ...mapped] : mapped;
      hasMore = mapped.length >= PAGE_SIZE;
    } catch {
      // keep route stable
    } finally {
      loading = false;
      loadingMore = false;
    }
  }

  function loadMore() {
    if (hasMore && !loadingMore) loadTransactions(true);
  }

  function selectAccount(accountId: string | null) {
    activeAccountId = accountId;
    loadTransactions();
  }

  async function loadBudget() {
    try {
      const now = new Date();
      const month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
      const data = await api(`/budget/${month}`);
      budgetGroups = (data.categoryGroups || [])
        .filter((g: any) => g.categories?.length > 0)
        .map((g: any) => ({
          name: g.name,
          categories: g.categories
            .filter((c: any) => c.name !== 'Starting Balances')
            .map((c: any) => ({
              name: c.name,
              budgeted: Math.round(Number(c.budgeted ?? 0) / 100),
              spent: Math.round(Math.abs(Number(c.spent ?? 0)) / 100),
              available: Math.round(Number(c.balance ?? 0) / 100)
            }))
        }))
        .filter((g: any) => g.categories.length > 0);
    } catch {
      // keep route stable
    }
  }

  async function loadSuggested() {
    try {
      const data = await api('/suggested-transfers');
      suggestedTransfers = data.map((s: any) => ({
        id: s.from.id + '-' + s.to.id,
        from: { account: s.from.account, payee: s.from.payee },
        to: { account: s.to.account, payee: s.to.payee },
        amount: s.amountDollars,
        confidence: s.confidence,
        fromId: s.from.id,
        toId: s.to.id
      }));
    } catch {
      // keep route stable
    }
  }

  async function categorize(id: string, category: string) {
    if (!category) return;
    lastCategory = category;
    let catId = categoryMap[category];
    if (!catId) {
      await loadCategories();
      catId = categoryMap[category];
      if (!catId) return;
    }

    const prev = transactions.find((t) => t.id === id);
    const prevCat = prev?.category || '';
    const prevType = prev?.categoryType || 'uncat';
    const wasUncat = prevType === 'uncat';
    transactions = transactions.map((t) =>
      t.id === id ? { ...t, category, categoryType: 'normal', categoryId: catId } : t
    );
    if (wasUncat) totalUncatCount = Math.max(0, totalUncatCount - 1);

    try {
      await api(`/transactions/${id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ category: catId })
      });
    } catch {
      transactions = transactions.map((t) =>
        t.id === id ? { ...t, category: prevCat, categoryType: prevType, categoryId: '' } : t
      );
      if (wasUncat) totalUncatCount++;
    }
  }

  async function bulkCategorize(category: string) {
    if (!category) return;
    lastCategory = category;
    const catId = categoryMap[category];
    const ids = Array.from(selected).filter((id) => {
      const transaction = transactions.find((txn) => txn.id === id);
      return transaction && transaction.categoryType === 'uncat';
    });

    transactions = transactions.map((t) =>
      ids.includes(t.id) ? { ...t, category, categoryType: 'normal', categoryId: catId } : t
    );
    totalUncatCount = Math.max(0, totalUncatCount - ids.length);
    selected = new Set();
    bulkCategoryOpen = false;

    for (const id of ids) {
      try {
        await api(`/transactions/${id}`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ category: catId })
        });
      } catch {
        // optimistic state stays
      }
    }
  }

  async function makeTransfer(fromId: string, toId: string) {
    saving = true;
    try {
      await api('/make-transfer', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ transactionId1: fromId, transactionId2: toId })
      });
      await loadTransactions();
    } catch {
      // keep route stable
    } finally {
      saving = false;
    }
  }

  async function linkSelectedAsTransfer() {
    if (selected.size !== 2) return;
    const ids = Array.from(selected);
    await makeTransfer(ids[0], ids[1]);
    selected = new Set();
  }

  async function linkSuggestedTransfer(suggestion: any) {
    await makeTransfer(suggestion.fromId, suggestion.toId);
    suggestedTransfers = suggestedTransfers.filter((s) => s.id !== suggestion.id);
  }

  function dismissSuggestion(id: string) {
    suggestedTransfers = suggestedTransfers.filter((s) => s.id !== id);
  }

  function toggleSelect(id: string) {
    const next = new Set(selected);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    selected = next;
  }

  function formatDateShort(dateStr: string) {
    const d = new Date(dateStr + 'T00:00:00');
    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
  }

  function formatBudgetAmount(amount: number) {
    return '$' + Math.abs(amount).toLocaleString('en-US');
  }

  function formatBalance(balance: number) {
    const abs = Math.abs(balance);
    return (balance < 0 ? '-' : '') + '$' + abs.toLocaleString('en-US');
  }

  function formatAmount(amount: number) {
    const abs = Math.abs(amount);
    const formatted = '$' + abs.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    return amount >= 0 ? '+' + formatted : '-' + formatted;
  }

  async function loadSummary() {
    try {
      const [summary, uncat] = await Promise.all([api('/summary'), api('/uncategorized-count')]);
      headerSpending = '$' + Math.abs(summary.spendingDollars || 0).toLocaleString('en-US');
      headerIncome = '$' + Math.abs(summary.incomeDollars || 0).toLocaleString('en-US');
      totalUncatCount = uncat.count || 0;
      const m = summary.month || '';
      if (m) {
        const d = new Date(m + '-01');
        currentMonthLabel = d.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
      }
    } catch {
      // keep route stable
    }
  }

  onMount(async () => {
    await Promise.all([loadAccounts(), loadCategories(), loadTransactions(), loadBudget(), loadSuggested(), loadSummary()]);
    loading = false;
  });
</script>

<div class="budget-page">
  <section class="budget-head reveal">
    <div class="head-copy">
      <div class="head-kicker">{currentMonthLabel || 'Budget'}</div>
      <h1>Budget</h1>
      <p class="head-summary">
        {uncatCount} waiting for category · {formatBalance(onBudgetTotal)} across on-budget accounts
      </p>
    </div>

    <div class="head-actions">
      <div class="view-switch">
        <button class:active={activeView === 'transactions'} onclick={() => (activeView = 'transactions')}>Transactions</button>
        <button class:active={activeView === 'budget'} onclick={() => (activeView = 'budget')}>Budget</button>
      </div>
      <button class="view-pill" class:active={accountsOpen} onclick={() => (accountsOpen = !accountsOpen)}>
        {activeAccountId ? selectedAccountName : 'Accounts'}
      </button>
    </div>
  </section>

  <section class="stats-strip reveal" aria-label="Budget summary">
    <div class="stat-block">
      <div class="stat-label">Spent</div>
      <div class="stat-value">{spendMagnitude}</div>
    </div>
    <div class="stat-block">
      <div class="stat-label">Income</div>
      <div class="stat-value">{incomeMagnitude}</div>
    </div>
    <div class="stat-block">
      <div class="stat-label">To sort</div>
      <div class="stat-value">{uncatCount}</div>
    </div>
    <div class="stat-block">
      <div class="stat-label">On budget cash</div>
      <div class="stat-value">{formatBalance(onBudgetTotal)}</div>
    </div>
  </section>

  <section class="account-ribbon reveal" class:open={accountsOpen}>
    <div class="section-head">
      <h2>Accounts</h2>
      <span>{accounts.length + offBudgetAccounts.length + 1} visible</span>
    </div>
    <div class="account-list items-card">
      <button class="account-row" class:active={!activeAccountId} onclick={() => { selectAccount(null); accountsOpen = false; }}>
        <div class="row-accent account-accent"></div>
        <div class="account-main">
          <div class="account-name">All accounts</div>
          <div class="account-meta">On-budget overview</div>
        </div>
        <div class="account-balance">{formatBalance(onBudgetTotal)}</div>
      </button>
      {#each accounts as acct}
        <button class="account-row" class:active={activeAccountId === acct.id} onclick={() => { selectAccount(acct.id || null); accountsOpen = false; }}>
          <div class="row-accent account-accent"></div>
          <div class="account-main">
            <div class="account-name">{acct.name}</div>
            <div class="account-meta">On budget</div>
          </div>
          <div class="account-balance" class:negative={!acct.positive}>{formatBalance(acct.balance)}</div>
        </button>
      {/each}
      {#if offBudgetAccounts.length > 0}
        {#each offBudgetAccounts as acct}
          <button class="account-row quiet" class:active={activeAccountId === acct.id} onclick={() => { selectAccount(acct.id || null); accountsOpen = false; }}>
            <div class="row-accent account-accent quiet"></div>
            <div class="account-main">
              <div class="account-name">{acct.name}</div>
              <div class="account-meta">Off budget</div>
            </div>
            <div class="account-balance">{formatBalance(acct.balance)}</div>
          </button>
        {/each}
      {/if}
    </div>
  </section>

  {#if activeView === 'transactions'}
    {#if suggestedTransfers.length > 0}
      <section class="transfer-strip reveal">
        <div class="section-head">
          <h2>Suggested transfers</h2>
          <span>{suggestedTransfers.length} open</span>
        </div>
        <div class="transfer-list items-card">
          {#each suggestedTransfers as s}
            <div class="transfer-row">
              <div class="row-accent transfer-accent"></div>
              <div class="transfer-copy">
                <div class="transfer-route">{s.from.account} to {s.to.account}</div>
                <div class="transfer-note">{s.from.payee} matched with {s.to.payee}</div>
              </div>
              <div class="transfer-meta">
                <strong>${s.amount.toLocaleString('en-US', { minimumFractionDigits: 2 })}</strong>
                <div class="transfer-actions">
                  <button class="mini-btn primary" onclick={() => linkSuggestedTransfer(s)}>Link</button>
                  <button class="mini-btn" onclick={() => dismissSuggestion(s.id)}>Skip</button>
                </div>
              </div>
            </div>
          {/each}
        </div>
      </section>
    {/if}

    <section class="ledger-surface reveal">
      <section class="ledger-head">
        <div class="section-head ledger-title-row">
          <h2>Ledger</h2>
          <span>{filteredTransactions.length} visible</span>
        </div>
        <div class="filter-strip">
          <button class:active={activeTab === 'all'} onclick={() => { activeTab = 'all'; loadTransactions(); }}>All</button>
          <button class:active={activeTab === 'uncategorized'} onclick={() => { activeTab = 'uncategorized'; loadTransactions(); }}>
            Needs category
            {#if uncatCount > 0}<span>{uncatCount}</span>{/if}
          </button>
          <button class:active={activeTab === 'categorized'} onclick={() => { activeTab = 'categorized'; loadTransactions(); }}>Categorized</button>
        </div>

        {#if selected.size > 0}
          <div class="selection-bar">
            <div class="selection-copy">{selected.size} selected</div>
            <div class="selection-actions">
              {#if canTransfer}
                <button class="mini-btn primary" onclick={linkSelectedAsTransfer} disabled={saving}>Make transfer</button>
              {/if}
              {#if bulkCategoryOpen}
                <select class="bulk-select" onchange={(e) => bulkCategorize((e.target as HTMLSelectElement).value)}>
                  <option value="">Apply category...</option>
                  {#each sortedCategories as cat}
                    <option value={cat}>{cat}</option>
                  {/each}
                </select>
              {:else}
                <button class="mini-btn" onclick={() => (bulkCategoryOpen = true)}>Set category</button>
              {/if}
              <button class="mini-btn" onclick={() => { selected = new Set(); bulkCategoryOpen = false; }}>Clear</button>
            </div>
          </div>
        {/if}
      </section>

      <section class="ledger">
        <div class="ledger-list items-card">
        {#each filteredTransactions as txn (txn.id)}
          <div
            class="txn-row"
            class:txn-uncat={txn.categoryType === 'uncat'}
            class:txn-transfer={txn.categoryType === 'transfer'}
            class:txn-selected={selected.has(txn.id)}
          >
            <label class="txn-check-wrap">
              <input type="checkbox" class="txn-check" checked={selected.has(txn.id)} onchange={() => toggleSelect(txn.id)} />
            </label>
            <div class="row-accent"></div>
            <div class="txn-main">
              <div class="txn-meta-top">
                <span class="txn-date">{txn.date}</span>
                <span class="txn-account">{txn.account}</span>
              </div>
              <div class="txn-name">{txn.payee}</div>
              <div class="txn-subline">
                {#if txn.note}<span>{txn.note}</span>{/if}
              </div>
            </div>
            <div class="txn-tail">
              <div class="txn-amount" class:pos={txn.amount >= 0} class:neg={txn.amount < 0}>{formatAmount(txn.amount)}</div>
              <div class="txn-category">
                {#if txn.categoryType === 'transfer'}
                  <span class="cat-pill transfer">Transfer</span>
                {:else if txn.categoryType === 'uncat'}
                  <select class="cat-select" onchange={(e) => categorize(txn.id, (e.target as HTMLSelectElement).value)}>
                    <option value="">Select category</option>
                    {#each sortedCategories as cat}
                      <option value={cat}>{cat}</option>
                    {/each}
                  </select>
                {:else}
                  <span class="cat-pill">{txn.category}</span>
                {/if}
              </div>
            </div>
          </div>
        {/each}

        {#if filteredTransactions.length === 0}
          <div class="empty-state">
            {#if activeTab === 'uncategorized'}
              No uncategorized transactions right now.
            {:else}
            No transactions to show.
          {/if}
          </div>
        {/if}
        </div>

        {#if hasMore}
          <button class="load-more-btn" onclick={loadMore} disabled={loadingMore}>
            {loadingMore ? 'Loading...' : 'Load more'}
          </button>
        {/if}
      </section>
    </section>
  {:else}
    <section class="budget-surface reveal">
      <section class="budget-board">
      {#each budgetGroups as group}
        <section class="budget-group">
          <div class="section-head budget-group-head">
            <h2>{group.name}</h2>
            <span>{group.categories.length} categories</span>
          </div>
          <div class="budget-rows">
            {#each group.categories as cat}
              <div class="budget-row" class:overspent={cat.available < 0}>
                <div class="budget-name-wrap">
                  <div class="budget-name">{cat.name}</div>
                  <div class="budget-guidance">
                    {#if cat.budgeted > 0}
                      <span>Budgeted {formatBudgetAmount(cat.budgeted)}</span>
                    {/if}
                    {#if cat.spent > 0}
                      <span>Spent {formatBudgetAmount(cat.spent)}</span>
                    {/if}
                    {#if cat.budgeted === 0 && cat.spent === 0}
                      <span>Nothing planned yet</span>
                    {/if}
                  </div>
                </div>
                <div class="budget-values">
                  <strong class:negative={cat.available < 0} class:positive={cat.available > 0}>
                    {cat.available < 0 ? '-' : ''}{formatBudgetAmount(Math.abs(cat.available))}
                  </strong>
                  <span>{cat.available < 0 ? 'Over' : 'Left'}</span>
                </div>
              </div>
            {/each}
          </div>
        </section>
      {/each}
      </section>
    </section>
  {/if}
</div>

<style>
  .budget-page {
    display: grid;
    gap: 20px;
    color: #1f1811;
  }

  .budget-head {
    display: flex;
    align-items: end;
    justify-content: space-between;
    gap: 18px;
    padding-top: 2px;
  }

  .head-copy {
    display: grid;
    gap: 8px;
  }

  .head-kicker,
  .stat-label,
  .section-head span {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: #8a7868;
  }

  h1,
  h2 {
    margin: 0;
    color: #1c140c;
    letter-spacing: -0.055em;
  }

  h1 {
    font-size: clamp(2.6rem, 5vw, 4.2rem);
    line-height: 0.92;
  }

  .head-summary {
    margin: 0;
    color: #69594a;
    font-size: 0.96rem;
    line-height: 1.35;
  }

  h2 {
    font-size: 1.25rem;
    line-height: 1;
  }

  .head-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    justify-content: flex-end;
  }

  .view-switch {
    display: inline-grid;
    grid-auto-flow: column;
    gap: 6px;
    padding: 5px;
    border-radius: 18px;
    background: rgba(255, 251, 246, 0.72);
    border: 1px solid rgba(35, 26, 17, 0.08);
  }

  .view-switch button,
  .mini-btn,
  .load-more-btn {
    min-height: 42px;
    padding: 0 14px;
    border-radius: 999px;
    border: 1px solid rgba(35, 26, 17, 0.08);
    background: rgba(255, 251, 246, 0.88);
    color: #4f4338;
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
  }

  .view-switch button {
    min-height: 38px;
    border-color: transparent;
    background: transparent;
  }

  .view-pill {
    min-height: 38px;
    padding: 0 14px;
    border-radius: 999px;
    border: 1px solid rgba(35, 26, 17, 0.08);
    background: rgba(255, 251, 246, 0.88);
    color: #4f4338;
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
  }

  .view-switch button.active,
  .view-pill.active,
  .mini-btn.primary {
    background: #2f241c;
    color: #fff7ee;
    border-color: #2f241c;
  }

  .stats-strip {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
  }

  .stat-block {
    display: grid;
    gap: 4px;
    padding: 0;
  }

  .stat-value {
    font-size: 1.16rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.04em;
    color: #1f1811;
  }

  .account-ribbon {
    display: none;
    gap: 10px;
  }

  .account-list {
    display: grid;
    gap: 0;
  }

  .account-row {
    display: grid;
    grid-template-columns: 4px minmax(0, 1fr) auto;
    align-items: center;
    gap: 14px;
    padding: 13px 16px;
    border: 0;
    background: transparent;
    color: #433629;
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: background 160ms ease, transform 160ms ease;
  }

  .account-row + .account-row {
    border-top: 1px solid rgba(35, 26, 17, 0.07);
  }

  .account-row:hover {
    background: rgba(255, 248, 242, 0.88);
    transform: translateX(2px);
  }

  .account-row.active {
    background: rgba(255, 248, 242, 0.92);
  }

  .account-accent {
    background: linear-gradient(180deg, #c2b1a0, #a69483);
  }

  .account-accent.quiet {
    background: linear-gradient(180deg, #bcb1a2, #978b7d);
  }

  .account-main {
    min-width: 0;
    display: grid;
    gap: 3px;
  }

  .account-name {
    font-size: 0.94rem;
    font-weight: 600;
    color: #1f1811;
  }

  .account-meta {
    color: #776655;
    font-size: 0.78rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  .account-balance {
    font-family: var(--mono);
    font-size: 0.9rem;
    font-weight: 700;
    color: #1f1811;
  }

  .account-balance.negative {
    color: #9a442b;
  }

  .account-row.quiet {
    opacity: 0.86;
  }

  .transfer-strip,
  .ledger-head,
  .ledger,
  .budget-board {
    padding: 0;
  }

  .ledger-surface,
  .budget-surface {
    display: grid;
    gap: 18px;
    padding: 22px 24px;
    border-radius: 28px;
    background: rgba(250, 244, 236, 0.74);
    border: 1px solid rgba(35, 26, 17, 0.08);
    backdrop-filter: blur(14px);
  }

  .section-head {
    display: flex;
    align-items: end;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
  }

  .ledger-title-row {
    margin-bottom: 0;
  }

  .transfer-list,
  .ledger-list,
  .budget-board,
  .budget-rows {
    display: grid;
    gap: 0;
  }

  .items-card {
    background: rgba(255, 255, 255, 0.82);
    border-radius: 24px;
    border: 1px solid rgba(35, 26, 17, 0.06);
    overflow: hidden;
    box-shadow: 0 16px 44px rgba(42, 30, 19, 0.05);
  }

  .transfer-row,
  .budget-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 0;
    border-bottom: 1px solid rgba(35, 26, 17, 0.08);
  }

  .transfer-row {
    display: grid;
    grid-template-columns: 4px minmax(0, 1fr) auto;
    align-items: center;
    gap: 14px;
    padding: 12px 14px;
    border-bottom: 0;
    transition: background 160ms ease, transform 160ms ease;
  }

  .transfer-row + .transfer-row {
    border-top: 1px solid rgba(35, 26, 17, 0.07);
  }

  .transfer-row:hover,
  .txn-row:hover {
    background: rgba(255, 248, 242, 0.88);
    transform: translateX(2px);
  }

  .transfer-copy,
  .budget-name-wrap {
    min-width: 0;
  }

  .transfer-accent {
    background: linear-gradient(180deg, #f6a33a, #e28615);
  }

  .transfer-route,
  .budget-name {
    font-size: 1rem;
    font-weight: 600;
    color: #1f1811;
  }

  .transfer-note {
    margin-top: 2px;
    color: #6b5b4c;
    font-size: 0.8rem;
    line-height: 1.35;
  }

  .transfer-meta {
    display: grid;
    gap: 5px;
    justify-items: end;
  }

  .transfer-meta strong {
    font-family: var(--mono);
    color: #1f1811;
  }

  .transfer-actions,
  .selection-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    justify-content: flex-end;
  }

  .ledger-head {
    display: grid;
    gap: 10px;
    padding-top: 2px;
  }

  .selection-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding-top: 12px;
    border-top: 1px solid rgba(35, 26, 17, 0.08);
  }

  .selection-copy {
    font-size: 0.92rem;
    font-weight: 600;
    color: #48392b;
  }

  .filter-strip {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }

  .filter-strip button,
  .bulk-select {
    min-height: 34px;
    padding: 0 12px;
    border-radius: 999px;
    border: 1px solid rgba(35, 26, 17, 0.08);
    background: rgba(255, 251, 246, 0.66);
    color: #5b4c3f;
    font: inherit;
    font-size: 0.84rem;
    font-weight: 600;
  }

  .filter-strip button.active {
    background: #2f241c;
    color: #fff7ee;
    border-color: #2f241c;
  }

  .filter-strip button span {
    margin-left: 6px;
    font-family: var(--mono);
    font-size: 0.78rem;
  }

  .ledger {
    border-top: 1px solid rgba(35, 26, 17, 0.08);
  }

  .txn-row {
    display: grid;
    grid-template-columns: auto 4px minmax(0, 1fr) auto;
    gap: 12px;
    align-items: center;
    padding: 12px 14px;
    margin: 0;
    width: 100%;
    background: none;
    transition: background 160ms ease, transform 160ms ease;
  }

  .txn-row + .txn-row {
    border-top: 1px solid rgba(35, 26, 17, 0.07);
  }

  .txn-row:focus-visible {
    outline: 2px solid rgba(122, 87, 52, 0.45);
    outline-offset: 1px;
  }

  .txn-selected {
    background: rgba(255, 248, 242, 0.88);
  }

  .txn-check-wrap {
    display: inline-flex;
    align-self: start;
    padding-top: 4px;
  }

  .row-accent {
    align-self: stretch;
    border-radius: 999px;
    background: rgba(35, 26, 17, 0.08);
  }

  .txn-uncat .row-accent {
    background: linear-gradient(180deg, #f6a33a, #e28615);
  }

  .txn-transfer .row-accent {
    background: linear-gradient(180deg, #85715d, #625446);
  }

  .txn-row:not(.txn-uncat):not(.txn-transfer) .row-accent {
    background: linear-gradient(180deg, #c2b1a0, #a69483);
  }

  .txn-check {
    width: 16px;
    height: 16px;
    accent-color: #6f5337;
  }

  .txn-main {
    min-width: 0;
    display: grid;
    gap: 3px;
  }

  .txn-meta-top {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 10px;
    align-items: center;
  }

  .txn-name {
    min-width: 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: #1f1811;
  }

  .txn-date,
  .txn-account {
    color: #7b6a59;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
  }

  .txn-subline {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 8px;
    color: #6d5c4b;
    font-size: 0.78rem;
    min-height: 1.1em;
  }

  .txn-subline span {
    display: inline;
    min-height: 0;
    padding: 0;
    border-radius: 0;
    background: transparent;
  }

  .txn-category {
    display: flex;
    justify-content: flex-end;
  }

  .txn-tail {
    display: grid;
    justify-items: end;
    gap: 6px;
    margin-left: auto;
  }

  .cat-pill {
    display: inline-flex;
    align-items: center;
    min-height: 26px;
    padding: 0 9px;
    border-radius: 999px;
    background: rgba(167, 111, 67, 0.09);
    color: #7b5737;
    font-size: 0.74rem;
    font-weight: 600;
  }

  .cat-pill.transfer {
    background: rgba(115, 97, 78, 0.12);
    color: #68584a;
  }

  .cat-select {
    width: 100%;
    max-width: 240px;
    min-height: 34px;
    padding: 0 10px;
    border-radius: 999px;
    border: 1px solid rgba(185, 137, 77, 0.22);
    background: rgba(255, 249, 243, 0.88);
    color: #2b2017;
    font: inherit;
    font-size: 0.8rem;
    font-weight: 600;
  }

  .txn-amount {
    flex-shrink: 0;
    font-family: var(--mono);
    font-size: 0.9rem;
    font-weight: 700;
  }

  .txn-amount.pos {
    color: #3b7858;
  }

  .txn-amount.neg {
    color: #9a442b;
  }

  .empty-state {
    padding: 18px 0;
    color: #6b5b4c;
  }

  .budget-group {
    display: grid;
    gap: 0;
    padding-top: 8px;
  }

  .budget-group + .budget-group {
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid rgba(35, 26, 17, 0.08);
  }

  .budget-group-head {
    margin-bottom: 8px;
  }

  .budget-name-wrap {
    display: grid;
    gap: 6px;
  }

  .budget-guidance {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 12px;
    color: #736252;
    font-size: 0.82rem;
  }

  .budget-values {
    display: grid;
    gap: 4px;
    justify-items: end;
    color: #6d5c4b;
    font-size: 0.76rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
  }

  .budget-values strong {
    font-family: var(--mono);
    font-size: 0.98rem;
    color: #1f1811;
    letter-spacing: 0;
    text-transform: none;
  }

  .budget-values strong.negative {
    color: #a23e26;
  }

  .budget-values strong.positive {
    color: #3b7858;
  }

  .load-more-btn {
    margin-top: 18px;
    justify-self: start;
  }

  @media (max-width: 900px) {
    .stats-strip {
      grid-template-columns: 1fr 1fr;
    }

    .txn-row {
      grid-template-columns: auto 4px minmax(0, 1fr);
    }

    .txn-tail {
      grid-column: 3;
      justify-content: flex-start;
      justify-items: start;
    }

    .budget-values {
      justify-items: start;
    }
  }

  @media (max-width: 768px) {
    .budget-page {
      gap: 16px;
      padding-inline: 16px;
    }

    .budget-head {
      align-items: start;
      gap: 14px;
      flex-direction: column;
    }

    .head-actions {
      width: 100%;
      justify-content: space-between;
    }

    h1 {
      font-size: clamp(2.2rem, 10vw, 3rem);
    }

    .view-switch {
      flex: 1;
    }

    .view-switch button {
      padding-inline: 12px;
    }

    .view-pill {
      max-width: 44%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .head-summary {
      display: none;
    }

    .account-ribbon.open {
      display: grid;
      margin-top: -4px;
    }

    .ledger-surface,
    .budget-surface {
      gap: 12px;
      padding: 14px 12px;
      border-radius: 24px;
    }

    .stats-strip {
      grid-template-columns: 1fr 1fr;
      gap: 10px 14px;
    }

    .transfer-row,
    .budget-row {
      flex-direction: column;
      align-items: stretch;
    }

    .transfer-row {
      grid-template-columns: 4px minmax(0, 1fr);
      gap: 10px;
      padding: 10px 12px;
    }

    .transfer-meta,
    .selection-actions {
      justify-items: start;
      justify-content: flex-start;
    }

    .selection-bar {
      flex-direction: column;
      align-items: stretch;
    }

    .txn-row {
      grid-template-columns: auto 4px minmax(0, 1fr);
      gap: 8px;
      padding: 10px 12px;
    }

    .txn-meta-top {
      gap: 6px 10px;
    }

    .txn-amount {
      font-size: 0.84rem;
    }

    .txn-tail {
      grid-column: 3;
      justify-items: start;
      margin-left: 0;
    }

    .cat-select {
      max-width: none;
      width: 100%;
    }

    .budget-values {
      justify-items: start;
    }

    .stat-block {
      gap: 3px;
    }
  }
</style>
