Загрузка данных


<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="theme-color" content="#8067db" />
    <meta
      name="description"
      content="Flow — открытый одностраничный сайт для учёта доходов и расходов. Все данные сохраняются в вашем браузере."
    />
    <title>Flow — ваши финансы в порядке</title>
    <!--
    ЧИТАЕМЫЙ ИСХОДНЫЙ КОД САЙТА В ОДНОМ ФАЙЛЕ

    Запуск: откройте этот файл в современном браузере.
    Редактирование: откройте этот же файл в VS Code, Блокноте или другом редакторе.
    Сборка, npm, React, CDN, дополнительные файлы и подключение к сети не нужны.

    Ниже последовательно расположены:
    1. CSS — оформление и адаптивная версия.
    2. HTML — базовая разметка.
    3. JavaScript — данные, шаблоны разделов, диаграммы, формы и обработчики.

    Шрифты берутся из системы. Значки и диаграммы нарисованы обычным SVG.
    Данные хранятся в localStorage. Для переноса используйте JSON-копию в настройках.
  -->
    <style>
      @layer base, components, responsive;

      @layer base {
        :root {
          font-family:
            "Manrope",
            -apple-system,
            BlinkMacSystemFont,
            "Segoe UI",
            sans-serif;
          color: #292936;
          background: #f7f8fa;
          font-synthesis: none;
          --primary: #8067db;
          --primary-dark: #6d54c6;
          --ink: #292936;
          --muted: #94949f;
          --line: #ececf1;
          --green: #559b80;
          --peach: #d59476;
          --sidebar: 226px;
        }
        * {
          box-sizing: border-box;
        }
        body {
          margin: 0;
          min-width: 360px;
          -webkit-font-smoothing: antialiased;
        }
        button,
        input,
        select,
        textarea {
          font: inherit;
        }
        button {
          cursor: pointer;
          -webkit-tap-highlight-color: transparent;
        }
        button {
          color: inherit;
        }
        button,
        input,
        textarea,
        select,
        a {
          outline: none;
        }
        button:focus-visible,
        a:focus-visible,
        .chart-plot:focus-visible {
          outline: 3px solid #b6a4ee;
          outline-offset: 3px;
        }
        button {
          transition:
            background 0.18s ease,
            color 0.18s ease,
            border-color 0.18s ease,
            box-shadow 0.18s ease,
            transform 0.18s ease;
        }
        button:disabled {
          cursor: not-allowed;
          opacity: 0.35;
        }
        button:active:not(:disabled) {
          transform: translateY(1px);
        }
        input,
        select,
        textarea {
          min-width: 0;
          color: var(--ink);
        }
        input::placeholder,
        textarea::placeholder {
          color: #b0afbb;
          opacity: 1;
        }
        input,
        select,
        textarea {
          transition:
            border-color 0.18s,
            box-shadow 0.18s;
        }
        h1,
        h2,
        h3,
        p {
          margin: 0;
        }
        button svg,
        span svg {
          flex-shrink: 0;
        }
        ::selection {
          background: #e3dafa;
          color: #503b8b;
        }
        ::-webkit-scrollbar {
          width: 6px;
          height: 5px;
        }
        ::-webkit-scrollbar-thumb {
          border-radius: 10px;
          background: #d7d4e2;
        }
        ::-webkit-scrollbar-track {
          background: transparent;
        }
        .sr-only {
          position: absolute;
          width: 1px;
          height: 1px;
          padding: 0;
          margin: -1px;
          overflow: hidden;
          clip: rect(0, 0, 0, 0);
          white-space: nowrap;
          border: 0;
        }
        @keyframes enter {
          from {
            opacity: 0;
            transform: translateY(10px);
          }
          to {
            opacity: 1;
            transform: translateY(0);
          }
        }
        @keyframes modal-enter {
          from {
            opacity: 0;
            transform: translateY(15px) scale(0.985);
          }
          to {
            opacity: 1;
            transform: translateY(0) scale(1);
          }
        }
        @keyframes fade-in {
          from {
            opacity: 0;
          }
          to {
            opacity: 1;
          }
        }
        @keyframes line-draw {
          from {
            stroke-dashoffset: 1;
          }
          to {
            stroke-dashoffset: 0;
          }
        }
        @keyframes ring-enter {
          from {
            opacity: 0;
            transform: scale(0.92) rotate(-8deg);
          }
          to {
            opacity: 1;
            transform: scale(1) rotate(0);
          }
        }
      }

      @layer components {
        .app-shell {
          min-height: 100vh;
        }
        .sidebar {
          position: fixed;
          width: var(--sidebar);
          top: 0;
          left: 0;
          bottom: 0;
          z-index: 40;
          background: #fff;
          border-right: 1px solid #eaeaf0;
          padding: 27px 18px 0;
          display: flex;
          flex-direction: column;
        }
        .sidebar-top {
          display: flex;
          align-items: center;
          justify-content: space-between;
        }
        .brand-button {
          border: none;
          background: none;
          padding: 0 10px;
        }
        .brand {
          display: flex;
          gap: 10px;
          align-items: center;
          font-size: 32px;
          line-height: 1;
          font-weight: 800;
          letter-spacing: -1.7px;
        }
        .brand-icon {
          display: grid;
          place-items: center;
          background: var(--primary);
          color: #fff;
          width: 36px;
          height: 36px;
          border-radius: 11px;
          box-shadow: 0 4px 10px #8067db15;
        }
        .brand-dot {
          color: var(--primary);
        }
        .workspace-picker {
          display: flex;
          align-items: center;
          gap: 9px;
          background: #fafafb;
          border: 1px solid #eaeaf0;
          border-radius: 10px;
          padding: 11px 9px;
          margin-top: 32px;
          width: 100%;
          text-align: left;
        }
        .workspace-picker:hover {
          background: #f4f1fb;
          border-color: #ddd4f5;
        }
        .workspace-symbol {
          background: #fff;
          border: 1px solid #eae8ef;
          color: #898098;
          display: grid;
          place-items: center;
          width: 31px;
          height: 33px;
          border-radius: 8px;
          flex-shrink: 0;
        }
        .workspace-picker > span:nth-child(2) {
          min-width: 0;
          flex: 1;
        }
        .workspace-picker strong {
          display: block;
          font-size: 11px;
          font-weight: 700;
          line-height: 19px;
          white-space: nowrap;
          text-overflow: ellipsis;
          overflow: hidden;
        }
        .workspace-picker small {
          display: block;
          font-size: 9px;
          color: #a3a0ad;
          line-height: 15px;
        }
        .workspace-picker > svg {
          color: #9d98aa;
        }
        .nav-label {
          font-size: 9px;
          font-weight: 600;
          color: #aaa7b4;
          letter-spacing: 1.3px;
          margin: 29px 12px 12px;
        }
        .main-nav {
          display: flex;
          flex-direction: column;
          gap: 6px;
        }
        .nav-item {
          display: flex;
          align-items: center;
          gap: 12px;
          border: 0;
          background: transparent;
          width: 100%;
          text-align: left;
          border-radius: 9px;
          padding: 13px 13px;
          font-size: 12px;
          font-weight: 500;
          color: #898492;
        }
        .nav-item > span:nth-child(2) {
          flex: 1;
        }
        .nav-item:hover {
          background: #f6f3fc;
          color: var(--primary);
        }
        .nav-item.active {
          color: #7b62d4;
          background: #eeebf9;
          font-weight: 700;
        }
        .nav-active-dot {
          width: 5px;
          height: 5px;
          background: #8f75df;
          border-radius: 50%;
          margin-right: 3px;
        }
        .nav-count {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          min-width: 21px;
          height: 20px;
          padding: 0 5px;
          color: #aaa4b4;
          background: #f2f1f5;
          font-size: 10px;
          border-radius: 5px;
        }
        .nav-item.active .nav-count {
          color: #8a73cf;
          background: #e3dcf6;
        }
        .sidebar-bottom {
          margin-top: auto;
          padding-top: 28px;
        }
        .calm-card {
          position: relative;
          background: linear-gradient(127deg, #f6f2fd, #eeebf8);
          border: 1px solid #eee8f7;
          border-radius: 12px;
          padding: 17px 18px 0;
          margin: 0 0 20px;
          overflow: hidden;
        }
        .calm-flower {
          width: 49px;
          height: 49px;
          margin-bottom: 10px;
        }
        .calm-spark {
          position: absolute;
          top: 33px;
          right: 29px;
          font-size: 14px;
          color: #bba9df;
        }
        .calm-card h3 {
          font-size: 14px;
          line-height: 23px;
          font-weight: 700;
          letter-spacing: -0.3px;
          color: #655482;
        }
        .calm-card p {
          font-size: 10px;
          line-height: 18px;
          margin-top: 8px;
          color: #aaa0ba;
        }
        .calm-footer {
          border-top: 1px solid #e7def3;
          margin-top: 16px;
          padding: 12px 0;
          display: flex;
          gap: 7px;
          align-items: center;
          font-size: 9px;
          color: #9f8bbb;
        }
        .calm-footer .tiny-dot {
          background: #a58bcf;
          width: 4px;
          height: 4px;
        }
        .help-nav {
          padding: 12px 9px 18px;
          font-size: 10px;
          gap: 9px;
        }
        .help-nav > svg:last-child {
          color: #b6b0c0;
        }
        .profile {
          border-top: 1px solid #efedf2;
          display: flex;
          align-items: center;
          gap: 9px;
          padding: 20px 2px;
        }
        .avatar,
        .top-avatar {
          display: grid;
          place-items: center;
          width: 34px;
          height: 34px;
          border-radius: 50%;
          background: #ede8e3;
          color: #8d7969;
          font-size: 13px;
          font-weight: 600;
        }
        .avatar {
          box-shadow: 0 0 0 3px #f8f5f3;
          width: 31px;
          height: 31px;
        }
        .profile > span:nth-child(2) {
          flex: 1;
        }
        .profile strong {
          font-size: 10px;
          display: block;
          font-weight: 600;
          line-height: 18px;
        }
        .profile small {
          font-size: 8px;
          color: #a7a2ae;
          display: flex;
          gap: 4px;
          align-items: center;
          line-height: 15px;
          white-space: nowrap;
        }
        .profile small i {
          display: block;
          width: 4px;
          height: 4px;
          background: #8cad9a;
          border-radius: 50%;
        }
        .profile .icon-button {
          color: #8d869c;
        }
        .main-shell {
          margin-left: var(--sidebar);
        }
        .topbar {
          height: 71px;
          background: #fff;
          border-bottom: 1px solid var(--line);
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 20px;
          padding: 0 35px;
        }
        .breadcrumb {
          display: flex;
          align-items: center;
          gap: 15px;
          font-size: 10px;
          color: #a8a4af;
          white-space: nowrap;
        }
        .breadcrumb svg {
          color: #bbb5c5;
        }
        .breadcrumb strong {
          font-size: 10px;
          font-weight: 600;
          color: #676270;
        }
        .topbar-right {
          display: flex;
          align-items: center;
          gap: 19px;
        }
        .global-search {
          display: flex;
          align-items: center;
          gap: 8px;
          color: #aaa5b3;
          padding: 7px 0 7px 9px;
          border-radius: 7px;
          border: 1px solid transparent;
          transition:
            background 0.2s,
            border-color 0.2s;
        }
        .global-search:focus-within,
        .global-search.has-value {
          background: #f9f7fd;
          border-color: #e7dff7;
          padding-right: 8px;
        }
        .global-search input {
          width: 130px;
          background: transparent;
          border: 0;
          font-size: 10px;
          padding: 0;
        }
        .global-search input::placeholder {
          color: #aaa5b3;
        }
        kbd {
          background: #f7f6f9;
          border: 1px solid #eceaf0;
          border-radius: 4px;
          padding: 2px 4px;
          color: #a6a0b0;
          font-family: inherit;
          font-size: 9px;
          line-height: 14px;
          white-space: nowrap;
        }
        .global-search button {
          border: 0;
          background: none;
          display: flex;
          padding: 0;
          color: #a39bab;
        }
        .topbar-divider {
          height: 21px;
          width: 1px;
          background: #eeebf1;
        }
        .today {
          display: flex;
          align-items: center;
          gap: 8px;
          font-size: 10px;
          color: #89828f;
        }
        .today svg {
          color: #a69fac;
        }
        .top-avatar {
          height: 30px;
          width: 30px;
          border: 3px solid #fff;
          box-sizing: content-box;
          box-shadow: 0 0 0 1px #ede8e5;
          font-size: 11px;
        }
        .top-avatar:hover {
          box-shadow: 0 0 0 2px #c9b6ea;
        }
        .main-content {
          padding: 29px 35px 0;
          max-width: 1540px;
          margin: 0 auto;
          animation: enter 0.4s ease both;
        }
        .page-heading {
          display: flex;
          justify-content: space-between;
          align-items: center;
          gap: 18px;
          margin-bottom: 25px;
        }
        .page-eyebrow {
          display: none;
        }
        h1 {
          font-size: 30px;
          letter-spacing: -1.15px;
          line-height: 1.4;
          font-weight: 750;
        }
        .heading-dot {
          color: #9478e0;
          margin-left: 1px;
        }
        .page-heading p {
          font-size: 11px;
          color: #9e97a5;
          margin-top: 6px;
          letter-spacing: 0.04px;
        }
        .page-actions {
          display: flex;
          align-items: center;
          gap: 9px;
          padding-top: 6px;
        }
        .button {
          border-radius: 8px;
          border: 1px solid transparent;
          padding: 11px 16px;
          display: inline-flex;
          justify-content: center;
          align-items: center;
          gap: 8px;
          min-height: 39px;
          line-height: 17px;
          font-size: 11px;
          font-weight: 600;
          white-space: nowrap;
        }
        .button.primary {
          background: var(--primary);
          color: white;
          border-color: #7c62d6;
          box-shadow:
            0 3px 7px #7962ca12,
            inset 0 1px 0 #ffffff19;
        }
        .button.primary:hover {
          background: var(--primary-dark);
          border-color: var(--primary-dark);
          box-shadow: 0 5px 15px #8067db25;
          transform: translateY(-1px);
        }
        .button.secondary {
          background: #fff;
          color: #736d7e;
          border-color: #e6e3ec;
          box-shadow: 0 2px 2px #27203302;
        }
        .button.secondary:hover {
          background: #fbf9fe;
          border-color: #d7cdea;
          color: #775cb4;
        }
        .button.small {
          padding: 8px 13px;
          min-height: 34px;
          font-size: 10px;
        }
        .button.danger {
          background: #c4696b;
          border-color: #c4696b;
          color: white;
        }
        .button.danger:hover {
          background: #b55558;
        }
        .button.danger-ghost {
          color: #b87178;
          background: #fbf2f3;
        }
        .button.danger-ghost:hover {
          background: #f7e6e9;
        }
        .full-width {
          width: 100%;
        }
        .icon-button {
          display: inline-flex;
          justify-content: center;
          align-items: center;
          width: 34px;
          height: 34px;
          background: none;
          border: 1px solid transparent;
          border-radius: 7px;
          padding: 0;
          color: #9690a0;
          flex-shrink: 0;
        }
        .icon-button:hover,
        .icon-button.selected {
          color: var(--primary);
          background: #f1ecfa;
        }
        .icon-button.tiny {
          width: 28px;
          height: 28px;
        }
        .text-button {
          border: 0;
          background: none;
          color: #8b74c6;
          font-size: 10px;
          font-weight: 600;
          display: flex;
          align-items: center;
          gap: 8px;
          padding: 6px 0 6px 6px;
        }
        .text-button:hover {
          color: #6244ae;
          gap: 12px;
        }
        .period-toolbar {
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 14px;
          margin-bottom: 20px;
        }
        .period-controls {
          display: inline-flex;
          padding: 4px;
          background: #edeef2;
          border: 1px solid #e9eaef;
          border-radius: 9px;
          gap: 2px;
        }
        .period-controls button {
          background: none;
          border: 1px solid transparent;
          border-radius: 6px;
          padding: 7px 16px;
          font-size: 10px;
          font-weight: 500;
          color: #9993a2;
          line-height: 16px;
        }
        .period-controls button:hover {
          color: #736089;
          background: #ffffff55;
        }
        .period-controls button.active {
          background: #fff;
          border-color: #e4e3e9;
          color: #746284;
          font-weight: 700;
          box-shadow: 0 1px 4px #44365009;
        }
        .date-range {
          display: flex;
          align-items: center;
          gap: 8px;
          font-size: 10px;
          color: #938b9b;
        }
        .date-range svg {
          color: #a59cae;
        }
        .stats-grid {
          display: grid;
          grid-template-columns: repeat(3, minmax(0, 1fr));
          gap: 18px;
          margin-bottom: 21px;
        }
        .stat-card {
          position: relative;
          border: 1px solid #e7e5ef;
          border-radius: 12px;
          background: #fff;
          padding: 17px 21px 14px;
          overflow: hidden;
          min-height: 144px;
          box-shadow: 0 2px 3px #34314101;
        }
        .stat-card:nth-child(2) {
          animation: enter 0.5s 0.04s backwards;
        }
        .stat-card:nth-child(3) {
          animation: enter 0.5s 0.08s backwards;
        }
        .balance-card {
          background: linear-gradient(108deg, #f0ebfb 0%, #eee9fb 50%, #eae3f7 100%);
          border-color: #e0d7f2;
        }
        .balance-art {
          position: absolute;
          top: -3px;
          right: -5px;
          fill: none;
          stroke: #bca9dd;
          stroke-width: 0.75;
          opacity: 0.22;
          pointer-events: none;
        }
        .stat-top {
          position: relative;
          display: flex;
          align-items: center;
          justify-content: space-between;
          color: #9a91a3;
          font-size: 11px;
          height: 26px;
        }
        .stat-top > span:first-child {
          display: flex;
          align-items: center;
          gap: 6px;
        }
        .balance-card .stat-top {
          color: #8d7caa;
        }
        .info-tip {
          display: inline-flex;
          align-items: center;
          opacity: 0.65;
          cursor: help;
        }
        .stat-icon {
          display: grid;
          place-items: center;
          width: 30px;
          height: 30px;
          border-radius: 9px;
        }
        .stat-icon.purple {
          color: #9e86c8;
          background: #ffffff85;
          border: 1px solid #f6f1fe;
        }
        .stat-icon.green {
          color: #6caa92;
          background: #eef6f2;
          border: 1px solid #eaf2ee;
        }
        .stat-icon.peach {
          color: #d1a184;
          background: #faf1eb;
          border: 1px solid #f7ede6;
        }
        .money {
          position: relative;
          display: block;
          margin-top: 9px;
          font-size: 31px;
          font-weight: 700;
          line-height: 43px;
          letter-spacing: -0.8px;
          white-space: nowrap;
          font-variant-numeric: tabular-nums;
        }
        .money-decimals {
          font-size: 24px;
          color: #b0a9b9;
          font-weight: 500;
          letter-spacing: -0.5px;
        }
        .money-symbol {
          margin-left: 8px;
          font-size: 24px;
          font-weight: 400;
          color: #a9a2b1;
        }
        .balance-card .money {
          color: #554668;
        }
        .balance-card .money-decimals,
        .balance-card .money-symbol {
          color: #a696bb;
        }
        .stat-bottom {
          height: 29px;
          margin-top: 4px;
          display: flex;
          align-items: center;
          justify-content: space-between;
        }
        .balance-status {
          display: inline-flex;
          align-items: center;
          gap: 5px;
          font-size: 9px;
          color: #9684ab;
          background: #e7dff26b;
          border: 1px solid #ddcfec66;
          border-radius: 5px;
          line-height: 19px;
          padding: 0 6px;
        }
        .balance-status.positive {
          color: #628d7e;
          background: #e5f0e970;
          border-color: #dbe8e160;
        }
        .stat-meta {
          display: flex;
          align-items: center;
          gap: 6px;
          font-size: 9px;
          color: #a29ba9;
        }
        .tiny-dot {
          display: inline-block;
          width: 5px;
          height: 5px;
          border-radius: 50%;
          flex-shrink: 0;
        }
        .green-dot {
          background: #87af9d;
        }
        .peach-dot {
          background: #ddaf93;
        }
        .sparkline {
          opacity: 0.85;
          margin-right: -2px;
          margin-top: -7px;
        }
        .charts-grid {
          display: grid;
          grid-template-columns: minmax(0, 2.08fr) minmax(270px, 1fr);
          gap: 20px;
          margin-bottom: 21px;
          align-items: stretch;
        }
        .card {
          background: #fff;
          border: 1px solid #e7e5ee;
          border-radius: 12px;
          box-shadow: 0 2px 3px #34294302;
        }
        .card-heading {
          display: flex;
          align-items: flex-start;
          justify-content: space-between;
          gap: 12px;
          padding: 21px 22px 0;
        }
        .card-heading h2 {
          font-size: 13px;
          line-height: 21px;
          font-weight: 700;
          letter-spacing: -0.25px;
          display: flex;
          align-items: center;
          gap: 8px;
        }
        .card-heading p {
          font-size: 9px;
          line-height: 16px;
          color: #aaa0b0;
          margin-top: 4px;
        }
        .title-dot {
          width: 5px;
          height: 5px;
          background: #c0addb;
          border-radius: 50%;
        }
        .chart-tabs {
          display: flex;
          gap: 1px;
          align-self: center;
          background: #f7f6f9;
          border: 1px solid #f0edf4;
          border-radius: 7px;
          padding: 3px;
          flex-shrink: 0;
        }
        .chart-tabs button {
          background: transparent;
          border: 1px solid transparent;
          color: #a197a9;
          border-radius: 5px;
          font-size: 9px;
          font-weight: 500;
          padding: 6px 9px;
          line-height: 13px;
        }
        .chart-tabs button.active {
          color: #9479bb;
          background: #fff;
          box-shadow: 0 1px 3px #72578a08;
          border-color: #e9e2f0;
          font-weight: 600;
        }
        .chart-tabs button:hover:not(.active) {
          color: var(--primary);
          background: #f0eaf7;
        }
        .chart-legend {
          display: flex;
          align-items: center;
          flex-wrap: wrap;
          gap: 22px;
          margin: 17px 22px 6px;
        }
        .legend-item {
          display: inline-flex;
          align-items: center;
          gap: 6px;
          font-size: 9px;
          color: #a195ab;
          white-space: nowrap;
        }
        .legend-item i,
        .breakdown-legend i,
        .chart-tooltip i {
          display: inline-block;
          width: 6px;
          height: 6px;
          border-radius: 50%;
          flex-shrink: 0;
        }
        .legend-item b {
          font-size: 10px;
          color: #7e6c88;
          font-weight: 600;
          margin-left: 2px;
        }
        .chart-interval {
          margin-left: auto;
          display: inline-flex;
          align-items: center;
          gap: 6px;
          font-size: 8px;
          color: #b1a9bb;
        }
        .chart-plot {
          position: relative;
          margin: 5px 15px 5px 10px;
          border-radius: 4px;
        }
        .chart-plot > svg {
          display: block;
          overflow: visible;
        }
        .axis-label {
          fill: #b1a7bc;
          font-size: 8px;
          font-family: "Manrope", sans-serif;
          font-weight: 400;
        }
        .chart-line {
          stroke-dasharray: 1;
          animation: line-draw 1.3s cubic-bezier(0.22, 0.61, 0.36, 1) both;
        }
        .chart-area {
          animation: fade-in 1.5s 0.15s both;
        }
        .chart-tooltip {
          position: absolute;
          min-width: 192px;
          pointer-events: none;
          z-index: 5;
          border: 1px solid #eae2f0;
          box-shadow: 0 7px 28px #43325212;
          background: #fffffff5;
          backdrop-filter: blur(8px);
          border-radius: 9px;
          padding: 11px 13px;
          animation: fade-in 0.12s ease;
        }
        .chart-tooltip strong {
          font-size: 9px;
          color: #675a75;
          display: block;
          padding-bottom: 8px;
          margin-bottom: 5px;
          border-bottom: 1px solid #f0ebf3;
        }
        .chart-tooltip > div {
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 13px;
          font-size: 9px;
          padding: 3px 0;
        }
        .chart-tooltip > div span {
          display: flex;
          align-items: center;
          gap: 6px;
          color: #9f91ac;
        }
        .chart-tooltip b {
          color: #665672;
          font-weight: 600;
        }
        .chart-footnote {
          margin: 3px 22px 0;
          padding: 11px 0 12px;
          display: flex;
          align-items: center;
          gap: 4px;
          font-size: 9px;
          color: #ab9fb5;
          border-top: 1px solid #f0ecf4;
          min-height: 39px;
          flex-wrap: wrap;
        }
        .chart-footnote strong {
          font-weight: 600;
          color: #8d7a9e;
        }
        .footnote-icon {
          display: inline-flex;
          margin-right: 3px;
          color: #b09bce;
        }
        .breakdown-card {
          display: flex;
          flex-direction: column;
        }
        .breakdown-card .card-heading {
          gap: 2px;
        }
        .breakdown-card .icon-button {
          margin-top: -3px;
          margin-right: -5px;
        }
        .donut-wrap {
          position: relative;
          width: 156px;
          height: 156px;
          margin: 7px auto 6px;
          flex-shrink: 0;
        }
        .donut {
          width: 100%;
          height: 100%;
          overflow: visible;
          animation: ring-enter 0.9s 0.1s cubic-bezier(0.23, 1, 0.32, 1) both;
        }
        .donut-segment {
          cursor: pointer;
          transition:
            stroke-width 0.2s,
            opacity 0.2s;
        }
        .donut-center {
          position: absolute;
          inset: 0;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
          pointer-events: none;
          gap: 4px;
        }
        .donut-center > span {
          font-size: 21px;
          letter-spacing: -0.7px;
          font-weight: 700;
          color: #655674;
          font-variant-numeric: tabular-nums;
        }
        .donut-center small {
          font-size: 8px;
          color: #ab9bb8;
          max-width: 103px;
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }
        .breakdown-legend {
          display: flex;
          flex-direction: column;
          gap: 2px;
          margin: 0 25px 11px;
        }
        .breakdown-legend button {
          display: flex;
          justify-content: space-between;
          align-items: center;
          border: 0;
          background: transparent;
          border-radius: 4px;
          padding: 5px 0;
          font-size: 9px;
          line-height: 14px;
        }
        .breakdown-legend button > span {
          display: inline-flex;
          align-items: center;
          gap: 8px;
          color: #92839e;
          text-align: left;
        }
        .breakdown-legend button > b {
          font-weight: 600;
          color: #75627f;
          letter-spacing: 0.1px;
        }
        .breakdown-legend button > b > span {
          font-size: 8px;
          color: #b2a5bb;
          margin-left: 2px;
          font-weight: 500;
        }
        .breakdown-legend button:hover {
          background: #faf7fe;
          transform: translateX(2px);
        }
        .breakdown-footer {
          margin: auto 22px 0;
          padding: 12px 0;
          border-top: 1px solid #f0ecf4;
          color: #b1a7bb;
          font-size: 8px;
          display: flex;
          align-items: center;
          justify-content: space-between;
        }
        .empty-donut-text {
          font-size: 10px;
          line-height: 19px;
          text-align: center;
          color: #aea1b8;
          padding: 3px 0;
        }
        .transactions-card {
          position: relative;
        }
        .transactions-card .card-heading {
          align-items: center;
          padding-top: 19px;
          padding-bottom: 17px;
        }
        .transactions-card .card-heading p {
          margin-top: 3px;
        }
        .count-badge {
          display: inline-flex;
          justify-content: center;
          align-items: center;
          font-size: 8px;
          line-height: 16px;
          height: 17px;
          padding: 0 5px;
          min-width: 18px;
          color: #ac9fba;
          background: #f6f2fb;
          border: 1px solid #efe8f5;
          border-radius: 4px;
          margin-left: 2px;
          font-weight: 500;
        }
        .table-scroll {
          width: 100%;
        }
        table {
          width: 100%;
          border-collapse: collapse;
          border-spacing: 0;
          text-align: left;
        }
        thead {
          background: #fcfbfd;
        }
        th {
          padding: 10px 16px;
          border-top: 1px solid #f2eef5;
          border-bottom: 1px solid #f2eef5;
          color: #b0a4bb;
          font-size: 8px;
          letter-spacing: 0.7px;
          text-transform: uppercase;
          font-weight: 500;
          line-height: 13px;
          white-space: nowrap;
        }
        th:first-child,
        td:first-child {
          padding-left: 23px;
          width: 33%;
        }
        th:nth-child(2) {
          width: 28%;
        }
        th:nth-child(3) {
          width: 18%;
        }
        th:nth-child(4) {
          width: 17%;
        }
        th button {
          border: 0;
          background: none;
          font: inherit;
          color: inherit;
          text-transform: inherit;
          letter-spacing: inherit;
          display: inline-flex;
          align-items: center;
          gap: 7px;
          padding: 0;
        }
        th button:hover {
          color: #7e5bad;
        }
        .actions-head {
          width: 44px;
        }
        td {
          padding: 11px 16px;
          border-bottom: 1px solid #f2eef5;
          vertical-align: middle;
          font-size: 10px;
        }
        tbody tr:last-child td {
          border-bottom: 0;
        }
        tbody tr {
          transition: background 0.17s ease;
        }
        tbody tr:hover {
          background: #fdfbff;
        }
        .operation-name {
          display: flex;
          align-items: center;
          gap: 11px;
          text-align: left;
          border: 0;
          background: transparent;
          padding: 0;
          width: 100%;
        }
        .operation-name:hover strong {
          color: #8b6dc1;
        }
        .category-icon {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          width: 34px;
          height: 34px;
          border-radius: 9px;
          flex-shrink: 0;
        }
        .operation-name > span:last-child {
          min-width: 0;
        }
        .operation-name strong {
          display: block;
          font-size: 10px;
          font-weight: 600;
          color: #756280;
          white-space: normal;
          overflow-wrap: anywhere;
          line-height: 16px;
          transition: color 0.18s;
        }
        .operation-name small {
          display: block;
          font-size: 8px;
          color: #b3a7bd;
          line-height: 14px;
          margin-top: 1px;
        }
        .category-label {
          font-size: 8px;
          color: #a292ad;
          background: #f8f5fc;
          border: 1px solid #f0eaf6;
          padding: 4px 8px;
          border-radius: 5px;
          white-space: nowrap;
        }
        .date-cell {
          color: #9d8eaa;
          font-size: 9px;
          white-space: nowrap;
        }
        .date-cell span {
          color: #b3a7be;
        }
        .amount-cell {
          text-align: right;
        }
        th.amount-cell button {
          justify-content: flex-end;
        }
        .transaction-amount {
          font-size: 10px;
          font-weight: 650;
          color: #85738f;
          white-space: nowrap;
          font-variant-numeric: tabular-nums;
          letter-spacing: -0.1px;
        }
        .transaction-amount.income {
          color: #73a58e;
        }
        .row-actions {
          position: relative;
          display: inline-flex;
        }
        td:last-child {
          padding-left: 0;
          padding-right: 18px;
          text-align: right;
        }
        .row-actions > button {
          color: #b4a6c0;
        }
        .row-menu {
          position: absolute;
          right: 4px;
          bottom: 29px;
          min-width: 156px;
          border: 1px solid #e9e0f1;
          border-radius: 8px;
          background: #fff;
          padding: 5px;
          z-index: 12;
          box-shadow: 0 8px 25px #58406e14;
          animation: enter 0.15s ease;
        }
        .row-menu button {
          display: flex;
          align-items: center;
          text-align: left;
          gap: 8px;
          padding: 10px;
          font-size: 10px;
          width: 100%;
          background: none;
          border: 0;
          border-radius: 4px;
          color: #8e779e;
        }
        .row-menu button:hover {
          background: #f7f0fd;
        }
        .danger-text {
          color: #c67d84 !important;
        }
        .table-footer {
          border-top: 1px solid #f0eaf6;
          display: flex;
          justify-content: space-between;
          align-items: center;
          font-size: 8px;
          color: #b5a8c1;
          padding: 12px 23px;
          min-height: 39px;
        }
        .saved-status {
          display: flex;
          align-items: center;
          gap: 5px;
          color: #aaa1b2;
        }
        .saved-status svg {
          color: #8fb7a2;
        }
        .pagination {
          display: flex;
          align-items: center;
          gap: 10px;
        }
        .pagination span {
          color: #8f76a0;
          font-size: 10px;
        }
        .empty-state {
          padding: 43px 22px 48px;
          text-align: center;
          border-top: 1px solid #f1ebf7;
        }
        .empty-icon {
          width: 61px;
          height: 61px;
          border-radius: 19px;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          color: #a28bc4;
          background: #f4eefb;
          margin-bottom: 15px;
        }
        .empty-state h3 {
          color: #7e668f;
          font-size: 16px;
          margin-bottom: 9px;
        }
        .empty-state p {
          color: #ab9bb7;
          font-size: 11px;
          line-height: 20px;
        }
        .empty-state > div {
          display: flex;
          justify-content: center;
          flex-wrap: wrap;
          gap: 9px;
          margin-top: 20px;
        }
        .transaction-filters {
          display: flex;
          justify-content: space-between;
          align-items: center;
          gap: 16px;
          margin-bottom: 20px;
          padding: 15px 0 0;
          border-top: 1px solid #e9e3ef;
        }
        .type-filter {
          display: inline-flex;
          gap: 5px;
        }
        .type-filter button {
          display: flex;
          align-items: center;
          gap: 6px;
          border: 1px solid transparent;
          background: none;
          color: #a495b1;
          border-radius: 7px;
          font-size: 10px;
          font-weight: 500;
          padding: 10px 14px;
        }
        .type-filter button.active {
          color: #9477c4;
          background: #ece5f9;
          border-color: #e4daf3;
          font-weight: 600;
        }
        .type-filter button:hover {
          color: #8765b6;
          background: #f3eef9;
        }
        .filter-select {
          display: flex;
          gap: 8px;
          align-items: center;
          background: white;
          border: 1px solid #e8dfef;
          padding: 10px 12px;
          border-radius: 7px;
          color: #b2a1bf;
        }
        .filter-select select {
          border: 0;
          background: none;
          appearance: none;
          color: #9a85a8;
          font-size: 10px;
          max-width: 175px;
          padding-right: 8px;
          cursor: pointer;
        }
        .active-filters {
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 12px;
          margin: -3px 0 17px;
          color: #ad9cba;
          font-size: 10px;
        }
        .active-filters button {
          border: 0;
          background: none;
          padding: 4px 0;
          display: flex;
          align-items: center;
          gap: 4px;
          color: #9c7cbb;
          font-size: 9px;
        }
        .filtered-summary {
          display: flex;
          justify-content: space-between;
          gap: 20px;
          padding: 20px 6px;
          font-size: 9px;
          color: #aa98b8;
        }
        .filtered-summary > div {
          display: flex;
          gap: 24px;
        }
        .filtered-summary > div span {
          display: flex;
          flex-direction: column;
          gap: 6px;
        }
        .filtered-summary b {
          color: #7e658f;
          font-size: 13px;
          font-weight: 600;
        }
        .income-text {
          color: #72a089 !important;
        }
        .breakdown-type {
          display: flex;
          padding: 3px;
          background: #f8f4fc;
          border: 1px solid #eee5f6;
          border-radius: 6px;
          align-self: center;
          margin-top: 12px;
        }
        .breakdown-type button {
          background: transparent;
          border: none;
          color: #ac98bb;
          font-size: 9px;
          padding: 6px 19px;
          border-radius: 4px;
        }
        .breakdown-type button.active {
          background: #fff;
          box-shadow: 0 1px 3px #6941860a;
          color: #9274b6;
        }
        .analytics-charts .donut-wrap {
          margin-top: 16px;
          margin-bottom: 14px;
          width: 172px;
          height: 172px;
        }
        .insights-grid {
          display: grid;
          grid-template-columns: minmax(0, 2.08fr) minmax(270px, 1fr);
          gap: 20px;
        }
        .comparison-card {
          padding-bottom: 5px;
        }
        .muted {
          color: #bda9cf;
        }
        .comparison-head,
        .comparison-row {
          display: grid;
          grid-template-columns: 1fr 0.85fr 0.85fr;
          gap: 15px;
          margin: 0 22px;
          align-items: center;
        }
        .comparison-head {
          padding: 17px 0 12px;
          font-size: 9px;
          color: #bba9c8;
          border-bottom: 1px solid #f1e9f8;
        }
        .comparison-head > :not(:first-child),
        .comparison-row > :not(:first-child) {
          text-align: right;
        }
        .comparison-row {
          padding: 15px 0;
          border-bottom: 1px solid #f2eafa;
          font-size: 11px;
          color: #ae99bd;
        }
        .comparison-row:last-child {
          border-bottom: 0;
        }
        .comparison-row > span:first-child {
          display: flex;
          gap: 10px;
          align-items: center;
          color: #9379a5;
        }
        .comparison-row strong {
          font-weight: 600;
          color: #8a6d9e;
        }
        .insight-card {
          background: linear-gradient(140deg, #f2edf9, #f9f5fb);
          padding: 20px 22px;
          border-color: #e9def3;
        }
        .insight-symbol {
          display: flex;
          width: 33px;
          height: 33px;
          align-items: center;
          justify-content: center;
          border: 1px solid #e5d6f0;
          border-radius: 9px;
          background: #fff9;
          color: #a085c4;
          margin-bottom: 11px;
        }
        .insight-card h2 {
          font-size: 14px;
          color: #9271a5;
          font-weight: 700;
          margin-bottom: 19px;
        }
        .insight-line {
          display: flex;
          justify-content: space-between;
          gap: 15px;
          font-size: 9px;
          padding: 11px 0;
          border-bottom: 1px solid #e9dfee;
        }
        .insight-line > span {
          color: #af98c0;
        }
        .insight-line strong {
          color: #9675a9;
          font-size: 11px;
          font-weight: 600;
          text-align: right;
        }
        .insight-line small {
          display: block;
          font-size: 8px;
          font-weight: 400;
          color: #b09ac0;
          padding-top: 4px;
        }
        .insight-card p {
          font-size: 8px;
          line-height: 16px;
          color: #b09cbf;
          margin-top: 14px;
        }
        .insight-card p .tiny-dot {
          margin-right: 5px;
          width: 4px;
          height: 4px;
        }
        .categories-top {
          display: flex;
          justify-content: space-between;
          align-items: center;
          gap: 16px;
          margin: 25px 0 20px;
        }
        .categories-top p {
          font-size: 9px;
          color: #b39dc0;
        }
        .categories-grid {
          display: grid;
          grid-template-columns: repeat(3, minmax(0, 1fr));
          gap: 20px;
        }
        .category-card {
          padding: 21px;
          transition:
            box-shadow 0.2s,
            border-color 0.2s,
            transform 0.2s;
        }
        .category-card:hover {
          box-shadow: 0 8px 25px #71508b09;
          border-color: #deceed;
          transform: translateY(-2px);
        }
        .category-card-top {
          display: flex;
          align-items: center;
          justify-content: space-between;
          margin-bottom: 17px;
        }
        .category-card .category-icon {
          width: 43px;
          height: 43px;
          border-radius: 12px;
        }
        .category-card h2 {
          font-size: 13px;
          font-weight: 650;
          color: #9371a3;
        }
        .category-card-count {
          font-size: 9px;
          color: #bba6c6;
          display: block;
          margin-top: 6px;
        }
        .category-total {
          display: flex;
          justify-content: space-between;
          align-items: center;
          width: 100%;
          border: 0;
          background: none;
          font-size: 24px;
          font-weight: 600;
          color: #9371a3;
          padding: 21px 0 16px;
          letter-spacing: -0.6px;
        }
        .category-total svg {
          color: #bfaad0;
          transition: transform 0.2s;
        }
        .category-total:hover svg {
          color: #8565ab;
          transform: translate(2px, -2px);
        }
        .category-progress {
          height: 4px;
          background: #f4effa;
          border-radius: 3px;
          overflow: hidden;
        }
        .category-progress span {
          height: 100%;
          display: block;
          border-radius: 3px;
          transition: width 0.7s ease;
        }
        .category-share {
          display: flex;
          justify-content: space-between;
          margin-top: 10px;
          font-size: 8px;
          color: #bca5cc;
        }
        .category-share b {
          font-weight: 500;
          color: #a789bb;
        }
        .new-category-card {
          border: 1px dashed #dccce9;
          background: transparent;
          border-radius: 12px;
          display: flex;
          flex-direction: column;
          gap: 10px;
          align-items: center;
          justify-content: center;
          min-height: 249px;
        }
        .new-category-card > span {
          display: flex;
          justify-content: center;
          align-items: center;
          color: #b496cf;
          background: #f0e8f9;
          width: 43px;
          height: 43px;
          border-radius: 12px;
          margin-bottom: 3px;
        }
        .new-category-card strong {
          font-size: 12px;
          color: #ac8dc0;
          font-weight: 500;
        }
        .new-category-card small {
          font-size: 9px;
          color: #c1acd0;
        }
        .new-category-card:hover {
          background: #f6effb;
          border-color: #b79bce;
        }
        .page-footer {
          display: flex;
          justify-content: space-between;
          gap: 15px;
          font-size: 8px;
          color: #b6acbf;
          padding: 23px 2px 25px;
        }
        .page-footer > span:last-child {
          display: flex;
          align-items: center;
          gap: 6px;
        }
        .page-footer i {
          display: inline-block;
          width: 3px;
          height: 3px;
          border-radius: 50%;
          background: #ccc0d6;
          margin: 0 6px;
        }
        .storage-warning {
          display: flex;
          align-items: center;
          gap: 10px;
          background: #fbecdd;
          border: 1px solid #ecd6bd;
          color: #ac8560;
          font-size: 11px;
          line-height: 20px;
          border-radius: 9px;
          padding: 12px 15px;
          margin-bottom: 18px;
        }
        .storage-warning svg {
          flex-shrink: 0;
        }

        .modal-overlay {
          position: fixed;
          inset: 0;
          z-index: 100;
          background: #2d253c48;
          backdrop-filter: blur(5px);
          display: flex;
          align-items: center;
          justify-content: center;
          padding: 24px;
          overflow-y: auto;
          animation: fade-in 0.2s ease;
        }
        .modal {
          background: #fff;
          border: 1px solid #f2eafa;
          box-shadow: 0 25px 100px #31243e30;
          border-radius: 19px;
          width: 470px;
          max-width: 100%;
          padding: 27px;
          animation: modal-enter 0.3s cubic-bezier(0.2, 0.75, 0.25, 1) both;
          max-height: calc(100dvh - 48px);
          overflow-y: auto;
        }
        .modal.wide {
          width: 580px;
        }
        .modal-heading {
          display: flex;
          gap: 12px;
          align-items: flex-start;
          justify-content: space-between;
          margin-bottom: 23px;
        }
        .modal-heading h2 {
          color: #574564;
          font-size: 21px;
          font-weight: 700;
          line-height: 30px;
          letter-spacing: -0.65px;
        }
        .modal-heading p {
          color: #ae9cbb;
          font-size: 10px;
          line-height: 18px;
          margin-top: 5px;
        }
        .modal-close {
          margin-top: -4px;
          margin-right: -8px;
          color: #baa8c7;
        }
        .type-switch {
          display: flex;
          background: #f8f5fb;
          border: 1px solid #eee6f5;
          border-radius: 9px;
          padding: 4px;
          margin-bottom: 18px;
        }
        .type-switch button {
          flex: 1;
          display: flex;
          align-items: center;
          justify-content: center;
          gap: 7px;
          padding: 10px;
          border-radius: 6px;
          border: 1px solid transparent;
          background: none;
          color: #b5a0c4;
          font-size: 11px;
          font-weight: 600;
        }
        .type-switch button.active {
          background: #fff;
          border-color: #ede3f5;
          box-shadow: 0 2px 5px #653c7807;
        }
        .type-switch button.active.expense {
          color: #c99980;
        }
        .type-switch button.active.income {
          color: #79a28d;
        }
        .amount-input {
          display: block;
          background: #faf8fc;
          border: 1px solid #efe8f6;
          border-radius: 11px;
          padding: 17px 19px;
          margin-bottom: 20px;
          transition:
            border-color 0.2s,
            box-shadow 0.2s;
        }
        .amount-input:focus-within {
          border-color: #b9a0dc;
          box-shadow: 0 0 0 3px #eee4fb;
        }
        .amount-input > span {
          font-size: 9px;
          color: #b29ac2;
        }
        .amount-input > div {
          display: flex;
          align-items: center;
          gap: 8px;
          margin-top: 9px;
        }
        .amount-input input {
          background: none;
          border: none;
          padding: 0;
          width: 100%;
          font-size: 33px;
          font-weight: 600;
          letter-spacing: -0.7px;
          line-height: 45px;
          color: #755589;
        }
        .amount-input input::placeholder {
          color: #c5b0d4;
          font-weight: 500;
        }
        .amount-input > div > span {
          font-size: 27px;
          color: #bda4cf;
        }
        .field {
          display: flex;
          flex-direction: column;
          gap: 8px;
          margin-bottom: 17px;
          min-width: 0;
        }
        .field > span {
          font-size: 10px;
          color: #9474a6;
          font-weight: 600;
        }
        .field > span i {
          font-style: normal;
          color: #c0a2d3;
          font-weight: 500;
        }
        .field > span small {
          font-size: 9px;
          font-weight: 400;
          color: #bea7cd;
          margin-left: 4px;
        }
        .field input,
        .field select,
        .field textarea {
          width: 100%;
          background: #fff;
          border: 1px solid #e7daef;
          border-radius: 7px;
          padding: 11px 12px;
          font-size: 11px;
          color: #8d6c9f;
          line-height: 18px;
        }
        .field input:focus,
        .field select:focus,
        .field textarea:focus {
          border-color: #b196d0;
          box-shadow: 0 0 0 3px #f2e9fb;
        }
        .field input::placeholder,
        .field textarea::placeholder {
          color: #c3afd0;
          font-size: 10px;
        }
        .field input[type="date"] {
          color-scheme: light;
          padding-right: 9px;
        }
        .field input::-webkit-calendar-picker-indicator {
          opacity: 0.35;
          cursor: pointer;
        }
        .field textarea {
          resize: vertical;
          min-height: 64px;
          max-height: 170px;
        }
        .field-hint {
          color: #baa3c9;
          font-size: 9px;
          line-height: 16px;
        }
        .select-wrap {
          position: relative;
        }
        .select-wrap select {
          appearance: none;
          padding-right: 31px;
          cursor: pointer;
        }
        .select-wrap select:disabled {
          background: #faf7fc;
          color: #b39ac3;
          cursor: not-allowed;
        }
        .select-wrap svg {
          position: absolute;
          right: 13px;
          top: 50%;
          transform: translateY(-50%);
          pointer-events: none;
          color: #bca1cd;
        }
        .form-row {
          display: grid;
          grid-template-columns: 1fr 1fr;
          gap: 14px;
        }
        .modal-footer {
          display: flex;
          justify-content: space-between;
          gap: 12px;
          padding-top: 19px;
          margin-top: 7px;
          border-top: 1px solid #f0e7f6;
        }
        .modal-footer .button {
          min-height: 41px;
          font-size: 10px;
        }
        .modal-footer .button:last-child {
          margin-left: auto;
        }
        .form-privacy {
          display: flex;
          align-items: center;
          justify-content: center;
          gap: 5px;
          font-size: 8px;
          color: #c0aacc;
          padding-top: 15px;
        }
        .form-error {
          display: flex;
          align-items: center;
          gap: 7px;
          background: #fcf0f0;
          border-radius: 7px;
          padding: 10px 11px;
          color: #be7e85;
          font-size: 10px;
          line-height: 18px;
          margin: 8px 0 15px;
        }
        .form-error svg {
          flex-shrink: 0;
        }
        .category-preview {
          display: flex;
          flex-direction: column;
          gap: 11px;
          align-items: center;
          justify-content: center;
          padding: 3px 0 25px;
        }
        .category-preview .category-icon {
          width: 61px;
          height: 61px;
          border-radius: 18px;
        }
        .category-preview strong {
          font-size: 12px;
          font-weight: 600;
          color: #a484b9;
        }
        .color-options {
          display: flex;
          justify-content: space-between;
          gap: 8px;
          margin: 3px 0 2px;
        }
        .color-options button {
          border: 3px solid white;
          box-shadow: 0 0 0 1px #ece4f3;
          border-radius: 50%;
          width: 32px;
          height: 32px;
          display: flex;
          align-items: center;
          justify-content: center;
          color: white;
          flex-shrink: 0;
        }
        .color-options button[aria-pressed="true"] {
          box-shadow: 0 0 0 2px #cfbde3;
        }
        .icon-options {
          display: grid;
          grid-template-columns: repeat(10, 1fr);
          gap: 5px;
        }
        .icon-options button {
          border: 1px solid #efe5f7;
          border-radius: 7px;
          background: #fcfaff;
          display: flex;
          justify-content: center;
          align-items: center;
          height: 34px;
          color: #b19ac4;
          padding: 0;
        }
        .icon-options button.active {
          border-color: #bca0d7;
          background: #efe4fa;
          color: #8d6bad;
        }
        .privacy-box {
          background: #f6f2fb;
          border: 1px solid #ece1f6;
          border-radius: 10px;
          padding: 14px;
          display: flex;
          align-items: flex-start;
          gap: 12px;
          margin: 21px 0;
        }
        .privacy-box > svg {
          color: #b095cc;
          flex-shrink: 0;
          margin-top: 2px;
        }
        .privacy-box strong {
          color: #9978b1;
          font-size: 10px;
          font-weight: 600;
        }
        .privacy-box p {
          color: #b69cc9;
          font-size: 9px;
          line-height: 17px;
          margin-top: 5px;
        }
        .settings-section h3 {
          display: flex;
          align-items: center;
          justify-content: space-between;
          color: #a17eb4;
          font-size: 11px;
          font-weight: 600;
          margin-bottom: 10px;
        }
        .settings-section h3 > span {
          color: #c3abd1;
          font-size: 8px;
          font-weight: 400;
        }
        .data-actions {
          border: 1px solid #eee3f6;
          border-radius: 9px;
          overflow: hidden;
        }
        .data-actions > button {
          border: 0;
          border-bottom: 1px solid #f1e8f7;
          background: #fff;
          padding: 12px;
          display: flex;
          align-items: center;
          gap: 10px;
          width: 100%;
          text-align: left;
          color: #af91c2;
        }
        .data-actions > button:nth-last-of-type(1) {
          border-bottom: 0;
        }
        .data-actions > button:hover {
          background: #fcf8ff;
        }
        .data-actions > button > span {
          flex: 1;
          font-size: 10px;
          color: #a081b3;
        }
        .data-actions small {
          display: block;
          font-size: 8px;
          color: #c2a8d1;
          margin-top: 4px;
        }
        .settings-danger {
          display: flex;
          justify-content: space-between;
          gap: 12px;
          margin: 15px 0 6px;
        }
        .settings-danger button {
          display: flex;
          align-items: center;
          gap: 6px;
          border: 0;
          background: none;
          padding: 4px 0;
          font-size: 9px;
          color: #b9a0ca;
        }
        .settings-danger button:hover {
          color: #9672af;
        }
        .confirm-modal {
          width: 430px;
        }
        .confirm-modal .modal-heading {
          margin-bottom: 15px;
        }
        .confirm-symbol {
          width: 47px;
          height: 47px;
          border-radius: 14px;
          background: #f1e8fa;
          color: #ab8ac7;
          display: grid;
          place-items: center;
          margin-bottom: 18px;
        }
        .confirm-symbol.danger {
          background: #fbebee;
          color: #c78897;
        }
        .confirm-description {
          color: #ab93bc;
          font-size: 11px;
          line-height: 22px;
          margin-bottom: 22px;
        }
        .help-item {
          display: flex;
          align-items: flex-start;
          gap: 14px;
          margin-bottom: 23px;
        }
        .help-item > span {
          width: 39px;
          height: 39px;
          border-radius: 11px;
          background: #f5edfd;
          color: #b091cb;
          display: flex;
          align-items: center;
          justify-content: center;
          flex-shrink: 0;
        }
        .help-item h3 {
          font-size: 12px;
          font-weight: 600;
          color: #9a76b0;
          margin-top: 2px;
        }
        .help-item p {
          font-size: 10px;
          line-height: 20px;
          color: #b99ece;
          margin-top: 6px;
        }
        .keyboard-hints {
          display: flex;
          gap: 24px;
          border-top: 1px solid #f0e5f8;
          padding: 17px 0 22px;
        }
        .keyboard-hints > span {
          display: flex;
          align-items: center;
          gap: 7px;
          font-size: 9px;
          color: #bea4ce;
        }
        .keyboard-hints kbd {
          background: #faf5ff;
          border-color: #ecdef6;
          color: #b59ac7;
        }
        .toast {
          display: flex;
          align-items: center;
          gap: 11px;
          max-width: calc(100vw - 32px);
          position: fixed;
          bottom: 28px;
          left: calc(50% + var(--sidebar) / 2);
          transform: translateX(-50%);
          z-index: 200;
          background: #fff;
          border: 1px solid #e7ddef;
          box-shadow: 0 8px 40px #3c2e5426;
          padding: 15px 15px 15px 18px;
          border-radius: 11px;
          font-size: 11px;
          color: #9575a9;
          animation: fade-in 0.25s ease;
        }
        .toast > svg {
          color: #8cb09c;
          flex-shrink: 0;
        }
        .toast.error > svg {
          color: #c48a85;
        }
        .toast button {
          background: none;
          border: 0;
          display: flex;
          margin-left: 10px;
          padding: 3px;
          color: #c4afd3;
        }
        .sidebar-backdrop,
        .mobile-menu,
        .mobile-close {
          display: none;
        }
      }

      @layer responsive {
        @media (min-width: 1550px) {
          :root {
            --sidebar: 246px;
          }
          .sidebar {
            padding-left: 23px;
            padding-right: 23px;
          }
          .main-content {
            padding-top: 35px;
            padding-left: 43px;
            padding-right: 43px;
          }
          .topbar {
            padding-left: 43px;
            padding-right: 43px;
          }
          .page-heading {
            margin-bottom: 28px;
          }
          h1 {
            font-size: 33px;
          }
          .page-heading p {
            font-size: 12px;
          }
          .stat-card {
            min-height: 158px;
            padding: 21px 25px 16px;
          }
          .money {
            font-size: 35px;
            line-height: 48px;
          }
          .stat-top {
            font-size: 12px;
          }
          .stat-bottom {
            margin-top: 8px;
          }
          .card-heading {
            padding: 25px 27px 0;
          }
          .card-heading h2 {
            font-size: 15px;
          }
          .card-heading p {
            font-size: 10px;
          }
          .chart-legend {
            margin: 19px 27px 10px;
          }
          .chart-plot {
            margin-top: 12px;
            margin-bottom: 13px;
          }
          .chart-tabs button {
            font-size: 10px;
            padding: 7px 12px;
          }
          .donut-wrap {
            width: 171px;
            height: 171px;
            margin: 14px auto 12px;
          }
          .breakdown-legend {
            margin-left: 29px;
            margin-right: 29px;
          }
          .breakdown-legend button {
            font-size: 10px;
            padding: 5px 0;
          }
          .breakdown-footer {
            font-size: 9px;
          }
          .transaction-amount,
          .operation-name strong {
            font-size: 11px;
          }
          td {
            padding-top: 13px;
            padding-bottom: 13px;
          }
        }
        @media (max-width: 1200px) {
          :root {
            --sidebar: 207px;
          }
          .sidebar {
            padding-left: 13px;
            padding-right: 13px;
          }
          .topbar {
            padding: 0 25px;
          }
          .main-content {
            padding: 26px 25px 0;
          }
          .topbar-right {
            gap: 14px;
          }
          .breadcrumb {
            gap: 10px;
            font-size: 9px;
          }
          .topbar .today {
            display: none;
          }
          .page-actions .button {
            font-size: 10px;
            padding: 10px 12px;
          }
          .page-heading p {
            font-size: 10px;
          }
          h1 {
            font-size: 28px;
          }
          .stat-card {
            padding: 16px 17px 13px;
          }
          .stats-grid {
            gap: 14px;
          }
          .money {
            font-size: 28px;
          }
          .money-decimals,
          .money-symbol {
            font-size: 21px;
          }
          .charts-grid,
          .insights-grid {
            grid-template-columns: minmax(0, 1.85fr) minmax(253px, 1fr);
            gap: 16px;
          }
          .card-heading {
            padding-left: 18px;
            padding-right: 18px;
          }
          .card-heading h2 {
            font-size: 12px;
          }
          .card-heading p {
            font-size: 8px;
          }
          .chart-tabs button {
            padding: 5px 7px;
            font-size: 8px;
          }
          .chart-tabs {
            padding: 2px;
          }
          .title-dot {
            display: none;
          }
          .chart-legend {
            gap: 13px;
            margin-left: 18px;
            margin-right: 18px;
          }
          .legend-item {
            font-size: 8px;
            gap: 4px;
          }
          .legend-item b {
            font-size: 9px;
          }
          .chart-footnote {
            font-size: 8px;
            margin-left: 18px;
            margin-right: 18px;
          }
          .breakdown-legend {
            margin-left: 20px;
            margin-right: 20px;
          }
          .operation-name {
            gap: 8px;
          }
          .category-icon {
            width: 31px;
            height: 31px;
            border-radius: 8px;
          }
          td {
            padding-left: 10px;
            padding-right: 10px;
          }
          th {
            padding-left: 10px;
            padding-right: 10px;
          }
          th:first-child,
          td:first-child {
            padding-left: 18px;
          }
          .category-label {
            padding: 4px 6px;
          }
          .categories-grid {
            gap: 16px;
          }
          .category-card {
            padding: 18px;
          }
          .categories-top p {
            font-size: 8px;
          }
        }
        @media (max-height: 800px) and (min-width: 901px) {
          .sidebar {
            padding-top: 23px;
          }
          .workspace-picker {
            margin-top: 25px;
          }
          .calm-card {
            padding-top: 13px;
          }
          .calm-flower {
            width: 38px;
            height: 38px;
            margin-bottom: 5px;
          }
          .calm-card h3 {
            font-size: 13px;
            line-height: 21px;
          }
          .calm-card p {
            margin-top: 5px;
          }
          .calm-footer {
            margin-top: 12px;
            padding: 9px 0;
          }
          .sidebar-bottom {
            padding-top: 20px;
          }
          .profile {
            padding-top: 16px;
            padding-bottom: 16px;
          }
        }
        @media (max-height: 695px) and (min-width: 901px) {
          .calm-card {
            display: none;
          }
        }
        @media (max-width: 1050px) and (min-width: 901px) {
          .chart-tabs {
            gap: 0;
          }
          .chart-tabs button {
            padding-left: 5px;
            padding-right: 5px;
          }
          .flow-chart .card-heading {
            gap: 7px;
            flex-wrap: wrap;
          }
          .flow-chart .card-heading p {
            display: none;
          }
          .chart-interval {
            display: none;
          }
          .stat-top {
            font-size: 10px;
          }
          .stat-icon {
            width: 27px;
            height: 27px;
          }
          .money {
            font-size: 25px;
            letter-spacing: -0.5px;
          }
          .money-decimals,
          .money-symbol {
            font-size: 19px;
          }
          .money-symbol {
            margin-left: 5px;
          }
          .sparkline {
            width: 65px;
          }
          .breakdown-card .card-heading h2 {
            font-size: 11px;
          }
          .categories-grid {
            grid-template-columns: repeat(2, minmax(0, 1fr));
          }
        }
        @media (max-width: 900px) {
          :root {
            --sidebar: 0px;
          }
          .sidebar {
            width: 226px;
            transform: translateX(-100%);
            transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
            padding-left: 18px;
            padding-right: 18px;
          }
          .sidebar.is-open {
            transform: translateX(0);
            z-index: 80;
          }
          .sidebar-backdrop {
            display: block;
            position: fixed;
            inset: 0;
            z-index: 75;
            background: #3a2b4b44;
            backdrop-filter: blur(3px);
            animation: fade-in 0.2s;
          }
          .mobile-menu,
          .mobile-close {
            display: inline-flex;
          }
          .mobile-close {
            width: 25px;
          }
          .sidebar .brand-button {
            padding-left: 5px;
          }
          .topbar {
            height: 65px;
          }
          .breadcrumb {
            gap: 9px;
          }
          .breadcrumb > span {
            font-size: 10px;
          }
          .topbar .today {
            display: inline-flex;
          }
          .main-content {
            padding: 25px 25px 0;
          }
          .chart-legend {
            margin-top: 20px;
          }
          .categories-grid {
            grid-template-columns: repeat(3, minmax(0, 1fr));
          }
          .calm-card {
            max-height: 215px;
          }
          .calm-flower {
            width: 42px;
            height: 42px;
          }
        }
        @media (max-width: 700px) {
          .topbar {
            padding: 0 19px;
          }
          .topbar-right {
            gap: 13px;
          }
          .topbar .today,
          .topbar-divider {
            display: none;
          }
          .breadcrumb > span {
            display: none;
          }
          .breadcrumb > svg {
            display: none;
          }
          .breadcrumb strong {
            font-size: 11px;
          }
          .global-search {
            padding-left: 5px;
          }
          .global-search input {
            width: 113px;
            font-size: 10px;
          }
          .global-search kbd {
            display: none;
          }
          .main-content {
            padding: 23px 20px 0;
          }
          .page-heading {
            align-items: flex-start;
            margin-bottom: 22px;
          }
          h1 {
            font-size: 27px;
            line-height: 37px;
          }
          .page-heading p {
            font-size: 10px;
            line-height: 17px;
            max-width: 260px;
          }
          .page-actions {
            padding-top: 4px;
            gap: 6px;
          }
          .page-actions .button.secondary {
            padding: 10px;
            font-size: 0;
            gap: 0;
            width: 37px;
          }
          .page-actions .button.primary {
            padding: 10px;
            font-size: 0;
            gap: 0;
            width: 39px;
            border-radius: 9px;
          }
          .page-actions .button.primary svg {
            width: 21px;
            height: 21px;
          }
          .period-toolbar {
            align-items: flex-start;
            flex-wrap: wrap;
            gap: 12px;
            margin-bottom: 20px;
          }
          .period-controls button {
            font-size: 10px;
            padding-left: 13px;
            padding-right: 13px;
          }
          .date-range {
            align-self: center;
            font-size: 9px;
            gap: 5px;
          }
          .date-range svg {
            width: 13px;
          }
          .stats-grid {
            gap: 12px;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            margin-bottom: 17px;
          }
          .balance-card {
            grid-column: 1 / -1;
            min-height: 123px;
          }
          .balance-card .stat-top {
            justify-content: flex-start;
          }
          .balance-card .stat-icon {
            position: absolute;
            right: 3px;
            top: 2px;
            width: 34px;
            height: 34px;
          }
          .balance-card .money {
            font-size: 32px;
            margin-top: 7px;
          }
          .balance-card .stat-bottom {
            position: absolute;
            bottom: 20px;
            right: 19px;
          }
          .balance-art {
            right: 22px;
            top: -21px;
            width: 230px;
            height: 215px;
          }
          .stat-card {
            padding: 17px 18px 12px;
          }
          .stat-top {
            font-size: 11px;
          }
          .money {
            font-size: 26px;
            line-height: 37px;
          }
          .money-decimals,
          .money-symbol {
            font-size: 19px;
          }
          .stat-card:not(.balance-card) {
            min-height: 138px;
          }
          .sparkline {
            width: 72px;
          }
          .charts-grid,
          .insights-grid {
            grid-template-columns: 1fr;
            gap: 17px;
            margin-bottom: 17px;
          }
          .card-heading {
            padding-left: 20px;
            padding-right: 20px;
          }
          .card-heading h2 {
            font-size: 13px;
          }
          .card-heading p {
            font-size: 9px;
          }
          .chart-tabs button {
            padding: 6px 9px;
            font-size: 9px;
          }
          .chart-legend {
            margin-left: 20px;
            margin-right: 20px;
            gap: 20px;
          }
          .legend-item {
            font-size: 9px;
          }
          .legend-item b {
            font-size: 10px;
          }
          .chart-interval {
            font-size: 8px;
          }
          .chart-footnote {
            font-size: 9px;
            margin: 0 20px;
          }
          .breakdown-card {
            display: grid;
            grid-template-columns: 44% 56%;
            grid-template-rows: auto 1fr auto;
          }
          .breakdown-card .card-heading {
            grid-column: 1 / -1;
          }
          .donut-wrap {
            grid-column: 1;
            grid-row: 2;
            align-self: center;
            width: 159px;
            height: 159px;
            margin: 11px auto 12px;
          }
          .breakdown-legend {
            grid-column: 2;
            grid-row: 2;
            align-self: center;
            margin: 0 24px 0 8px;
            gap: 10px;
          }
          .breakdown-legend button {
            font-size: 10px;
          }
          .breakdown-footer {
            grid-column: 1 / -1;
            margin-left: 20px;
            margin-right: 20px;
            font-size: 9px;
          }
          .breakdown-type {
            grid-column: 1 / -1;
            grid-row: 2;
            justify-self: center;
            margin-bottom: 0;
          }
          .analytics-charts .donut-wrap {
            grid-row: 3;
            width: 169px;
            height: 169px;
            margin: 11px auto 12px;
          }
          .analytics-charts .breakdown-legend {
            grid-row: 3;
          }
          .analytics-charts .breakdown-footer {
            grid-row: 4;
          }
          .table-scroll {
            overflow-x: auto;
            min-height: 130px;
          }
          table {
            min-width: 600px;
          }
          th:first-child,
          td:first-child {
            padding-left: 20px;
          }
          .transactions-card .card-heading {
            padding-bottom: 17px;
          }
          .row-menu {
            position: absolute;
          }
          .table-footer {
            padding-left: 20px;
            padding-right: 20px;
          }
          .operation-name strong {
            font-size: 11px;
          }
          .transaction-amount {
            font-size: 11px;
          }
          td {
            padding-top: 12px;
            padding-bottom: 12px;
          }
          .page-footer > span:first-child {
            display: none;
          }
          .page-footer {
            justify-content: center;
            padding-top: 20px;
          }
          .page-footer > span:last-child {
            flex-wrap: wrap;
            justify-content: center;
            font-size: 8px;
          }
          .transaction-filters {
            flex-wrap: wrap;
          }
          .type-filter {
            gap: 4px;
          }
          .type-filter button {
            padding: 9px 11px;
            font-size: 10px;
          }
          .filter-select {
            padding: 9px 11px;
          }
          .filtered-summary {
            flex-direction: column;
            gap: 17px;
            padding: 21px 2px;
          }
          .filtered-summary > div {
            justify-content: space-between;
          }
          .filtered-summary b {
            font-size: 12px;
          }
          .categories-grid {
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 13px;
          }
          .categories-top {
            margin-top: 20px;
            flex-wrap: wrap;
            gap: 12px;
          }
          .categories-top p {
            font-size: 9px;
          }
          .category-card {
            padding: 17px;
          }
          .category-card h2 {
            font-size: 12px;
          }
          .category-total {
            font-size: 21px;
          }
          .category-card .category-icon {
            width: 38px;
            height: 38px;
          }
          .new-category-card {
            min-height: 237px;
          }
          .toast {
            left: 50%;
            bottom: 20px;
            font-size: 10px;
            width: max-content;
            max-width: calc(100vw - 30px);
            padding: 13px 12px 13px 15px;
          }
          .modal-overlay {
            padding: 15px;
          }
          .modal {
            max-height: calc(100dvh - 30px);
            padding: 24px;
            border-radius: 16px;
          }
          .modal-heading h2 {
            font-size: 20px;
          }
          .modal-footer .button {
            font-size: 10px;
            padding-left: 13px;
            padding-right: 13px;
          }
          .modal-heading p {
            font-size: 10px;
          }
        }
        @media (max-width: 430px) {
          .topbar {
            padding: 0 13px;
          }
          .top-avatar {
            width: 26px;
            height: 26px;
          }
          .topbar-right {
            gap: 11px;
          }
          .main-content {
            padding: 21px 16px 0;
          }
          h1 {
            font-size: 26px;
          }
          .page-heading p {
            max-width: 245px;
            font-size: 9px;
          }
          .page-actions {
            gap: 5px;
          }
          .page-actions .button.secondary {
            display: none;
          }
          .page-actions .button.primary {
            width: 37px;
            min-height: 37px;
            padding: 8px;
          }
          .period-toolbar {
            gap: 12px;
          }
          .period-controls {
            padding: 3px;
            width: 100%;
            justify-content: space-between;
          }
          .period-controls button {
            flex: 1;
            padding-left: 9px;
            padding-right: 9px;
          }
          .date-range {
            font-size: 9px;
          }
          .stat-card {
            padding: 14px 15px 10px;
          }
          .balance-card {
            min-height: 132px;
          }
          .balance-card .stat-bottom {
            position: relative;
            bottom: auto;
            right: auto;
            margin-top: 3px;
          }
          .balance-card .money {
            font-size: 30px;
            margin-top: 5px;
          }
          .balance-card .stat-icon {
            top: 0;
            right: 0;
          }
          .balance-art {
            right: -15px;
            top: -17px;
          }
          .stat-card:not(.balance-card) {
            min-height: 132px;
          }
          .stat-top {
            font-size: 10px;
          }
          .stat-icon {
            width: 26px;
            height: 26px;
          }
          .stat-icon svg {
            width: 17px;
            height: 17px;
          }
          .money {
            font-size: 23px;
            margin-top: 10px;
          }
          .money-decimals,
          .money-symbol {
            font-size: 17px;
          }
          .money-symbol {
            margin-left: 5px;
          }
          .stat-meta {
            font-size: 8px;
            gap: 4px;
          }
          .sparkline {
            width: 50px;
            height: 29px;
            margin-top: 0;
          }
          .card-heading {
            padding-left: 16px;
            padding-right: 16px;
            gap: 7px;
          }
          .card-heading h2 {
            font-size: 12px;
          }
          .card-heading p {
            font-size: 8px;
          }
          .flow-chart .card-heading {
            align-items: flex-start;
            flex-wrap: wrap;
          }
          .chart-tabs {
            padding: 2px;
          }
          .chart-tabs button {
            padding: 5px 7px;
            font-size: 8px;
          }
          .chart-legend {
            margin: 16px 16px 7px;
            gap: 15px;
          }
          .legend-item {
            gap: 5px;
            font-size: 8px;
          }
          .legend-item b {
            font-size: 9px;
          }
          .chart-interval {
            display: none;
          }
          .chart-footnote {
            font-size: 8px;
            margin-left: 16px;
            margin-right: 16px;
            gap: 3px;
          }
          .donut-wrap {
            width: 145px;
            height: 145px;
          }
          .donut-center > span {
            font-size: 19px;
          }
          .donut-center small {
            font-size: 7px;
          }
          .breakdown-legend {
            margin-right: 16px;
            margin-left: 9px;
            gap: 8px;
          }
          .breakdown-legend button {
            font-size: 8px;
          }
          .breakdown-legend button > span {
            gap: 5px;
          }
          .breakdown-legend i {
            width: 5px;
            height: 5px;
          }
          .breakdown-footer {
            margin-left: 16px;
            margin-right: 16px;
            font-size: 8px;
          }
          .text-button {
            font-size: 9px;
            gap: 6px;
          }
          .text-button svg {
            width: 13px;
          }
          .count-badge {
            font-size: 7px;
          }
          .table-footer {
            padding-left: 16px;
            padding-right: 16px;
            font-size: 7px;
          }
          .saved-status {
            gap: 4px;
          }
          .category-card {
            padding: 14px;
          }
          .category-card h2 {
            font-size: 10px;
          }
          .category-card-count {
            font-size: 8px;
          }
          .category-total {
            font-size: 18px;
            padding-top: 18px;
          }
          .category-share {
            font-size: 7px;
          }
          .new-category-card {
            min-height: 219px;
          }
          .new-category-card strong {
            font-size: 10px;
          }
          .new-category-card small {
            font-size: 8px;
          }
          .analytics-charts .donut-wrap {
            width: 145px;
            height: 145px;
          }
          .comparison-head,
          .comparison-row {
            margin-left: 17px;
            margin-right: 17px;
            font-size: 9px;
          }
          .comparison-head {
            font-size: 8px;
            grid-template-columns: 1fr 0.7fr 1fr;
          }
          .comparison-row {
            grid-template-columns: 1fr 0.7fr 1fr;
          }
          .comparison-row > span:first-child {
            gap: 5px;
          }
          .comparison-row > span:first-child svg {
            width: 14px;
          }
          .modal {
            padding: 21px;
          }
          .modal-heading h2 {
            font-size: 19px;
          }
          .modal-heading p {
            font-size: 9px;
          }
          .form-row {
            gap: 10px;
          }
          .field input,
          .field select {
            font-size: 10px;
            padding-left: 10px;
            padding-right: 10px;
          }
          .select-wrap select {
            padding-right: 26px;
          }
          .select-wrap svg {
            right: 10px;
          }
          .modal-footer .button {
            padding-left: 12px;
            padding-right: 12px;
            font-size: 9px;
            gap: 6px;
          }
          .color-options {
            gap: 6px;
          }
          .color-options button {
            width: 28px;
            height: 28px;
          }
          .icon-options {
            grid-template-columns: repeat(5, 1fr);
            gap: 6px;
          }
          .privacy-box {
            padding: 12px;
          }
          .settings-danger button {
            font-size: 8px;
          }
          .help-item {
            gap: 10px;
          }
          .help-item p {
            font-size: 9px;
            line-height: 19px;
          }
          .help-item h3 {
            font-size: 11px;
          }
          .keyboard-hints {
            gap: 16px;
          }
        }
        @media (prefers-reduced-motion: reduce) {
          *,
          *::before,
          *::after {
            animation-duration: 0.01ms !important;
            animation-delay: 0ms !important;
            transition-duration: 0.01ms !important;
            scroll-behavior: auto !important;
          }
        }
      }

      @layer components {
        .app-shell {
          overflow-x: clip;
        }
        .nav-item {
          font-size: 13px;
        }
        .nav-label {
          font-size: 9px;
        }
        .workspace-picker strong {
          font-size: 12px;
        }
        .workspace-picker small {
          font-size: 10px;
        }
        .workspace-picker {
          gap: 8px;
        }
        .workspace-symbol {
          width: 29px;
        }
        .calm-card p {
          font-size: 11px;
          color: #9586a8;
        }
        .calm-footer {
          font-size: 10px;
        }
        .help-nav {
          font-size: 11px;
        }
        .profile strong {
          font-size: 11px;
        }
        .profile small {
          font-size: 9px;
        }
        .breadcrumb,
        .breadcrumb strong {
          font-size: 11px;
        }
        .global-search input {
          font-size: 11px;
          width: 142px;
        }
        .today {
          font-size: 11px;
        }
        .page-heading p {
          font-size: 12px;
          color: #96919f;
        }
        .button {
          font-size: 12px;
        }
        .period-controls button {
          font-size: 11px;
          padding-top: 6px;
          padding-bottom: 6px;
        }
        .period-controls button.active {
          color: #675a79;
        }
        .date-range {
          font-size: 11px;
        }
        .stat-top {
          font-size: 12px;
          color: #88808f;
        }
        .money {
          font-size: 33px;
        }
        .stat-meta {
          font-size: 10px;
          color: #97909f;
        }
        .balance-status {
          font-size: 10px;
          color: #9480ab;
        }
        .card-heading h2 {
          font-size: 15px;
          color: #37323f;
        }
        .card-heading p {
          font-size: 11px;
          color: #a097a8;
        }
        .chart-tabs button {
          font-size: 10px;
          padding: 6px 10px;
        }
        .chart-tabs button.active {
          color: #8063b2;
        }
        .chart-legend {
          gap: 24px;
          margin-top: 16px;
        }
        .legend-item {
          font-size: 10px;
          color: #968b9f;
        }
        .legend-item b {
          font-size: 11px;
          color: #6d5c7a;
        }
        .chart-interval {
          font-size: 9px;
        }
        .axis-label {
          font-size: 10px;
          fill: #a49aae;
        }
        .chart-footnote {
          font-size: 10px;
          color: #9a8aa6;
          min-height: 42px;
        }
        .chart-footnote strong {
          color: #867294;
        }
        .chart-tooltip strong,
        .chart-tooltip > div {
          font-size: 10px;
        }
        .donut-wrap {
          width: 153px;
          height: 153px;
          margin-top: 6px;
          margin-bottom: 1px;
        }
        .donut-center > span {
          color: #51485e;
          font-size: 23px;
        }
        .donut-center small {
          font-size: 9px;
          color: #aa9bb7;
        }
        .breakdown-legend {
          margin-bottom: 9px;
          gap: 0;
        }
        .breakdown-legend button {
          font-size: 11px;
          padding: 4px 0;
          line-height: 17px;
        }
        .breakdown-legend button > span {
          color: #84748f;
        }
        .breakdown-legend button > b {
          color: #756280;
        }
        .breakdown-legend button > b > span {
          font-size: 10px;
        }
        .breakdown-footer {
          font-size: 9px;
          color: #ad9ebb;
          padding: 11px 0;
        }
        .text-button {
          font-size: 11px;
        }
        .count-badge {
          font-size: 9px;
          min-width: 20px;
          height: 19px;
        }
        .table-scroll {
          position: relative;
        }
        th {
          font-size: 9px;
          color: #a193ae;
        }
        .operation-name strong {
          font-size: 12px;
          color: #64566f;
          font-weight: 600;
        }
        .operation-name small {
          font-size: 10px;
          color: #a697b2;
          margin-top: 3px;
        }
        .category-label {
          font-size: 10px;
          color: #9580a6;
        }
        .date-cell {
          font-size: 11px;
          color: #9a87a8;
        }
        .transaction-amount {
          font-size: 12px;
          color: #766180;
        }
        .transaction-amount.income {
          color: #659d82;
        }
        .category-icon {
          width: 36px;
          height: 36px;
        }
        .table-footer {
          font-size: 9px;
        }
        .row-menu button {
          font-size: 11px;
          color: #8a6fa0;
        }
        .page-footer {
          font-size: 9px;
        }
        .mobile-operation-date {
          display: none;
        }
        .type-filter button {
          font-size: 11px;
        }
        .filter-select select {
          font-size: 11px;
        }
        .category-card h2 {
          font-size: 14px;
          color: #695375;
        }
        .category-total {
          color: #735b84;
        }
        .category-card-count {
          font-size: 11px;
          color: #a793b6;
        }
        .category-share {
          font-size: 10px;
        }
        .categories-top p {
          font-size: 11px;
          color: #a591b2;
        }
        .filtered-summary {
          font-size: 11px;
        }
        .comparison-head {
          font-size: 11px;
          color: #a28aad;
        }
        .comparison-row {
          font-size: 12px;
          color: #9f86ac;
        }
        .comparison-row strong {
          color: #785c8a;
        }
        .insight-card h2 {
          color: #7e5f95;
        }
        .insight-line {
          font-size: 11px;
        }
        .insight-line > span {
          color: #a188b3;
        }
        .insight-line strong {
          font-size: 12px;
          color: #846199;
        }
        .insight-card p {
          font-size: 10px;
          color: #a18aac;
        }
        .breakdown-type button {
          font-size: 10px;
        }
        .modal-heading h2 {
          color: #45384f;
        }
        .modal-heading p {
          font-size: 11px;
          color: #9d89ab;
        }
        .type-switch button {
          font-size: 12px;
          color: #a089b1;
        }
        .amount-input > span {
          font-size: 11px;
          color: #9c81ae;
        }
        .field > span {
          font-size: 12px;
          color: #7c608f;
        }
        .field > span small {
          font-size: 10px;
          color: #ae96bd;
        }
        .field input,
        .field select,
        .field textarea {
          font-size: 12px;
          color: #775985;
        }
        .field input::placeholder,
        .field textarea::placeholder {
          font-size: 11px;
          color: #baa5c7;
        }
        .field-hint,
        .form-privacy {
          font-size: 10px;
          color: #ab92ba;
        }
        .modal-footer .button {
          font-size: 11px;
        }
        .category-preview strong {
          font-size: 14px;
          color: #9371a6;
        }
        .privacy-box strong {
          font-size: 12px;
          color: #8d6aa2;
        }
        .privacy-box p {
          font-size: 11px;
          line-height: 19px;
          color: #a68ab8;
        }
        .settings-section h3 {
          font-size: 12px;
        }
        .data-actions > button > span {
          font-size: 12px;
          color: #9774ab;
        }
        .data-actions small {
          font-size: 10px;
          color: #b499c6;
        }
        .settings-danger button {
          font-size: 10px;
        }
        .confirm-description {
          font-size: 12px;
          color: #967ca6;
        }
        .help-item h3 {
          font-size: 13px;
          color: #88629f;
        }
        .help-item p {
          font-size: 12px;
          color: #a589b9;
        }
        .keyboard-hints > span {
          font-size: 10px;
        }
      }

      @layer responsive {
        @media (max-width: 1200px) and (min-width: 701px) {
          .chart-tabs button {
            font-size: 9px;
            padding: 5px 7px;
          }
          .card-heading h2 {
            font-size: 13px;
          }
          .card-heading p {
            font-size: 10px;
          }
          .legend-item {
            font-size: 9px;
          }
          .legend-item b {
            font-size: 10px;
          }
          .chart-interval {
            display: none;
          }
          .chart-footnote {
            font-size: 9px;
          }
          .breakdown-legend button {
            font-size: 10px;
          }
          .breakdown-legend button > b > span {
            font-size: 9px;
          }
          .breakdown-card .card-heading h2 {
            font-size: 13px;
          }
          .category-label {
            font-size: 9px;
          }
          .date-cell,
          .transaction-amount {
            font-size: 10px;
          }
          .operation-name strong {
            font-size: 11px;
          }
          .operation-name small {
            font-size: 9px;
          }
          .workspace-picker strong {
            font-size: 11px;
          }
        }
        @media (max-width: 1050px) and (min-width: 901px) {
          .card-heading h2 {
            font-size: 12px;
          }
          .card-heading p {
            font-size: 9px;
          }
          .breakdown-card .card-heading h2 {
            font-size: 12px;
          }
          .money {
            font-size: 27px;
          }
          .balance-status {
            font-size: 8px;
          }
          .stat-meta {
            font-size: 9px;
          }
        }
        @media (max-width: 700px) {
          .table-scroll {
            overflow: visible;
          }
          table {
            min-width: 0;
            table-layout: fixed;
          }
          th:nth-child(2),
          th:nth-child(3),
          td:nth-child(2),
          td:nth-child(3) {
            display: none;
          }
          th:first-child,
          td:first-child {
            width: 53%;
            padding-left: 20px;
            padding-right: 7px;
          }
          th:nth-child(4),
          td:nth-child(4) {
            width: 37%;
            padding-left: 4px;
            padding-right: 7px;
          }
          .actions-head,
          td:last-child {
            width: 10%;
            padding-right: 10px;
            padding-left: 0;
          }
          .operation-name {
            gap: 9px;
          }
          .operation-name strong {
            font-size: 12px;
          }
          .operation-name small {
            font-size: 9px;
            line-height: 15px;
            white-space: nowrap;
          }
          .mobile-operation-date {
            display: inline;
          }
          .transaction-amount {
            font-size: 11px;
          }
          th {
            font-size: 9px;
            padding-top: 12px;
            padding-bottom: 12px;
          }
          .category-icon {
            width: 34px;
            height: 34px;
          }
          .row-actions .icon-button {
            width: 25px;
          }
          .table-footer {
            font-size: 9px;
          }
          .saved-status {
            font-size: 8px;
          }
          .card-heading h2 {
            font-size: 14px;
          }
          .card-heading p {
            font-size: 10px;
          }
          .chart-tabs button {
            font-size: 9px;
          }
          .chart-legend {
            gap: 18px;
          }
          .legend-item {
            font-size: 10px;
          }
          .legend-item b {
            font-size: 11px;
          }
          .chart-footnote {
            font-size: 9px;
          }
          .breakdown-legend button {
            font-size: 11px;
          }
          .breakdown-footer {
            font-size: 9px;
          }
          .donut-wrap {
            margin-top: 13px;
            margin-bottom: 13px;
          }
          .stat-meta {
            font-size: 10px;
          }
          .balance-status {
            font-size: 10px;
          }
          .page-heading p {
            font-size: 11px;
          }
          .date-range {
            font-size: 10px;
          }
          .type-filter button {
            font-size: 11px;
          }
          .filter-select select {
            font-size: 11px;
          }
          .category-card h2 {
            font-size: 12px;
          }
          .category-card-count {
            font-size: 10px;
          }
          .categories-top p {
            font-size: 10px;
          }
          .category-share {
            font-size: 9px;
          }
          .modal-heading p {
            font-size: 11px;
          }
          .field input,
          .field select,
          .field textarea {
            font-size: 13px;
          }
          .field > span {
            font-size: 12px;
          }
          .help-item p {
            font-size: 11px;
          }
          .settings-danger button {
            font-size: 9px;
          }
        }
        @media (max-width: 430px) {
          .card-heading h2 {
            font-size: 13px;
          }
          .card-heading p {
            font-size: 9px;
          }
          .chart-tabs button {
            font-size: 8px;
            padding: 6px;
          }
          .flow-chart .card-heading {
            flex-wrap: wrap;
            gap: 12px;
          }
          .legend-item {
            font-size: 9px;
          }
          .legend-item b {
            font-size: 10px;
          }
          .chart-footnote {
            font-size: 8px;
          }
          .breakdown-legend button {
            font-size: 9px;
          }
          .breakdown-legend button > b > span {
            font-size: 9px;
          }
          .donut-center > span {
            font-size: 20px;
          }
          .donut-center small {
            font-size: 8px;
          }
          .stat-meta {
            font-size: 9px;
          }
          .balance-status {
            font-size: 9px;
          }
          .text-button {
            font-size: 9px;
          }
          .table-footer {
            font-size: 8px;
            padding-left: 16px;
            padding-right: 16px;
          }
          .saved-status {
            font-size: 7px;
          }
          th:first-child,
          td:first-child {
            padding-left: 14px;
          }
          .operation-name {
            gap: 7px;
          }
          .operation-name strong {
            font-size: 11px;
          }
          .operation-name small {
            font-size: 8px;
          }
          .category-icon {
            width: 29px;
            height: 31px;
            border-radius: 8px;
          }
          .transaction-amount {
            font-size: 10px;
          }
          .page-heading p {
            font-size: 10px;
          }
          .category-card h2 {
            font-size: 11px;
          }
          .category-card-count {
            font-size: 9px;
          }
          .category-share {
            font-size: 8px;
          }
          .field input,
          .field select {
            font-size: 11px;
          }
          .modal-footer .button {
            font-size: 10px;
          }
        }
      }

      @layer components {
        .storage-warning.preview-note {
          background: #f1ecfa;
          border-color: #e3d8f4;
          color: #9681af;
        }
      }

      /* Дополнения для автономной версии на обычном JavaScript. */
      [hidden] {
        display: none !important;
      }
      .chart-plot {
        min-width: 0;
      }
      .modal-content {
        width: 100%;
      }
      .donut-segment:hover {
        stroke-width: 25;
      }
      .toast-wrap:empty {
        display: none;
      }
      .field-error {
        color: #b55558;
      }

      .nav-item:not(.active) .nav-active-dot {
        visibility: hidden;
      }
      input[type="search"]::-webkit-search-cancel-button {
        display: none;
      }
    </style>
  </head>
  <body>
    <div class="app-shell">
      <div class="sidebar-backdrop" id="sidebar-backdrop" data-action="close-sidebar" hidden></div>

      <!-- Боковое меню. На телефоне открывается кнопкой в верхней панели. -->
      <aside class="sidebar" id="sidebar">
        <div class="sidebar-top">
          <button
            class="brand-button"
            data-action="navigate"
            data-page="overview"
            aria-label="Flow — на главную"
          >
            <span class="brand">
              <span class="brand-icon">
                <svg width="28" height="28" viewBox="0 0 40 40" fill="none" aria-hidden="true">
                  <path
                    d="M10 23c0-7 4.5-12 12-12h9M10 31c0-7 4.5-12 12-12h7"
                    stroke="currentColor"
                    stroke-width="4.6"
                    stroke-linecap="round"
                  ></path>
                </svg>
              </span>
              <span>flow<span class="brand-dot">.</span></span>
            </span>
          </button>
          <button
            class="icon-button mobile-close"
            data-action="close-sidebar"
            aria-label="Закрыть меню"
          >
            <span data-icon="x" data-size="20"></span>
          </button>
        </div>

        <button class="workspace-picker" data-action="settings">
          <span class="workspace-symbol"><span data-icon="wallet" data-size="17"></span></span>
          <span
            ><strong id="workspace-name">Личные финансы</strong
            ><small>Ваше пространство</small></span
          >
          <span data-icon="chevron-down" data-size="13"></span>
        </button>

        <span class="nav-label">ВАШИ ФИНАНСЫ</span>
        <nav class="main-nav" aria-label="Основная навигация">
          <button
            class="nav-item active"
            data-action="navigate"
            data-page="overview"
            aria-current="page"
          >
            <span data-icon="dashboard" data-size="19"></span>
            <span>Обзор</span><span class="nav-active-dot"></span>
          </button>
          <button class="nav-item" data-action="navigate" data-page="transactions">
            <span data-icon="transactions" data-size="19"></span>
            <span>Операции</span><span class="nav-count" id="transaction-count">0</span>
          </button>
          <button class="nav-item" data-action="navigate" data-page="analytics">
            <span data-icon="chart" data-size="19"></span>
            <span>Аналитика</span><span class="nav-active-dot"></span>
          </button>
          <button class="nav-item" data-action="navigate" data-page="categories">
            <span data-icon="shapes" data-size="19"></span>
            <span>Категории</span><span class="nav-active-dot"></span>
          </button>
        </nav>

        <div class="sidebar-bottom">
          <div class="calm-card">
            <svg class="calm-flower" viewBox="0 0 66 66" aria-hidden="true">
              <g fill="none" stroke="#927cdf" stroke-width="1.05">
                <ellipse cx="33" cy="33" rx="12" ry="28"></ellipse>
                <ellipse cx="33" cy="33" rx="12" ry="28" transform="rotate(60 33 33)"></ellipse>
                <ellipse cx="33" cy="33" rx="12" ry="28" transform="rotate(120 33 33)"></ellipse>
                <circle cx="33" cy="33" r="7"></circle>
              </g>
            </svg>
            <span class="calm-spark">✦</span>
            <h3>Больше ясности.<br />Меньше тревоги.</h3>
            <p>Деньги любят порядок.<br />А вы — спокойствие.</p>
            <span class="calm-footer"><span class="tiny-dot"></span>В своём ритме, с flow</span>
          </div>
          <button class="nav-item help-nav" data-action="help">
            <span data-icon="help" data-size="18"></span>
            <span>Помощь и подсказки</span>
            <span data-icon="arrow-up-right" data-size="14"></span>
          </button>
          <div class="profile">
            <span class="avatar">В</span>
            <span
              ><strong>Ваш профиль</strong><small><i></i>Локальное хранение</small></span
            >
            <button class="icon-button tiny" data-action="settings" aria-label="Настройки">
              <span data-icon="sliders" data-size="17"></span>
            </button>
          </div>
        </div>
      </aside>

      <div class="main-shell">
        <header class="topbar">
          <div class="breadcrumb">
            <button
              class="icon-button mobile-menu"
              data-action="open-sidebar"
              aria-label="Открыть меню"
            >
              <span data-icon="menu" data-size="21"></span>
            </button>
            <span>Моё пространство</span>
            <span data-icon="chevron-right" data-size="13"></span>
            <strong id="breadcrumb-page">Обзор</strong>
          </div>
          <div class="topbar-right">
            <label class="global-search" id="global-search-label">
              <span data-icon="search" data-size="16"></span>
              <input
                id="global-search"
                type="search"
                placeholder="Поиск операций"
                aria-label="Поиск операций"
                autocomplete="off"
              />
              <button
                type="button"
                id="clear-search"
                data-action="clear-search"
                aria-label="Очистить поиск"
                hidden
              >
                <span data-icon="x" data-size="14"></span>
              </button>
              <kbd id="search-shortcut">⌘ K</kbd>
            </label>
            <span class="topbar-divider"></span>
            <span class="today"
              ><span data-icon="calendar" data-size="15"></span><span id="today-label"></span
            ></span>
            <button
              class="top-avatar"
              data-action="settings"
              title="Настройки пространства"
              aria-label="Настройки пространства"
            >
              В
            </button>
          </div>
        </header>

        <!-- Содержимое разделов строится функцией renderPage() из обычных HTML-шаблонов. -->
        <main class="main-content" id="main-content"></main>
      </div>
    </div>

    <!-- Окна и уведомления вынесены из основного содержимого для управления фокусом. -->
    <div id="modal-root"></div>
    <div id="toast-root" class="toast-wrap" aria-live="polite"></div>

    <noscript>
      <p style="padding: 24px">
        Для работы Flow включите JavaScript в браузере. Сайт не отправляет ваши данные на сервер.
      </p>
    </noscript>

    <script>
      "use strict";

      /*
       * FLOW — автономный финансовый дашборд.
       * Весь код открыт: обычный HTML, CSS и JavaScript без зависимостей.
       * Для запуска сохраните файл с расширением .html и откройте в браузере.
       * Данные остаются в localStorage; резервные копии — обычные JSON-файлы.
       */

      // ---------------------------------------------------------------------------
      // 1. Значки. Это обычные SVG-фигуры, а не изображения в Base64.
      // ---------------------------------------------------------------------------

      const ICONS = {
        dashboard:
          '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
        transactions: '<path d="M8 3 4 7l4 4M4 7h16M16 13l4 4-4 4M4 17h16"/>',
        chart: '<path d="M3 3v18h18M7 15l4-5 4 2 6-7M7 18v-3M11 18v-4M15 18v-3M19 18v-7"/>',
        shapes:
          '<circle cx="17" cy="17" r="4"/><rect x="3" y="13" width="7" height="7" rx="1"/><path d="m7 3 4 7H3Z"/>',
        search: '<circle cx="10.5" cy="10.5" r="7.5"/><path d="m16 16 5 5"/>',
        "search-x":
          '<circle cx="10.5" cy="10.5" r="7.5"/><path d="m16 16 5 5m-13-15 5 5m0-5-5 5"/>',
        plus: '<path d="M12 5v14M5 12h14"/>',
        x: '<path d="m6 6 12 12M18 6 6 18"/>',
        "arrow-up-right": '<path d="M7 17 17 7M7 7h10v10"/>',
        "arrow-down-left": '<path d="M17 7 7 17M7 7v10h10"/>',
        "arrow-down-right": '<path d="m7 7 10 10M7 17h10V7"/>',
        "arrow-right": '<path d="M4 12h16m-6-6 6 6-6 6"/>',
        "chevron-left": '<path d="m15 18-6-6 6-6"/>',
        "chevron-right": '<path d="m9 18 6-6-6-6"/>',
        "chevron-down": '<path d="m6 9 6 6 6-6"/>',
        download: '<path d="M12 3v12m-5-5 5 5 5-5M4 15v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4"/>',
        upload: '<path d="M12 16V4m-5 5 5-5 5 5M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/>',
        wallet:
          '<path d="M20 8V5a2 2 0 0 0-2-2H6a3 3 0 0 0 0 6h14v11H6a3 3 0 0 1-3-3V6"/><path d="M20 12h-5v5h5M17 14.5h.01"/>',
        sliders: '<path d="M4 7h6m4 0h6M4 17h10m4 0h2M10 4v6m4-6v6M14 14v6m4-6v6"/>',
        more: '<circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/>',
        pencil: '<path d="m15 5 4 4M4 20l5-1L21 7a2.8 2.8 0 0 0-4-4L5 15Z"/>',
        trash: '<path d="M3 6h18M8 6V3h8v3M5 6l1 15h12l1-15M10 10v7M14 10v7"/>',
        check: '<path d="m5 12 4 4L19 6"/>',
        "double-check": '<path d="m2 12 4 4L16 6m-6 10 3 3L23 9"/>',
        "check-circle": '<circle cx="12" cy="12" r="9"/><path d="m8 12 3 3 5-6"/>',
        shield: '<path d="m12 3 8 3v6c0 5-8 9-8 9s-8-4-8-9V6Z"/><path d="m8 12 3 3 5-6"/>',
        help: '<circle cx="12" cy="12" r="9"/><path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 2-3 4M12 17h.01"/>',
        alert: '<circle cx="12" cy="12" r="9"/><path d="M12 7v6M12 17h.01"/>',
        menu: '<path d="M4 6h16M4 12h16M4 18h16"/>',
        calendar:
          '<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M16 3v4M8 3v4M3 11h18M8 15h.01M12 15h.01M16 15h.01M8 18h.01M12 18h.01"/>',
        server:
          '<rect x="3" y="3" width="18" height="7" rx="2"/><rect x="3" y="14" width="18" height="7" rx="2"/><path d="M7 6.5h.01M7 17.5h.01M12 6.5h5M12 17.5h5"/>',
        layers: '<path d="m12 3 10 5-10 5L2 8Zm-10 9 10 5 10-5M2 16l10 5 10-5"/>',
        shopping:
          '<path d="m5 3-2 5v12a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V8l-2-5ZM3 8h18M8 12a4 4 0 0 0 8 0"/>',
        coffee:
          '<path d="M4 8h13v9a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4ZM17 9h2a3 3 0 0 1 0 6h-2M7 2v2M11 2v2M15 2v2"/>',
        car: '<path d="m5 5-3 7v6h20v-6l-3-7ZM2 12h20M5 18v3M19 18v3M6 15h.01M18 15h.01"/>',
        gift: '<rect x="3" y="8" width="18" height="4" rx="1"/><path d="M5 12v9h14v-9M12 8v13"/><path d="M12 8H7a3 3 0 1 1 3-3Zm0 0h5a3 3 0 1 0-3-3Z"/>',
        briefcase:
          '<rect x="3" y="7" width="18" height="14" rx="2"/><path d="M8 7V3h8v4M3 12a20 20 0 0 0 18 0M10 13v3h4v-3"/>',
        other: '<circle cx="12" cy="12" r="9"/><path d="M7 12h.01M12 12h.01M17 12h.01"/>',
        bank: '<path d="m3 7 9-5 9 5ZM4 21h16M6 10v8M10 10v8M14 10v8M18 10v8"/>',
        sparkles:
          '<path d="m12 3 2.5 6.5L21 12l-6.5 2.5L12 21l-2.5-6.5L3 12l6.5-2.5ZM20 2v4M18 4h4"/>',
        lock: '<rect x="5" y="10" width="14" height="11" rx="2"/><path d="M8 10V6a4 4 0 0 1 8 0v4M12 14v3"/>',
        sort: '<path d="M8 3v18m-4-4 4 4 4-4M16 21V3m-4 4 4-4 4 4"/>',
        restore: '<path d="M3 11a9 9 0 1 1 2 7M3 4v7h7"/>',
        database:
          '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 4 18 4 18 0V5M3 12c0 4 18 4 18 0"/>',
      };

      function icon(name, size = 18, className = "") {
        const paths = ICONS[name] || ICONS.other;
        return `<svg class="${className}" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
      }

      // ---------------------------------------------------------------------------
      // 2. Начальные данные и настройки.
      //    Эти четыре операции перенесены из исходной таблицы. Их можно удалить.
      // ---------------------------------------------------------------------------

      const DEFAULT_CATEGORIES = [
        {
          id: "infrastructure",
          name: "Инфраструктура",
          type: "expense",
          color: "#8870dc",
          icon: "server",
        },
        { id: "services", name: "Сервисы и ПО", type: "expense", color: "#b6a2ea", icon: "layers" },
        { id: "shopping", name: "Покупки", type: "expense", color: "#edbea5", icon: "shopping" },
        { id: "food", name: "Еда и напитки", type: "expense", color: "#dfae71", icon: "coffee" },
        { id: "transport", name: "Транспорт", type: "expense", color: "#82a9c1", icon: "car" },
        { id: "other-expense", name: "Другое", type: "expense", color: "#a0a4b5", icon: "other" },
        { id: "salary", name: "Основной доход", type: "income", color: "#8a76df", icon: "wallet" },
        {
          id: "freelance",
          name: "Подработка",
          type: "income",
          color: "#79b69d",
          icon: "briefcase",
        },
        { id: "gifts", name: "Подарки", type: "income", color: "#e2a3b6", icon: "gift" },
        { id: "other-income", name: "Другое", type: "income", color: "#a0a4b5", icon: "other" },
      ];

      const DEMO_TRANSACTIONS = [
        {
          id: "sample-1",
          name: "VPS2",
          amount: 1800,
          date: "2026-09-04",
          type: "expense",
          category: "infrastructure",
          note: "",
          createdAt: 1,
        },
        {
          id: "sample-2",
          name: "Voltx VPS2",
          amount: 1100,
          date: "2026-09-05",
          type: "expense",
          category: "services",
          note: "",
          createdAt: 2,
        },
        {
          id: "sample-3",
          name: "Cream",
          amount: 315,
          date: "2026-09-05",
          type: "expense",
          category: "shopping",
          note: "",
          createdAt: 3,
        },
        {
          id: "sample-4",
          name: "Доход",
          amount: 232,
          date: "2026-09-04",
          type: "income",
          category: "salary",
          note: "",
          createdAt: 4,
        },
      ];

      const DEFAULT_SETTINGS = { name: "Личные финансы", currency: "RUB" };
      const STORAGE_KEYS = {
        transactions: "flow.transactions.v1",
        categories: "flow.categories.v1",
        settings: "flow.settings.v1",
      };
      const CURRENCY_SYMBOLS = { RUB: "₽", EUR: "€", USD: "$" };
      const PALETTE = [
        "#8870dc",
        "#b6a2ea",
        "#edbea5",
        "#79b69d",
        "#82a9c1",
        "#e2a3b6",
        "#dfae71",
        "#a0a4b5",
      ];
      const CATEGORY_ICONS = [
        "server",
        "layers",
        "shopping",
        "coffee",
        "car",
        "gift",
        "briefcase",
        "other",
        "wallet",
        "bank",
      ];
      const PERIODS = [
        { id: "3d", label: "3 дня", days: 3 },
        { id: "7d", label: "7 дней", days: 7 },
        { id: "month", label: "Месяц", days: 30 },
        { id: "all", label: "Всё время", days: null },
      ];
      const PAGE_NAMES = {
        overview: "Обзор",
        transactions: "Операции",
        analytics: "Аналитика",
        categories: "Категории",
      };
      const HEADINGS = {
        overview: ["Обзор финансов", "Всё, что важно. В одном месте."],
        transactions: ["Все операции", "Ваша история доходов и расходов, без лишнего."],
        analytics: ["Аналитика", "Замечайте закономерности. Принимайте лучшие решения."],
        categories: ["Категории", "У каждой операции — своё место."],
      };
      const MONTHS = [
        "янв.",
        "февр.",
        "мар.",
        "апр.",
        "мая",
        "июн.",
        "июл.",
        "авг.",
        "сент.",
        "окт.",
        "нояб.",
        "дек.",
      ];
      const ONE_DAY = 86400000;

      const state = {
        transactions: [],
        categories: [],
        settings: { ...DEFAULT_SETTINGS },
        page: "overview",
        period: "all",
        search: "",
        typeFilter: "all",
        categoryFilter: "all",
        categoryPageType: "expense",
        breakdownType: "expense",
        chartMode: "combined",
        sortField: "date",
        sortDirection: -1,
        tablePage: 1,
        storageError: false,
        mobileOpen: false,
      };

      let chartModel = null;
      let modalState = null;
      let modalReturnFocus = null;
      let toastTimer = null;
      let resizeTimer = null;

      const $ = (selector, root = document) => root.querySelector(selector);
      const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];
      const clone = (value) => JSON.parse(JSON.stringify(value));

      // Пользовательский текст экранируется перед вставкой в HTML.
      function escapeHTML(value) {
        return String(value ?? "").replace(
          /[&<>"']/g,
          (character) =>
            ({
              "&": "&amp;",
              "<": "&lt;",
              ">": "&gt;",
              '"': "&quot;",
              "'": "&#39;",
            })[character],
        );
      }

      function makeId() {
        return (
          globalThis.crypto?.randomUUID?.() ||
          `id-${Date.now()}-${Math.random().toString(36).slice(2)}`
        );
      }

      function toISO(date) {
        return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
      }

      function todayISO() {
        return toISO(new Date());
      }

      function parseDate(value) {
        // Полдень в местном часовом поясе исключает сдвиг даты из-за UTC.
        return new Date(`${value}T12:00:00`);
      }

      function addDays(date, count) {
        const result = new Date(date);
        result.setDate(result.getDate() + count);
        return result;
      }

      function dayDifference(first, last) {
        return Math.round((parseDate(last) - parseDate(first)) / ONE_DAY);
      }

      function isValidDate(value) {
        return (
          typeof value === "string" &&
          /^\d{4}-\d{2}-\d{2}$/.test(value) &&
          !Number.isNaN(parseDate(value).getTime()) &&
          toISO(parseDate(value)) === value
        );
      }

      function shortDate(value) {
        const date = parseDate(value);
        return `${date.getDate()} ${MONTHS[date.getMonth()]}`;
      }

      function longDate(value) {
        return parseDate(value)
          .toLocaleDateString("ru-RU", { day: "numeric", month: "long", year: "numeric" })
          .replace(" г.", "");
      }

      function formatNumber(value, decimals = false) {
        return new Intl.NumberFormat("ru-RU", {
          minimumFractionDigits: decimals ? 2 : 0,
          maximumFractionDigits: 2,
        }).format(value);
      }

      function formatMoney(value, decimals = false) {
        return `${value < 0 ? "−" : ""}${formatNumber(Math.abs(value), decimals)} ${CURRENCY_SYMBOLS[state.settings.currency]}`;
      }

      function moneyMarkup(value) {
        const parts = Math.abs(value).toFixed(2).split(".");
        return `<span class="money" title="${formatMoney(value, true)}">${value < 0 ? "−" : ""}${formatNumber(Number(parts[0]))}<span class="money-decimals">,${parts[1]}</span><span class="money-symbol">${CURRENCY_SYMBOLS[state.settings.currency]}</span></span>`;
      }

      function operationWord(count) {
        const last = count % 10;
        if (count % 100 >= 11 && count % 100 <= 14) return "операций";
        return last === 1 ? "операция" : last >= 2 && last <= 4 ? "операции" : "операций";
      }

      // Суммируем целые копейки/центы, чтобы избежать ошибок вида 0.1 + 0.2.
      function sumAmounts(items) {
        return items.reduce((total, item) => total + Math.round(item.amount * 100), 0) / 100;
      }

      function getTotals(items) {
        const income = sumAmounts(items.filter((item) => item.type === "income"));
        const expense = sumAmounts(items.filter((item) => item.type === "expense"));
        return { income, expense, balance: Math.round((income - expense) * 100) / 100 };
      }

      function findCategory(id, type = "expense") {
        return (
          state.categories.find((category) => category.id === id) ||
          state.categories.find((category) => category.id === `other-${type}`) ||
          DEFAULT_CATEGORIES.find((category) => category.id === `other-${type}`)
        );
      }

      function categoryIcon(category, size = 18) {
        return `<span class="category-icon" style="color:${category.color};background:${category.color}16">${icon(category.icon, size)}</span>`;
      }

      // ---------------------------------------------------------------------------
      // 3. Загрузка и сохранение. Сервер и сетевые запросы не используются.
      // ---------------------------------------------------------------------------

      function validCategory(category) {
        return (
          category &&
          typeof category.id === "string" &&
          /^[a-zA-Z0-9_-]{1,80}$/.test(category.id) &&
          typeof category.name === "string" &&
          category.name.length > 0 &&
          category.name.length <= 80 &&
          ["expense", "income"].includes(category.type) &&
          /^#[0-9a-fA-F]{6}$/.test(category.color) &&
          CATEGORY_ICONS.includes(category.icon)
        );
      }

      function validTransaction(transaction) {
        return (
          transaction &&
          typeof transaction.id === "string" &&
          /^[a-zA-Z0-9_-]{1,80}$/.test(transaction.id) &&
          typeof transaction.name === "string" &&
          transaction.name.length > 0 &&
          transaction.name.length <= 200 &&
          typeof transaction.note === "string" &&
          transaction.note.length <= 2000 &&
          Number.isFinite(transaction.amount) &&
          transaction.amount >= 0.01 &&
          transaction.amount <= 999999999999 &&
          Math.abs(transaction.amount - Math.round(transaction.amount * 100) / 100) < 0.00000001 &&
          ["expense", "income"].includes(transaction.type) &&
          typeof transaction.category === "string" &&
          Number.isFinite(transaction.createdAt) &&
          isValidDate(transaction.date) &&
          transaction.date >= "1900-01-01" &&
          transaction.date <= todayISO()
        );
      }

      function validSettings(settings) {
        return (
          settings &&
          typeof settings.name === "string" &&
          settings.name.length > 0 &&
          Object.hasOwn(CURRENCY_SYMBOLS, settings.currency)
        );
      }

      function readStorage(key, fallback, validate) {
        try {
          const raw = localStorage.getItem(key);
          if (raw === null) return clone(fallback);
          const value = JSON.parse(raw);
          return validate(value) ? value : clone(fallback);
        } catch {
          state.storageError = true;
          return clone(fallback);
        }
      }

      function loadData() {
        state.transactions = readStorage(
          STORAGE_KEYS.transactions,
          DEMO_TRANSACTIONS,
          (items) => Array.isArray(items) && items.every(validTransaction),
        );
        state.categories = readStorage(
          STORAGE_KEYS.categories,
          DEFAULT_CATEGORIES,
          (items) => Array.isArray(items) && items.every(validCategory),
        );
        state.settings = readStorage(STORAGE_KEYS.settings, DEFAULT_SETTINGS, validSettings);
        ensureFallbackCategories();
        saveData();
      }

      function ensureFallbackCategories() {
        for (const category of DEFAULT_CATEGORIES.filter((item) => item.id.startsWith("other-"))) {
          if (!state.categories.some((item) => item.id === category.id))
            state.categories.push({ ...category });
        }
        state.transactions = state.transactions.map((item) => {
          const category = state.categories.find(
            (entry) => entry.id === item.category && entry.type === item.type,
          );
          return category ? item : { ...item, category: `other-${item.type}` };
        });
      }

      function saveData() {
        try {
          localStorage.setItem(STORAGE_KEYS.transactions, JSON.stringify(state.transactions));
          localStorage.setItem(STORAGE_KEYS.categories, JSON.stringify(state.categories));
          localStorage.setItem(STORAGE_KEYS.settings, JSON.stringify(state.settings));
          state.storageError = false;
        } catch {
          state.storageError = true;
        }
      }

      // ---------------------------------------------------------------------------
      // 4. Периоды, фильтры и данные диаграмм.
      // ---------------------------------------------------------------------------

      function getRange() {
        const period = PERIODS.find((item) => item.id === state.period);
        const end = todayISO();
        const start = period.days
          ? toISO(addDays(parseDate(end), -(period.days - 1)))
          : state.transactions.reduce(
              (earliest, item) => (item.date < earliest ? item.date : earliest),
              end,
            );
        return { start, end };
      }

      function rangeLabel(range) {
        const first = parseDate(range.start);
        const last = parseDate(range.end);
        if (range.start === range.end) return longDate(range.end);
        if (first.getFullYear() === last.getFullYear() && first.getMonth() === last.getMonth()) {
          return `${first.getDate()} — ${longDate(range.end)}`;
        }
        const firstYear =
          first.getFullYear() !== last.getFullYear() ? ` ${first.getFullYear()}` : "";
        return `${shortDate(range.start)}${firstYear} — ${shortDate(range.end)} ${last.getFullYear()}`;
      }

      function periodTransactions(range = getRange()) {
        return state.transactions.filter(
          (item) => item.date >= range.start && item.date <= range.end,
        );
      }

      function filteredTransactions() {
        const query = state.search.trim().toLocaleLowerCase("ru-RU");
        return periodTransactions().filter((item) => {
          if (state.typeFilter !== "all" && item.type !== state.typeFilter) return false;
          if (state.categoryFilter !== "all" && item.category !== state.categoryFilter)
            return false;
          const searchText =
            `${item.name} ${item.note} ${item.amount} ${item.date} ${findCategory(item.category, item.type).name}`.toLocaleLowerCase(
              "ru-RU",
            );
          return !query || searchText.includes(query);
        });
      }

      function buildChartData(items, range) {
        const days = dayDifference(range.start, range.end) + 1;
        const interval = days <= 45 ? "day" : days <= 180 ? "week" : "month";
        const buckets = new Map();

        function bucketKey(date) {
          if (interval === "month") return `${date.slice(0, 7)}-01`;
          if (interval === "week")
            return toISO(
              addDays(parseDate(range.start), Math.floor(dayDifference(range.start, date) / 7) * 7),
            );
          return date;
        }

        if (interval === "month") {
          const date = parseDate(range.start);
          date.setDate(1);
          while (toISO(date) <= range.end) {
            const key = toISO(date);
            buckets.set(key, { date: key, income: 0, expense: 0 });
            date.setMonth(date.getMonth() + 1);
          }
        } else {
          for (let index = 0; index < days; index += interval === "week" ? 7 : 1) {
            const key = toISO(addDays(parseDate(range.start), index));
            buckets.set(key, { date: key, income: 0, expense: 0 });
          }
        }

        for (const item of items) {
          const bucket = buckets.get(bucketKey(item.date));
          if (bucket) bucket[item.type] += Math.round(item.amount * 100);
        }
        const points = [...buckets.values()].map((point) => ({
          ...point,
          income: point.income / 100,
          expense: point.expense / 100,
        }));
        return { points, interval };
      }

      // Монотонная кубическая интерполяция: плавные линии без отрицательных выбросов.
      function smoothPath(points) {
        if (points.length === 0) return "";
        if (points.length === 1) return `M${points[0].x},${points[0].y}`;
        const slopes = points
          .slice(1)
          .map((point, index) => (point.y - points[index].y) / (point.x - points[index].x));
        const tangents = points.map((point, index) => {
          if (index === 0) return slopes[0];
          if (index === points.length - 1) return slopes.at(-1);
          if (slopes[index - 1] * slopes[index] <= 0) return 0;
          return (2 * slopes[index - 1] * slopes[index]) / (slopes[index - 1] + slopes[index]);
        });
        return points.reduce((path, point, index) => {
          if (index === 0) return `M${point.x},${point.y}`;
          const previous = points[index - 1];
          const offset = (point.x - previous.x) / 3;
          return `${path} C${previous.x + offset},${previous.y + tangents[index - 1] * offset} ${point.x - offset},${point.y - tangents[index] * offset} ${point.x},${point.y}`;
        }, "");
      }

      function sparkline(values, color) {
        const data = values.length > 1 ? values : [0, ...values];
        const maximum = Math.max(1, ...data);
        const points = data.map((value, index) => ({
          x: 2 + (index / (data.length - 1)) * 84,
          y: 31 - (value / maximum) * 24,
        }));
        return `<svg class="sparkline" width="90" height="38" viewBox="0 0 90 38" aria-hidden="true">
    <path d="${smoothPath(points)}" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round"/>
    <circle cx="${points.at(-1).x}" cy="${points.at(-1).y}" r="3" fill="${color}" stroke="white" stroke-width="2"/>
  </svg>`;
      }

      // ---------------------------------------------------------------------------
      // 5. HTML-шаблоны разделов. Разметка не минифицирована и доступна для правок.
      // ---------------------------------------------------------------------------

      function renderPageHeading() {
        const [title, subtitle] = HEADINGS[state.page];
        const categoryPage = state.page === "categories";
        return `
    <div class="page-heading">
      <div>
        <h1>${title}<span class="heading-dot">.</span></h1>
        <p>${subtitle}</p>
      </div>
      <div class="page-actions">
        <button class="button secondary" data-action="export" title="Скачать операции за выбранный период">
          ${icon("download", 16)}Экспорт
        </button>
        <button class="button primary" data-action="${categoryPage ? "new-category" : "new-transaction"}">
          ${icon("plus", 18)}${categoryPage ? "Новая категория" : "Добавить операцию"}
        </button>
      </div>
    </div>
  `;
      }

      function renderPeriodToolbar(range) {
        return `
    <div class="period-toolbar">
      <div class="period-controls" role="group" aria-label="Период статистики">
        ${PERIODS.map(
          (period) => `
          <button data-action="period" data-period="${period.id}" class="${state.period === period.id ? "active" : ""}"
            aria-pressed="${state.period === period.id}" ${period.id === "month" ? 'title="Последние 30 дней, включая сегодня"' : ""}>
            ${period.label}
          </button>
        `,
        ).join("")}
      </div>
      <div class="date-range">${icon("calendar", 14)}<span>${rangeLabel(range)}</span></div>
    </div>
  `;
      }

      function renderStats(items, chartData) {
        const totals = getTotals(items);
        const incomeCount = items.filter((item) => item.type === "income").length;
        const expenseCount = items.length - incomeCount;
        const balanceStatus =
          totals.balance < 0
            ? "Расходы выше доходов"
            : totals.balance > 0
              ? "Вы в плюсе"
              : "Всё в равновесии";

        return `
    <div class="stats-grid">
      <section class="stat-card balance-card">
        <svg class="balance-art" width="190" height="180" viewBox="0 0 190 180" aria-hidden="true">
          <circle cx="176" cy="89" r="83"/><circle cx="176" cy="89" r="62"/>
          <circle cx="176" cy="89" r="41"/><circle cx="176" cy="89" r="20"/>
        </svg>
        <div class="stat-top">
          <span>Баланс за период <span class="info-tip" title="Доходы минус расходы за выбранный период">${icon("help", 13)}</span></span>
          <span class="stat-icon purple">${icon("wallet", 19)}</span>
        </div>
        ${moneyMarkup(totals.balance)}
        <div class="stat-bottom">
          <span class="balance-status ${totals.balance < 0 ? "negative" : "positive"}">
            ${icon(totals.balance < 0 ? "arrow-down-right" : "arrow-up-right", 13)}${balanceStatus}
          </span>
        </div>
      </section>

      <section class="stat-card">
        <div class="stat-top"><span>Доходы</span><span class="stat-icon green">${icon("arrow-down-left", 20)}</span></div>
        ${moneyMarkup(totals.income)}
        <div class="stat-bottom">
          <span class="stat-meta"><i class="tiny-dot green-dot"></i>${incomeCount} ${operationWord(incomeCount)}</span>
          ${sparkline(
            chartData.points.map((point) => point.income),
            "#8e79dc",
          )}
        </div>
      </section>

      <section class="stat-card">
        <div class="stat-top"><span>Расходы</span><span class="stat-icon peach">${icon("arrow-up-right", 20)}</span></div>
        ${moneyMarkup(totals.expense)}
        <div class="stat-bottom">
          <span class="stat-meta"><i class="tiny-dot peach-dot"></i>${expenseCount} ${operationWord(expenseCount)}</span>
          ${sparkline(
            chartData.points.map((point) => point.expense),
            "#dfa588",
          )}
        </div>
      </section>
    </div>
  `;
      }

      function renderFlowChart(items, range) {
        const totals = getTotals(items);
        const chartData = buildChartData(items, range);
        const expanded = state.page === "analytics";
        const modes = [
          { id: "combined", name: "Общая" },
          { id: "income", name: "Доходы" },
          { id: "expense", name: "Расходы" },
        ];
        const intervalLabel = { day: "По дням", week: "По неделям", month: "По месяцам" }[
          chartData.interval
        ];
        let insight = "Добавьте первую операцию — здесь появится ваша история";
        if (totals.balance < 0)
          insight = `Расходы превышают доходы на <strong>${formatMoney(-totals.balance)}</strong>`;
        if (totals.balance > 0)
          insight = `За этот период вы в плюсе на <strong>${formatMoney(totals.balance)}</strong>`;
        if (totals.balance === 0 && items.length) insight = "Доходы и расходы в равновесии";

        return `
    <section class="card flow-chart ${expanded ? "chart-expanded" : ""}">
      <div class="card-heading">
        <div><h2>Движение средств <span class="title-dot"></span></h2><p>Ваша финансовая картина за период</p></div>
        <div class="chart-tabs" aria-label="Режим диаграммы">
          ${modes
            .map(
              (mode) => `
            <button data-action="chart-mode" data-mode="${mode.id}" class="${state.chartMode === mode.id ? "active" : ""}" aria-pressed="${state.chartMode === mode.id}">${mode.name}</button>
          `,
            )
            .join("")}
        </div>
      </div>
      <div class="chart-legend">
        ${["income", "expense"]
          .filter((type) => state.chartMode === "combined" || type === state.chartMode)
          .map(
            (type) => `
          <span class="legend-item"><i style="background:${type === "income" ? "#8a73de" : "#e2a58a"}"></i>
            ${type === "income" ? "Доходы" : "Расходы"}<b>${formatMoney(totals[type])}</b>
          </span>
        `,
          )
          .join("")}
        <span class="chart-interval">${intervalLabel}${icon("chart", 13)}</span>
      </div>
      <div class="chart-plot" id="chart-plot" tabindex="0" aria-label="Интерактивная диаграмма. Стрелки влево и вправо показывают суммы за день."></div>
      <div class="chart-footnote"><span class="footnote-icon">${icon("sparkles", 13)}</span>${insight}</div>
    </section>
  `;
      }

      function renderBreakdown(items) {
        const canSwitch = state.page === "analytics";
        const type = canSwitch ? state.breakdownType : "expense";
        const groups = state.categories
          .filter((category) => category.type === type)
          .map((category) => ({
            ...category,
            amount: sumAmounts(
              items.filter((item) => item.category === category.id && item.type === type),
            ),
          }))
          .filter((category) => category.amount > 0)
          .sort((first, second) => second.amount - first.amount);
        const total = sumAmounts(groups);
        const shown =
          groups.length > 4
            ? [
                ...groups.slice(0, 3),
                {
                  id: "all",
                  name: "Остальные",
                  color: "#a8b7bf",
                  amount: sumAmounts(groups.slice(3)),
                },
              ]
            : groups;
        const circumference = 2 * Math.PI * 64;
        let accumulated = 0;

        const segments = shown
          .map((category) => {
            const fraction = category.amount / total;
            const length = Math.max(1, fraction * circumference - 5);
            const offset = -accumulated;
            accumulated += fraction * circumference;
            return `
      <circle class="donut-segment" cx="85" cy="85" r="64" fill="none" stroke="${category.color}" stroke-width="21"
        stroke-dasharray="${length} ${circumference - length}" stroke-dashoffset="${offset}" transform="rotate(-90 85 85)"
        data-action="filter-category" data-category="${escapeHTML(category.id)}" data-type="${type}">
        <title>${escapeHTML(category.name)}: ${formatMoney(category.amount)} (${Math.round(fraction * 100)}%)</title>
      </circle>
    `;
          })
          .join("");

        const categoryWord =
          groups.length === 1
            ? "категория"
            : groups.length >= 2 && groups.length <= 4
              ? "категории"
              : "категорий";
        return `
    <section class="card breakdown-card">
      <div class="card-heading">
        <div><h2>Структура ${type === "expense" ? "расходов" : "доходов"}</h2><p>${type === "expense" ? "Всё по своим категориям" : "Откуда приходят деньги"}</p></div>
        <button class="icon-button tiny" data-action="navigate" data-page="categories" aria-label="Перейти к категориям">${icon("arrow-up-right", 18)}</button>
      </div>
      ${
        canSwitch
          ? `
        <div class="breakdown-type">
          <button data-action="breakdown-type" data-type="expense" class="${type === "expense" ? "active" : ""}">Расходы</button>
          <button data-action="breakdown-type" data-type="income" class="${type === "income" ? "active" : ""}">Доходы</button>
        </div>
      `
          : ""
      }
      <div class="donut-wrap">
        <svg class="donut" viewBox="0 0 170 170" role="img" aria-label="Распределение по категориям">
          <circle cx="85" cy="85" r="64" fill="none" stroke="#f2eff8" stroke-width="21"/>
          ${segments}
        </svg>
        <div class="donut-center"><span>${formatMoney(total)}</span><small>всего ${type === "expense" ? "расходов" : "доходов"}</small></div>
      </div>
      <div class="breakdown-legend">
        ${
          shown.length
            ? shown
                .map(
                  (category) => `
          <button data-action="filter-category" data-category="${escapeHTML(category.id)}" data-type="${type}"
            title="${escapeHTML(category.name)}: ${formatMoney(category.amount)}">
            <span><i style="background:${category.color}"></i>${escapeHTML(category.name)}</span>
            <b>${Math.round((category.amount / total) * 100)}<span>%</span></b>
          </button>
        `,
                )
                .join("")
            : '<p class="empty-donut-text">Здесь будут ваши категории,<br>когда появятся операции</p>'
        }
      </div>
      <div class="breakdown-footer"><span>${groups.length} ${categoryWord} за период</span>${icon("shapes", 13)}</div>
    </section>
  `;
      }

      function renderTable(items, overview = false) {
        const sorted = [...items].sort((first, second) => {
          if (state.sortField === "amount")
            return (first.amount - second.amount) * state.sortDirection;
          return (
            first[state.sortField].localeCompare(second[state.sortField], "ru") *
              state.sortDirection || second.createdAt - first.createdAt
          );
        });
        const pageSize = overview ? 4 : 10;
        const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
        const currentPage = overview ? 1 : Math.min(state.tablePage, totalPages);
        const start = (currentPage - 1) * pageSize;
        const rows = sorted.slice(start, start + pageSize);
        const shownLabel = overview
          ? rows.length
          : `${start + 1}–${Math.min(start + pageSize, sorted.length)}`;

        return `
    <section class="card transactions-card">
      <div class="card-heading">
        <div>
          <h2>${overview ? "Последние операции" : "История операций"}<span class="count-badge">${items.length}</span></h2>
          <p>${overview ? "Маленькие детали. Полная картина." : `${items.length} ${operationWord(items.length)} по вашим условиям`}</p>
        </div>
        ${overview ? `<button class="text-button" data-action="navigate" data-page="transactions">Все операции ${icon("arrow-right", 15)}</button>` : ""}
      </div>
      ${
        rows.length
          ? `
        <div class="table-scroll">
          <table>
            <thead><tr>
              <th><button data-action="sort" data-field="name">Операция ${icon("sort", 11)}</button></th>
              <th>Категория</th>
              <th><button data-action="sort" data-field="date">Дата ${icon("sort", 11)}</button></th>
              <th class="amount-cell"><button data-action="sort" data-field="amount">Сумма ${icon("sort", 11)}</button></th>
              <th class="actions-head"><span class="sr-only">Действия</span></th>
            </tr></thead>
            <tbody>
              ${rows
                .map((item) => {
                  const category = findCategory(item.category, item.type);
                  return `
                  <tr>
                    <td>
                      <button class="operation-name" data-action="edit-transaction" data-id="${escapeHTML(item.id)}">
                        ${categoryIcon(category)}
                        <span><strong>${escapeHTML(item.name)}</strong><small>${item.type === "income" ? "Поступление" : "Списание"}<span class="mobile-operation-date"> · ${shortDate(item.date)}</span></small></span>
                      </button>
                    </td>
                    <td><span class="category-label">${escapeHTML(category.name)}</span></td>
                    <td class="date-cell">${shortDate(item.date)} <span>${parseDate(item.date).getFullYear()}</span></td>
                    <td class="amount-cell transaction-amount ${item.type}">${item.type === "income" ? "+" : "−"}${formatMoney(item.amount, true)}</td>
                    <td><div class="row-actions">
                      <button class="icon-button tiny" data-action="row-menu" data-id="${escapeHTML(item.id)}" aria-label="Действия: ${escapeHTML(item.name)}" aria-expanded="false">${icon("more", 19)}</button>
                    </div></td>
                  </tr>
                `;
                })
                .join("")}
            </tbody>
          </table>
        </div>
        <div class="table-footer">
          <span>Показано ${shownLabel} из ${sorted.length}</span>
          ${
            !overview && totalPages > 1
              ? `
            <div class="pagination">
              <button class="icon-button tiny" data-action="table-page" data-value="${currentPage - 1}" aria-label="Предыдущая страница" ${currentPage === 1 ? "disabled" : ""}>${icon("chevron-left", 16)}</button>
              <span>${currentPage} / ${totalPages}</span>
              <button class="icon-button tiny" data-action="table-page" data-value="${currentPage + 1}" aria-label="Следующая страница" ${currentPage === totalPages ? "disabled" : ""}>${icon("chevron-right", 16)}</button>
            </div>
          `
              : `
            <span class="saved-status ${state.storageError ? "danger-text" : ""}">
              ${icon(state.storageError ? "alert" : "double-check", 14)}${state.storageError ? "Сохранение недоступно" : "Сохранено на устройстве"}
            </span>
          `
          }
        </div>
      `
          : `
        <div class="empty-state">
          <span class="empty-icon">${icon("search-x", 28)}</span>
          <h3>Пока ничего нет</h3><p>Добавьте операцию или выберите другой период.<br>Ваша финансовая история начинается здесь.</p>
          <div>
            <button class="button secondary small" data-action="reset-filters">${icon("restore", 14)}Сбросить фильтры</button>
            <button class="button primary small" data-action="new-transaction">${icon("plus", 15)}Добавить операцию</button>
          </div>
        </div>
      `
      }
    </section>
  `;
      }

      function renderTransactionFilters() {
        const types = [
          { id: "all", name: "Все операции" },
          { id: "income", name: "Доходы", icon: "arrow-down-left" },
          { id: "expense", name: "Расходы", icon: "arrow-up-right" },
        ];
        const items = filteredTransactions();
        const totals = getTotals(items);
        return `
    <div class="transaction-filters">
      <div class="type-filter">
        ${types.map((type) => `<button data-action="type-filter" data-type="${type.id}" class="${state.typeFilter === type.id ? "active" : ""}">${type.icon ? icon(type.icon, 15) : ""}${type.name}</button>`).join("")}
      </div>
      <div class="filter-select">
        ${icon("shapes", 15)}
        <select id="filter-category" aria-label="Фильтр по категории">
          <option value="all">Все категории</option>
          ${state.categories
            .filter((category) => state.typeFilter === "all" || category.type === state.typeFilter)
            .map(
              (category) => `
            <option value="${escapeHTML(category.id)}" ${state.categoryFilter === category.id ? "selected" : ""}>
              ${escapeHTML(category.name)}${state.typeFilter === "all" && category.name === "Другое" ? ` (${category.type === "income" ? "доходы" : "расходы"})` : ""}
            </option>
          `,
            )
            .join("")}
        </select>${icon("chevron-down", 13)}
      </div>
    </div>
    ${
      state.search || state.typeFilter !== "all" || state.categoryFilter !== "all"
        ? `
      <div class="active-filters">
        <span>${state.search ? `Поиск: «${escapeHTML(state.search)}» · ` : ""}Найдено: ${items.length} ${operationWord(items.length)}</span>
        <button data-action="reset-filters">${icon("x", 13)}Сбросить фильтры</button>
      </div>
    `
        : ""
    }
    ${renderTable(items)}
    <div class="filtered-summary">
      <span>Итого по выбранным операциям</span>
      <div>
        <span>Доходы <b class="income-text">+${formatMoney(totals.income)}</b></span>
        <span>Расходы <b>${formatMoney(totals.expense)}</b></span>
        <span>Баланс <b>${formatMoney(totals.balance)}</b></span>
      </div>
    </div>
  `;
      }

      function renderInsights(items, range) {
        const current = getTotals(items);
        const days = dayDifference(range.start, range.end) + 1;
        const previousEnd = toISO(addDays(parseDate(range.start), -1));
        const previousStart = toISO(addDays(parseDate(range.start), -days));
        const previous = getTotals(
          state.transactions.filter(
            (item) => item.date >= previousStart && item.date <= previousEnd,
          ),
        );
        const largest = items
          .filter((item) => item.type === "expense")
          .sort((first, second) => second.amount - first.amount)[0];
        const allTime = state.period === "all";

        return `
    <div class="insights-grid">
      <section class="card comparison-card">
        <div class="card-heading"><div><h2>Период в цифрах</h2><p>${allTime ? "Главные показатели за всё время" : "Сравнение с предыдущим таким же периодом"}</p></div>${icon("chart", 20, "muted")}</div>
        <div class="comparison-head"><span>Показатель</span><span>Сейчас</span><span>${allTime ? "В среднем за день" : "Ранее"}</span></div>
        ${[
          { key: "income", name: "Доходы", icon: "arrow-down-left" },
          { key: "expense", name: "Расходы", icon: "arrow-up-right" },
          { key: "balance", name: "Баланс", icon: "wallet" },
        ]
          .map(
            (row) => `
          <div class="comparison-row"><span>${icon(row.icon, 16)}${row.name}</span><strong>${formatMoney(current[row.key])}</strong><span>${formatMoney(allTime ? current[row.key] / days : previous[row.key])}</span></div>
        `,
          )
          .join("")}
      </section>
      <section class="card insight-card">
        <div class="insight-symbol">${icon("sparkles", 21)}</div>
        <h2>Чуть больше ясности</h2>
        <div class="insight-line"><span>Средний расход в день</span><strong>${formatMoney(current.expense / days)}</strong></div>
        <div class="insight-line"><span>Самый большой расход</span><strong>${largest ? `${formatMoney(largest.amount)}<small>${escapeHTML(largest.name)}</small>` : "—"}</strong></div>
        <p><span class="tiny-dot green-dot"></span>${items.length} ${operationWord(items.length)} формируют вашу картину за период.</p>
      </section>
    </div>
  `;
      }

      function renderCategories(items) {
        const type = state.categoryPageType;
        const total = getTotals(items)[type];
        return `
    <div class="categories-top">
      <div class="type-filter">
        <button data-action="category-type" data-type="expense" class="${type === "expense" ? "active" : ""}">${icon("arrow-up-right", 16)}Расходы</button>
        <button data-action="category-type" data-type="income" class="${type === "income" ? "active" : ""}">${icon("arrow-down-left", 16)}Доходы</button>
      </div>
      <p>Нажмите на сумму, чтобы посмотреть операции</p>
    </div>
    <div class="categories-grid">
      ${state.categories
        .filter((category) => category.type === type)
        .map((category) => {
          const entries = items.filter((item) => item.category === category.id);
          const amount = sumAmounts(entries);
          const percentage = total ? Math.round((amount / total) * 100) : 0;
          return `
          <section class="card category-card">
            <div class="category-card-top">
              ${categoryIcon(category, 23)}
              <button class="icon-button tiny" data-action="edit-category" data-id="${escapeHTML(category.id)}" aria-label="Настроить категорию ${escapeHTML(category.name)}">${icon("pencil", 15)}</button>
            </div>
            <h2>${escapeHTML(category.name)}</h2>
            <span class="category-card-count">${entries.length} ${operationWord(entries.length)} за период</span>
            <button class="category-total" data-action="filter-category" data-category="${escapeHTML(category.id)}" data-type="${type}">${formatMoney(amount)}${icon("arrow-up-right", 17)}</button>
            <div class="category-progress"><span style="width:${percentage}%;background:${category.color}"></span></div>
            <div class="category-share"><span>Доля ${type === "expense" ? "расходов" : "доходов"}</span><b>${percentage}%</b></div>
          </section>
        `;
        })
        .join("")}
      <button class="new-category-card" data-action="new-category"><span>${icon("plus", 23)}</span><strong>Новая категория</strong><small>Немного больше порядка</small></button>
    </div>
  `;
      }

      function renderPage(animate = false) {
        const range = getRange();
        const items = periodTransactions(range);
        const hasCharts = state.page === "overview" || state.page === "analytics";
        let content = "";

        if (hasCharts) {
          const chartData = buildChartData(items, range);
          content += renderStats(items, chartData);
          content += `<div class="charts-grid ${state.page === "analytics" ? "analytics-charts" : ""}">${renderFlowChart(items, range)}${renderBreakdown(items)}</div>`;
        }
        if (state.page === "overview") content += renderTable(items, true);
        if (state.page === "transactions") content += renderTransactionFilters();
        if (state.page === "analytics") content += renderInsights(items, range);
        if (state.page === "categories") content += renderCategories(items);

        const preview = globalThis.origin === "null";
        const storageWarning = state.storageError
          ? `
    <div class="storage-warning ${preview ? "preview-note" : ""}">
      ${icon("alert", 17)}
      ${preview ? "В этом предпросмотре сохранение недоступно. Скачайте Flow.html и откройте его в браузере." : "Браузер не разрешил сохранить данные. Сделайте резервную копию перед закрытием страницы."}
    </div>
  `
          : "";

        $("#main-content").innerHTML = `
    ${storageWarning}
    ${renderPageHeading()}
    ${renderPeriodToolbar(range)}
    ${content}
    <footer class="page-footer">
      <span>Меньше шума. Больше смысла.</span>
      <span>${icon("lock", 12)}Данные остаются на устройстве <i></i>flow © ${new Date().getFullYear()}</span>
    </footer>
  `;

        $("#workspace-name").textContent = state.settings.name;
        $("#transaction-count").textContent = state.transactions.length;
        $("#breadcrumb-page").textContent = PAGE_NAMES[state.page];
        $("#today-label").textContent = new Date().toLocaleDateString("ru-RU", {
          day: "numeric",
          month: "long",
        });
        document.title = `Flow — ${PAGE_NAMES[state.page]}`;

        for (const button of $$(".main-nav [data-page]")) {
          const active = button.dataset.page === state.page;
          button.classList.toggle("active", active);
          if (active) button.setAttribute("aria-current", "page");
          else button.removeAttribute("aria-current");
        }
        syncSearchField();
        if (hasCharts) paintChart();
        else chartModel = null;

        if (animate && !matchMedia("(prefers-reduced-motion: reduce)").matches) {
          $("#main-content").animate(
            [
              { opacity: 0.4, transform: "translateY(7px)" },
              { opacity: 1, transform: "translateY(0)" },
            ],
            { duration: 260, easing: "ease-out" },
          );
        }
      }

      function syncSearchField() {
        const input = $("#global-search");
        if (input.value !== state.search) input.value = state.search;
        $("#global-search-label").classList.toggle("has-value", Boolean(state.search));
        $("#clear-search").hidden = !state.search;
        $("#search-shortcut").hidden = Boolean(state.search);
      }

      // ---------------------------------------------------------------------------
      // 6. Отрисовка SVG-диаграммы и интерактивных подсказок.
      // ---------------------------------------------------------------------------

      function paintChart() {
        const plot = $("#chart-plot");
        if (!plot) return;
        const range = getRange();
        const data = buildChartData(periodTransactions(range), range);
        const points =
          data.points.length === 1 ? [data.points[0], { ...data.points[0] }] : data.points;
        const keys = state.chartMode === "combined" ? ["expense", "income"] : [state.chartMode];
        const colors = { income: "#8a73de", expense: "#e2a58a" };
        const width = Math.max(270, plot.clientWidth);
        const height = state.page === "analytics" ? 262 : 186;
        const left = width < 400 ? 40 : 48;
        const right = 16;
        const top = 17;
        const bottom = height - 30;
        const largest = Math.max(0, ...points.flatMap((point) => keys.map((key) => point[key])));
        const rawStep = (largest || 100) / 4;
        const magnitude = 10 ** Math.floor(Math.log10(rawStep));
        const factor = [1, 2, 2.5, 5, 10].find((value) => value * magnitude >= rawStep) || 10;
        const step = Math.max(0.01, factor * magnitude);
        const maximum = Math.max(step, Math.ceil((largest || 100) / step) * step);
        const xAt = (index) =>
          left + (index / Math.max(1, points.length - 1)) * (width - left - right);
        const yAt = (value) => bottom - (value / maximum) * (bottom - top);
        const labelEvery = Math.max(1, Math.ceil((points.length - 1) / (width < 450 ? 4 : 6)));

        function axisLabel(value) {
          if (value >= 1000000) return `${formatNumber(value / 1000000)} млн`;
          if (value >= 10000) return `${formatNumber(value / 1000)} тыс.`;
          return formatNumber(value);
        }

        const grid = Array.from({ length: Math.round(maximum / step) + 1 }, (_, index) => {
          const value = index * step;
          const y = yAt(value);
          return `
      <g><line x1="${left}" x2="${width - right}" y1="${y}" y2="${y}" stroke="#ececf2" stroke-dasharray="${index ? "3 5" : "none"}"/>
      <text x="${left - 12}" y="${y + 4}" text-anchor="end" class="axis-label">${axisLabel(value)}</text></g>
    `;
        }).join("");

        const lines = keys
          .map((key) => {
            const coordinates = points.map((point, index) => ({
              x: xAt(index),
              y: yAt(point[key]),
            }));
            const path = smoothPath(coordinates);
            return `
      <g>
        <path class="chart-area" d="${path} L${xAt(points.length - 1)},${bottom} L${left},${bottom} Z" fill="url(#gradient-${key})"/>
        <path class="chart-line" pathLength="1" d="${path}" fill="none" stroke="${colors[key]}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
        <circle cx="${xAt(points.length - 1)}" cy="${yAt(points.at(-1)[key])}" r="3.8" fill="${colors[key]}" stroke="white" stroke-width="2"/>
      </g>
    `;
          })
          .join("");

        const labels = points
          .map((point, index) => {
            if (data.points.length === 1 && index === 0) return "";
            if (index % labelEvery && index !== points.length - 1) return "";
            if (
              index > 0 &&
              index < points.length - 1 &&
              index > points.length - 1 - labelEvery * 0.7
            )
              return "";
            const label =
              data.interval === "month"
                ? `${MONTHS[parseDate(point.date).getMonth()]} ${parseDate(point.date).getFullYear()}`
                : shortDate(point.date);
            const anchor = index === 0 ? "start" : index === points.length - 1 ? "end" : "middle";
            return `<text x="${xAt(index)}" y="${height - 7}" text-anchor="${anchor}" class="axis-label">${label}</text>`;
          })
          .join("");

        plot.innerHTML = `
    <svg width="100%" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="Движение средств за период">
      <defs>
        ${["income", "expense"]
          .map(
            (key) => `
          <linearGradient id="gradient-${key}" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stop-color="${colors[key]}" stop-opacity="0.17"/>
            <stop offset="100%" stop-color="${colors[key]}" stop-opacity="0.012"/>
          </linearGradient>
        `,
          )
          .join("")}
      </defs>
      ${grid}<g class="chart-lines">${lines}</g>${labels}
      <g id="chart-cursor"></g>
    </svg>
    <div class="chart-tooltip" id="chart-tooltip" hidden></div>
  `;

        chartModel = {
          points,
          keys,
          colors,
          interval: data.interval,
          width,
          height,
          left,
          right,
          top,
          bottom,
          xAt,
          yAt,
          activeIndex: null,
        };
        plot.onpointermove = (event) => {
          const x = event.clientX - plot.getBoundingClientRect().left;
          const index = Math.round(((x - left) / (width - left - right)) * (points.length - 1));
          showChartTooltip(Math.max(0, Math.min(points.length - 1, index)));
        };
        plot.onpointerleave = hideChartTooltip;
        plot.onblur = hideChartTooltip;
        plot.onkeydown = (event) => {
          if (!["ArrowLeft", "ArrowRight"].includes(event.key)) return;
          event.preventDefault();
          const direction = event.key === "ArrowRight" ? 1 : -1;
          const index = Math.max(
            0,
            Math.min(points.length - 1, (chartModel.activeIndex ?? -direction) + direction),
          );
          showChartTooltip(index);
        };
      }

      function showChartTooltip(index) {
        if (!chartModel || !$("#chart-tooltip")) return;
        const model = chartModel;
        const point = model.points[index];
        model.activeIndex = index;
        const x = model.xAt(index);
        const tooltip = $("#chart-tooltip");
        const periodName =
          model.interval === "week" ? " · неделя" : model.interval === "month" ? " · месяц" : "";
        tooltip.innerHTML = `
    <strong>${longDate(point.date)}${periodName}</strong>
    ${[...model.keys]
      .reverse()
      .map(
        (key) => `
      <div><span><i style="background:${model.colors[key]}"></i>${key === "income" ? "Доходы" : "Расходы"}</span><b>${formatMoney(point[key])}</b></div>
    `,
      )
      .join("")}
  `;
        tooltip.style.left = `${Math.max(5, Math.min(model.width - 210, x - 100))}px`;
        tooltip.style.top = "0px";
        tooltip.hidden = false;
        $("#chart-cursor").innerHTML = `
    <line x1="${x}" x2="${x}" y1="${model.top}" y2="${model.bottom}" stroke="#a5a0bd" stroke-dasharray="3 4"/>
    ${model.keys
      .map(
        (key) => `
      <circle cx="${x}" cy="${model.yAt(point[key])}" r="8" fill="${model.colors[key]}" fill-opacity="0.14"/>
      <circle cx="${x}" cy="${model.yAt(point[key])}" r="4" fill="${model.colors[key]}" stroke="white" stroke-width="2"/>
    `,
      )
      .join("")}
  `;
      }

      function hideChartTooltip() {
        if ($("#chart-tooltip")) $("#chart-tooltip").hidden = true;
        if ($("#chart-cursor")) $("#chart-cursor").innerHTML = "";
        if (chartModel) chartModel.activeIndex = null;
      }

      // ---------------------------------------------------------------------------
      // 7. Модальные окна. Один общий контейнер, управление фокусом и клавишей Esc.
      // ---------------------------------------------------------------------------

      function showModal(title, subtitle, content, options = {}) {
        if (!modalState) modalReturnFocus = document.activeElement;
        modalState = options;
        $("#modal-root").innerHTML = `
    <div class="modal-overlay">
      <section class="modal ${options.className || ""}" role="dialog" aria-modal="true" aria-labelledby="modal-title">
        <div class="modal-heading">
          <div><h2 id="modal-title">${escapeHTML(title)}</h2>${subtitle ? `<p>${escapeHTML(subtitle)}</p>` : ""}</div>
          <button class="icon-button modal-close" type="button" data-action="close-modal" aria-label="Закрыть">${icon("x", 20)}</button>
        </div>
        ${content}
      </section>
    </div>
  `;
        document.body.style.overflow = "hidden";
        requestAnimationFrame(() => {
          const focusTarget =
            $("[autofocus]", $("#modal-root")) ||
            $("input:not([type=hidden]), select:not([disabled]), textarea", $("#modal-root")) ||
            $("button", $("#modal-root"));
          focusTarget?.focus();
        });
      }

      function closeModal() {
        if (!modalState) return;
        $("#modal-root").innerHTML = "";
        modalState = null;
        document.body.style.overflow = state.mobileOpen ? "hidden" : "";
        const focusTarget = modalReturnFocus;
        requestAnimationFrame(() => {
          if (focusTarget?.isConnected) focusTarget.focus();
        });
      }

      function showFormError(message) {
        const error = $("#form-error");
        if (!error) return;
        error.innerHTML = `${icon("alert", 16)}<span>${escapeHTML(message)}</span>`;
        error.hidden = false;
      }

      function categoryOptions(type, selectedId) {
        return state.categories
          .filter((category) => category.type === type)
          .map(
            (category) =>
              `<option value="${escapeHTML(category.id)}" ${selectedId === category.id ? "selected" : ""}>${escapeHTML(category.name)}</option>`,
          )
          .join("");
      }

      function openTransaction(id = null) {
        const existing = id ? state.transactions.find((item) => item.id === id) : null;
        const type = existing?.type || "expense";
        const categoryId =
          existing?.category || state.categories.find((category) => category.type === type)?.id;

        showModal(
          existing ? "Редактировать операцию" : "Новая операция",
          existing
            ? "Все изменения сразу отразятся в статистике."
            : "Ещё один шаг к ясности в финансах.",
          `
    <form id="transaction-form" class="transaction-form">
      <input type="hidden" name="type" value="${type}">
      <div class="type-switch">
        <button type="button" data-action="transaction-type" data-type="expense" class="${type === "expense" ? "active expense" : ""}">${icon("arrow-up-right", 17)}Расход</button>
        <button type="button" data-action="transaction-type" data-type="income" class="${type === "income" ? "active income" : ""}">${icon("arrow-down-left", 17)}Доход</button>
      </div>
      <label class="amount-input" for="transaction-amount">
        <span>Сумма операции</span>
        <div><input id="transaction-amount" name="amount" inputmode="decimal" placeholder="0,00" aria-label="Сумма операции"
          value="${existing ? String(existing.amount).replace(".", ",") : ""}" maxlength="18" autocomplete="off" autofocus required>
          <span>${CURRENCY_SYMBOLS[state.settings.currency]}</span>
        </div>
      </label>
      <label class="field" for="transaction-name">
        <span>Название <i>*</i></span>
        <input id="transaction-name" name="name" value="${escapeHTML(existing?.name || "")}" placeholder="${type === "expense" ? "Например, оплата сервера" : "Например, оплата за проект"}" maxlength="80" required>
      </label>
      <div class="form-row">
        <label class="field" for="transaction-category"><span>Категория</span>
          <div class="select-wrap"><select id="transaction-category" name="category">${categoryOptions(type, categoryId)}</select>${icon("chevron-down", 14)}</div>
        </label>
        <label class="field" for="transaction-date"><span>Дата</span>
          <input id="transaction-date" name="date" type="date" value="${existing?.date || todayISO()}" min="1900-01-01" max="${todayISO()}" required>
        </label>
      </div>
      <label class="field" for="transaction-note"><span>Заметка <small>необязательно</small></span>
        <textarea id="transaction-note" name="note" placeholder="То, что стоит запомнить…" maxlength="400" rows="2">${escapeHTML(existing?.note || "")}</textarea>
      </label>
      <div class="form-error" id="form-error" role="alert" hidden></div>
      <div class="modal-footer">
        <button class="button secondary" type="button" data-action="close-modal">Отмена</button>
        <button class="button primary" type="submit">${icon("check", 17)}${existing ? "Сохранить изменения" : "Добавить операцию"}</button>
      </div>
      <p class="form-privacy">${icon("lock", 12)}Только на вашем устройстве. Только для вас.</p>
    </form>
  `,
          { kind: "transaction", editingId: existing?.id || null },
        );
      }

      function changeTransactionType(type) {
        const form = $("#transaction-form");
        if (!form || !["income", "expense"].includes(type)) return;
        form.elements.type.value = type;
        for (const button of $$(".type-switch button", form)) {
          const active = button.dataset.type === type;
          button.className = active ? `active ${type}` : "";
        }
        $("#transaction-category").innerHTML = categoryOptions(type);
        $("#transaction-name").placeholder =
          type === "expense" ? "Например, оплата сервера" : "Например, оплата за проект";
        $("#form-error").hidden = true;
      }

      function openCategory(id = null) {
        const category = id ? state.categories.find((item) => item.id === id) : null;
        const type = category?.type || state.categoryPageType;
        const color = category?.color || PALETTE[0];
        const selectedIcon = category?.icon || "other";
        const canDelete = category && !category.id.startsWith("other-");

        showModal(
          category ? "Настроить категорию" : "Новая категория",
          "Организуйте финансы так, как удобно вам.",
          `
    <form id="category-form" class="category-form">
      <input type="hidden" name="color" value="${color}">
      <input type="hidden" name="icon" value="${selectedIcon}">
      <div class="category-preview">
        <span id="category-preview-icon">${categoryIcon({ color, icon: selectedIcon }, 27)}</span>
        <strong id="category-preview-name">${escapeHTML(category?.name || "Ваша категория")}</strong>
      </div>
      <label class="field" for="category-name"><span>Название</span>
        <input id="category-name" name="name" value="${escapeHTML(category?.name || "")}" placeholder="Например, путешествия" maxlength="35" required autofocus>
      </label>
      <label class="field" for="category-type"><span>Тип категории</span>
        <div class="select-wrap"><select id="category-type" name="type" ${category ? "disabled" : ""}>
          <option value="expense" ${type === "expense" ? "selected" : ""}>Расходы</option>
          <option value="income" ${type === "income" ? "selected" : ""}>Доходы</option>
        </select>${icon("chevron-down", 14)}</div>
      </label>
      <div class="field"><span>Цвет</span><div class="color-options">
        ${PALETTE.map((value) => `<button type="button" data-action="pick-color" data-value="${value}" aria-label="Цвет ${value}" aria-pressed="${value === color}" style="background:${value}">${value === color ? icon("check", 18) : ""}</button>`).join("")}
      </div></div>
      <div class="field"><span>Значок</span><div class="icon-options">
        ${CATEGORY_ICONS.map((value) => `<button type="button" data-action="pick-icon" data-value="${value}" aria-label="Значок ${value}" aria-pressed="${value === selectedIcon}" class="${value === selectedIcon ? "active" : ""}">${icon(value, 20)}</button>`).join("")}
      </div></div>
      <div class="form-error" id="form-error" role="alert" hidden></div>
      <div class="modal-footer">
        ${canDelete ? `<button class="button danger-ghost" type="button" data-action="delete-category" data-id="${escapeHTML(category.id)}">${icon("trash", 16)}Удалить</button>` : '<button class="button secondary" type="button" data-action="close-modal">Отмена</button>'}
        <button class="button primary" type="submit">${icon("check", 16)}Сохранить</button>
      </div>
    </form>
  `,
          { kind: "category", editingId: category?.id || null },
        );
      }

      function updateCategoryPreview() {
        const form = $("#category-form");
        if (!form) return;
        const color = form.elements.color.value;
        const selectedIcon = form.elements.icon.value;
        $("#category-preview-name").textContent =
          form.elements.name.value.trim() || "Ваша категория";
        $("#category-preview-icon").innerHTML = categoryIcon({ color, icon: selectedIcon }, 27);
        for (const button of $$("[data-action=pick-color]", form)) {
          const selected = button.dataset.value === color;
          button.setAttribute("aria-pressed", String(selected));
          button.innerHTML = selected ? icon("check", 18) : "";
        }
        for (const button of $$("[data-action=pick-icon]", form)) {
          const selected = button.dataset.value === selectedIcon;
          button.setAttribute("aria-pressed", String(selected));
          button.classList.toggle("active", selected);
        }
      }

      function openSettings() {
        const currency = state.settings.currency;
        showModal(
          "Ваше пространство",
          "Небольшие настройки для большого удобства.",
          `
    <form id="settings-form">
      <div class="settings-fields">
        <label class="field" for="settings-name"><span>Название пространства</span>
          <input id="settings-name" name="name" value="${escapeHTML(state.settings.name)}" maxlength="32" required>
        </label>
        <label class="field" for="settings-currency"><span>Валюта</span>
          <div class="select-wrap"><select id="settings-currency" name="currency" aria-label="Валюта">
            <option value="RUB" ${currency === "RUB" ? "selected" : ""}>Российский рубль · ₽</option>
            <option value="EUR" ${currency === "EUR" ? "selected" : ""}>Евро · €</option>
            <option value="USD" ${currency === "USD" ? "selected" : ""}>Доллар США · $</option>
          </select>${icon("chevron-down", 14)}</div>
          <small class="field-hint">Меняется только символ. Суммы не конвертируются.</small>
        </label>
      </div>
      <div class="privacy-box">${icon("shield", 22)}
        <div><strong>Ваши данные принадлежат вам</strong><p>Хранятся в этом браузере, без отправки на сервер. Сделайте резервную копию перед очисткой браузера или сменой устройства.</p></div>
      </div>
      <div class="settings-section">
        <h3>Управление данными <span>${state.transactions.length} ${operationWord(state.transactions.length)}</span></h3>
        <div class="data-actions">
          <button type="button" data-action="export-all">${icon("download", 16)}<span>Экспорт в CSV<small>Для Excel и Google Таблиц</small></span>${icon("chevron-right", 15)}</button>
          <button type="button" data-action="backup">${icon("database", 16)}<span>Сохранить резервную копию<small>Операции, категории и настройки · JSON</small></span>${icon("chevron-right", 15)}</button>
          <button type="button" data-action="choose-backup">${icon("upload", 16)}<span>Восстановить из копии<small>Заменить текущие данные файлом JSON</small></span>${icon("chevron-right", 15)}</button>
          <input id="backup-file" type="file" accept="application/json,.json" hidden>
        </div>
        <div class="settings-danger">
          <button type="button" data-action="restore-demo">${icon("restore", 13)}Вернуть пример</button>
          <button type="button" class="danger-text" data-action="clear-data">${icon("trash", 13)}Удалить операции</button>
        </div>
      </div>
      <div class="form-error" id="form-error" role="alert" hidden></div>
      <div class="modal-footer"><button class="button secondary" type="button" data-action="close-modal">Отмена</button><button class="button primary" type="submit">${icon("check", 16)}Сохранить</button></div>
    </form>
  `,
          { kind: "settings", className: "settings-modal" },
        );
      }

      function confirmAction(title, description, label, callback, dangerous = false) {
        showModal(
          title,
          "",
          `
    <div class="confirm-symbol ${dangerous ? "danger" : ""}">${icon(dangerous ? "trash" : "restore", 24)}</div>
    <p class="confirm-description">${escapeHTML(description)}</p>
    <div class="modal-footer">
      <button class="button secondary" data-action="close-modal">Отмена</button>
      <button class="button ${dangerous ? "danger" : "primary"}" data-action="confirm">${escapeHTML(label)}</button>
    </div>
  `,
          { kind: "confirm", className: "confirm-modal", callback },
        );
      }

      function openHelp() {
        const tips = [
          {
            icon: "plus",
            title: "Начните с одной операции",
            text: "Добавляйте доходы и расходы, выбирайте дату и категорию. Изменить запись можно по нажатию на её название.",
          },
          {
            icon: "calendar",
            title: "Смотрите на картину целиком",
            text: "Периоды «3 дня», «7 дней» и «Месяц» включают последние 3, 7 и 30 дней, включая сегодня. «Всё время» — вся ваша история.",
          },
          {
            icon: "chart",
            title: "Замечайте главное",
            text: "Переключайте доходы и расходы на диаграмме. Наведите на график, чтобы увидеть точные суммы. Нажмите на категорию, чтобы посмотреть её операции.",
          },
          {
            icon: "shield",
            title: "Никаких лишних глаз",
            text: "Данные сохраняются только в браузере на этом устройстве. В настройках можно скачать резервную копию и перенести её на другой компьютер.",
          },
        ];
        showModal(
          "Знакомьтесь, flow",
          "Простое пространство для ваших финансов.",
          `
    <div class="help-content">
      ${tips.map((tip) => `<div class="help-item"><span>${icon(tip.icon, 21)}</span><div><h3>${tip.title}</h3><p>${tip.text}</p></div></div>`).join("")}
      <div class="keyboard-hints"><span><kbd>N</kbd>Новая операция</span><span><kbd>Ctrl / ⌘ K</kbd>Поиск</span></div>
      <button class="button primary full-width" data-action="close-modal">Всё понятно ${icon("arrow-right", 16)}</button>
    </div>
  `,
          { kind: "help" },
        );
      }

      // ---------------------------------------------------------------------------
      // 8. Добавление, редактирование и удаление данных.
      // ---------------------------------------------------------------------------

      function submitTransaction(form) {
        const input = Object.fromEntries(new FormData(form));
        const normalizedAmount = String(input.amount).replace(/\s/g, "").replace(",", ".");
        const amount = Number(normalizedAmount);
        const name = String(input.name || "").trim();

        if (!/^(?:\d+(?:\.\d{0,2})?|\.\d{1,2})$/.test(normalizedAmount)) {
          return showFormError("Введите сумму с не более чем двумя знаками после запятой.");
        }
        if (!Number.isFinite(amount) || amount < 0.01 || amount > 999999999999) {
          return showFormError("Сумма должна быть от 0,01 и меньше 1 триллиона.");
        }
        if (!name || name.length > 80)
          return showFormError("Укажите название длиной до 80 символов.");
        if (!isValidDate(input.date) || input.date < "1900-01-01" || input.date > todayISO()) {
          return showFormError("Выберите корректную дату, не позднее сегодняшней.");
        }
        const category = state.categories.find(
          (item) => item.id === input.category && item.type === input.type,
        );
        if (!category) return showFormError("Выберите категорию для этого типа операции.");

        const existing = state.transactions.find((item) => item.id === modalState.editingId);
        const transaction = {
          id: existing?.id || makeId(),
          name,
          amount: Math.round(amount * 100) / 100,
          date: input.date,
          type: input.type,
          category: input.category,
          note: String(input.note || "")
            .trim()
            .slice(0, 400),
          createdAt: existing?.createdAt || Date.now(),
        };
        const outsidePeriod = transaction.date < getRange().start;
        if (existing) {
          state.transactions = state.transactions.map((item) =>
            item.id === transaction.id ? transaction : item,
          );
        } else {
          state.transactions.push(transaction);
        }
        if (outsidePeriod) state.period = "all";
        state.tablePage = 1;
        saveData();
        closeModal();
        renderPage();
        const message = existing
          ? "Изменения сохранены"
          : transaction.type === "income"
            ? "Доход добавлен. Так держать!"
            : "Расход добавлен. Всё под контролем.";
        showToast(
          state.storageError
            ? "Операция добавлена, но браузер не разрешил сохранить данные."
            : outsidePeriod
              ? `${message} Выбран период «Всё время».`
              : message,
          state.storageError,
        );
      }

      function deleteTransaction(id) {
        const transaction = state.transactions.find((item) => item.id === id);
        if (!transaction) return;
        confirmAction(
          "Удалить операцию?",
          `«${transaction.name}» на сумму ${formatMoney(transaction.amount)} будет удалена из истории и статистики.`,
          "Удалить операцию",
          () => {
            state.transactions = state.transactions.filter((item) => item.id !== id);
            state.tablePage = 1;
            saveData();
            renderPage();
            showToast("Операция удалена");
          },
          true,
        );
      }

      function submitCategory(form) {
        const input = Object.fromEntries(new FormData(form));
        const existing = state.categories.find((item) => item.id === modalState.editingId);
        const name = String(input.name || "").trim();
        const type = existing?.type || input.type;
        if (!name || name.length > 35)
          return showFormError("Укажите название длиной до 35 символов.");
        if (
          state.categories.some(
            (item) =>
              item.id !== existing?.id &&
              item.type === type &&
              item.name.toLocaleLowerCase("ru-RU") === name.toLocaleLowerCase("ru-RU"),
          )
        ) {
          return showFormError("Категория с таким названием уже есть.");
        }
        const category = {
          id: existing?.id || makeId(),
          name,
          type,
          color: input.color,
          icon: input.icon,
        };
        if (!validCategory(category)) return showFormError("Проверьте цвет и значок категории.");
        state.categories = existing
          ? state.categories.map((item) => (item.id === existing.id ? category : item))
          : [...state.categories, category];
        state.categoryPageType = type;
        saveData();
        closeModal();
        renderPage();
        showToast("Категория сохранена");
      }

      function deleteCategory(id) {
        const category = state.categories.find((item) => item.id === id);
        if (!category || id.startsWith("other-")) return;
        confirmAction(
          "Удалить категорию?",
          `Операции из категории «${category.name}» сохранятся и будут перенесены в «Другое».`,
          "Удалить категорию",
          () => {
            state.transactions = state.transactions.map((item) =>
              item.category === id ? { ...item, category: `other-${item.type}` } : item,
            );
            state.categories = state.categories.filter((item) => item.id !== id);
            state.categoryFilter = "all";
            saveData();
            renderPage();
            showToast("Категория удалена. Операции сохранены.");
          },
          true,
        );
      }

      function submitSettings(form) {
        const input = Object.fromEntries(new FormData(form));
        const name = String(input.name || "").trim();
        if (!name || name.length > 32)
          return showFormError("Укажите название пространства длиной до 32 символов.");
        if (!Object.hasOwn(CURRENCY_SYMBOLS, input.currency))
          return showFormError("Выберите валюту из списка.");
        state.settings = { name, currency: input.currency };
        saveData();
        closeModal();
        renderPage();
        showToast("Настройки сохранены");
      }

      // ---------------------------------------------------------------------------
      // 9. Экспорт CSV и резервные копии в открытом формате JSON.
      // ---------------------------------------------------------------------------

      function downloadFile(content, mimeType, filename) {
        const blob = new Blob([content], { type: mimeType });
        const url = URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.href = url;
        link.download = filename;
        document.body.append(link);
        link.click();
        link.remove();
        setTimeout(() => URL.revokeObjectURL(url), 5000);
      }

      function exportCSV(items) {
        if (!items.length) return showToast("В выбранном периоде нет операций для экспорта", true);
        // Защищаем текстовые ячейки от интерпретации Excel как формул.
        const safeCell = (text) => (/^[=+\-@\t\r]/.test(text) ? `'${text}` : text);
        const quote = (value) => `"${String(value).replace(/"/g, '""')}"`;
        const rows = [["Дата", "Тип", "Название", "Категория", "Сумма", "Валюта", "Заметка"]];
        for (const item of [...items].sort((first, second) =>
          second.date.localeCompare(first.date),
        )) {
          rows.push([
            item.date,
            item.type === "income" ? "Доход" : "Расход",
            safeCell(item.name),
            safeCell(findCategory(item.category, item.type).name),
            String(item.amount).replace(".", ","),
            state.settings.currency,
            safeCell(item.note),
          ]);
        }
        // BOM помогает Excel правильно распознать UTF-8 и кириллицу.
        const csv = "\uFEFF" + rows.map((row) => row.map(quote).join(";")).join("\r\n");
        downloadFile(csv, "text/csv;charset=utf-8", `flow-операции-${todayISO()}.csv`);
        showToast(`Экспортировано: ${items.length} ${operationWord(items.length)}`);
      }

      function saveBackup() {
        const backup = {
          version: 1,
          exportedAt: new Date().toISOString(),
          transactions: state.transactions,
          categories: state.categories,
          settings: state.settings,
        };
        downloadFile(
          JSON.stringify(backup, null, 2),
          "application/json;charset=utf-8",
          `flow-backup-${todayISO()}.json`,
        );
        showToast("Резервная копия сохранена");
      }

      async function restoreBackup(file) {
        try {
          if (file.size > 10 * 1024 * 1024) throw new Error("Файл слишком большой");
          const data = JSON.parse(await file.text());
          if (
            data.version !== 1 ||
            !Array.isArray(data.transactions) ||
            !Array.isArray(data.categories) ||
            data.transactions.length > 100000 ||
            data.categories.length > 500 ||
            !validSettings(data.settings)
          ) {
            throw new Error("Неверная структура копии");
          }
          if (!data.categories.every(validCategory) || !data.transactions.every(validTransaction))
            throw new Error("Некорректные записи");
          if (
            new Set(data.categories.map((item) => item.id)).size !== data.categories.length ||
            new Set(data.transactions.map((item) => item.id)).size !== data.transactions.length
          )
            throw new Error("Повторяющиеся идентификаторы");
          if (
            !data.transactions.every((item) =>
              data.categories.some(
                (category) => category.id === item.category && category.type === item.type,
              ),
            )
          ) {
            throw new Error("Не найдены категории операций");
          }

          // Берём только известные поля; лишние данные из файла не используются.
          const transactions = data.transactions.map(
            ({ id, name, amount, date, type, category, note, createdAt }) => ({
              id,
              name,
              amount,
              date,
              type,
              category,
              note,
              createdAt,
            }),
          );
          const categories = data.categories.map(({ id, name, type, color, icon }) => ({
            id,
            name,
            type,
            color,
            icon,
          }));
          const settings = {
            name: data.settings.name.slice(0, 32),
            currency: data.settings.currency,
          };

          confirmAction(
            "Восстановить данные?",
            `В файле ${transactions.length} ${operationWord(transactions.length)}. Текущая история, категории и настройки будут заменены. При необходимости сначала сохраните их резервную копию.`,
            "Восстановить",
            () => {
              state.transactions = transactions;
              state.categories = categories;
              state.settings = settings;
              ensureFallbackCategories();
              resetFilters(false);
              state.period = "all";
              saveData();
              renderPage();
              showToast("Ваши данные восстановлены");
            },
          );
        } catch {
          showToast("Не удалось открыть копию. Выберите корректный JSON-файл Flow.", true);
        }
      }

      // ---------------------------------------------------------------------------
      // 10. Навигация, меню и уведомления.
      // ---------------------------------------------------------------------------

      function navigate(page) {
        if (!Object.hasOwn(PAGE_NAMES, page)) return;
        state.page = page;
        state.search = "";
        state.typeFilter = "all";
        state.categoryFilter = "all";
        state.tablePage = 1;
        toggleSidebar(false);
        renderPage(true);
        window.scrollTo({
          top: 0,
          behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth",
        });
      }

      function resetFilters(render = true) {
        state.search = "";
        state.typeFilter = "all";
        state.categoryFilter = "all";
        state.period = "all";
        state.tablePage = 1;
        if (render) renderPage();
      }

      function filterByCategory(category, type) {
        state.page = "transactions";
        state.search = "";
        state.categoryFilter = category;
        state.typeFilter = type;
        state.tablePage = 1;
        renderPage(true);
        window.scrollTo({ top: 0, behavior: "smooth" });
      }

      function toggleSidebar(open) {
        state.mobileOpen = open;
        $("#sidebar").classList.toggle("is-open", open);
        $("#sidebar-backdrop").hidden = !open;
        document.body.style.overflow = open || modalState ? "hidden" : "";
      }

      function closeRowMenus() {
        for (const menu of $$(".row-menu")) menu.remove();
        for (const button of $$("[data-action=row-menu]")) {
          button.classList.remove("selected");
          button.setAttribute("aria-expanded", "false");
        }
      }

      function toggleRowMenu(button) {
        const wasOpen = button.getAttribute("aria-expanded") === "true";
        closeRowMenus();
        if (wasOpen) return;
        const id = escapeHTML(button.dataset.id);
        button.classList.add("selected");
        button.setAttribute("aria-expanded", "true");
        button.insertAdjacentHTML(
          "afterend",
          `
    <div class="row-menu">
      <button data-action="edit-transaction" data-id="${id}">${icon("pencil", 14)}Редактировать</button>
      <button class="danger-text" data-action="delete-transaction" data-id="${id}">${icon("trash", 14)}Удалить</button>
    </div>
  `,
        );
      }

      function showToast(message, error = false) {
        clearTimeout(toastTimer);
        $("#toast-root").innerHTML = `
    <div class="toast ${error ? "error" : ""}" role="${error ? "alert" : "status"}">
      ${icon(error ? "alert" : "check-circle", 19)}
      <span>${escapeHTML(message)}</span>
      <button data-action="close-toast" aria-label="Закрыть уведомление">${icon("x", 15)}</button>
    </div>
  `;
        toastTimer = setTimeout(() => {
          $("#toast-root").innerHTML = "";
        }, 4500);
      }

      // ---------------------------------------------------------------------------
      // 11. Обработчики событий. Атрибуты data-action связывают кнопки с функциями.
      // ---------------------------------------------------------------------------

      function handleAction(button) {
        const action = button.dataset.action;
        switch (action) {
          case "navigate":
            navigate(button.dataset.page);
            break;
          case "open-sidebar":
            toggleSidebar(true);
            break;
          case "close-sidebar":
            toggleSidebar(false);
            break;
          case "new-transaction":
            openTransaction();
            break;
          case "edit-transaction":
            closeRowMenus();
            openTransaction(button.dataset.id);
            break;
          case "delete-transaction":
            closeRowMenus();
            deleteTransaction(button.dataset.id);
            break;
          case "row-menu":
            toggleRowMenu(button);
            break;
          case "transaction-type":
            changeTransactionType(button.dataset.type);
            break;
          case "new-category":
            openCategory();
            break;
          case "edit-category":
            openCategory(button.dataset.id);
            break;
          case "delete-category":
            deleteCategory(button.dataset.id);
            break;
          case "settings":
            openSettings();
            break;
          case "help":
            openHelp();
            break;
          case "close-modal":
            closeModal();
            break;
          case "confirm": {
            const callback = modalState?.callback;
            closeModal();
            if (callback) callback();
            break;
          }
          case "close-toast":
            clearTimeout(toastTimer);
            $("#toast-root").innerHTML = "";
            break;
          case "clear-search":
            state.search = "";
            state.tablePage = 1;
            renderPage();
            $("#global-search").focus();
            break;
          case "reset-filters":
            resetFilters();
            break;
          case "filter-category":
            filterByCategory(button.dataset.category, button.dataset.type);
            break;
          case "period": {
            if (!PERIODS.some((period) => period.id === button.dataset.period)) break;
            state.period = button.dataset.period;
            state.tablePage = 1;
            renderPage();
            $(`.period-controls [data-period="${state.period}"]`)?.focus({ preventScroll: true });
            break;
          }
          case "chart-mode": {
            state.chartMode = button.dataset.mode;
            renderPage();
            $(`.chart-tabs [data-mode="${state.chartMode}"]`)?.focus({ preventScroll: true });
            break;
          }
          case "breakdown-type":
            state.breakdownType = button.dataset.type;
            renderPage();
            break;
          case "type-filter":
            state.typeFilter = button.dataset.type;
            state.categoryFilter = "all";
            state.tablePage = 1;
            renderPage();
            break;
          case "category-type":
            state.categoryPageType = button.dataset.type;
            renderPage();
            break;
          case "table-page":
            state.tablePage = Math.max(1, Number(button.dataset.value));
            renderPage();
            break;
          case "sort": {
            const field = button.dataset.field;
            if (!["name", "date", "amount"].includes(field)) break;
            state.sortDirection = state.sortField === field ? -state.sortDirection : -1;
            state.sortField = field;
            renderPage();
            break;
          }
          case "pick-color": {
            if (!PALETTE.includes(button.dataset.value)) break;
            $("#category-form").elements.color.value = button.dataset.value;
            updateCategoryPreview();
            break;
          }
          case "pick-icon": {
            if (!CATEGORY_ICONS.includes(button.dataset.value)) break;
            $("#category-form").elements.icon.value = button.dataset.value;
            updateCategoryPreview();
            break;
          }
          case "export":
            exportCSV(
              state.page === "transactions" ? filteredTransactions() : periodTransactions(),
            );
            break;
          case "export-all":
            exportCSV(state.transactions);
            break;
          case "backup":
            saveBackup();
            break;
          case "choose-backup":
            $("#backup-file")?.click();
            break;
          case "clear-data": {
            confirmAction(
              "Начать с чистого листа?",
              "Все операции будут удалены. Категории и настройки останутся. Это действие нельзя отменить — рекомендуем сначала сохранить резервную копию.",
              "Удалить все операции",
              () => {
                state.transactions = [];
                resetFilters(false);
                saveData();
                renderPage();
                showToast("История очищена. Можно начать заново.");
              },
              true,
            );
            break;
          }
          case "restore-demo": {
            confirmAction(
              "Вернуть пример из таблицы?",
              "Текущие операции и категории будут заменены четырьмя записями с вашей картинки. Сначала сохраните резервную копию, если она нужна.",
              "Вернуть пример",
              () => {
                state.transactions = clone(DEMO_TRANSACTIONS);
                state.categories = clone(DEFAULT_CATEGORIES);
                resetFilters(false);
                saveData();
                renderPage();
                showToast("Пример восстановлен");
              },
            );
            break;
          }
        }
      }

      document.addEventListener("click", (event) => {
        if (!(event.target instanceof Element)) return;
        if (!event.target.closest(".row-actions")) closeRowMenus();
        const button = event.target.closest("[data-action]");
        if (!button || button.disabled) return;
        handleAction(button);
      });

      document.addEventListener("pointerdown", (event) => {
        if (event.target instanceof Element && event.target.classList.contains("modal-overlay"))
          closeModal();
      });

      document.addEventListener("submit", (event) => {
        const form = event.target;
        if (!(form instanceof HTMLFormElement)) return;
        event.preventDefault();
        if (form.id === "transaction-form") submitTransaction(form);
        if (form.id === "category-form") submitCategory(form);
        if (form.id === "settings-form") submitSettings(form);
      });

      document.addEventListener("input", (event) => {
        if (event.target.id === "global-search") {
          state.search = event.target.value;
          state.page = "transactions";
          state.period = "all";
          state.typeFilter = "all";
          state.categoryFilter = "all";
          state.tablePage = 1;
          renderPage();
        }
        if (event.target.id === "category-name") updateCategoryPreview();
      });

      document.addEventListener("change", (event) => {
        if (event.target.id === "filter-category") {
          state.categoryFilter = event.target.value;
          state.tablePage = 1;
          renderPage();
        }
        if (event.target.id === "backup-file" && event.target.files?.[0]) {
          restoreBackup(event.target.files[0]);
          event.target.value = "";
        }
      });

      document.addEventListener("keydown", (event) => {
        if (event.key === "Escape") {
          closeModal();
          closeRowMenus();
          toggleSidebar(false);
          return;
        }

        if (modalState && event.key === "Tab") {
          const controls = $$(
            "button:not([disabled]), input:not([type=hidden]):not([disabled]), select:not([disabled]), textarea, [tabindex='0']",
            $("#modal-root"),
          ).filter((element) => element.offsetParent !== null);
          if (!controls.length) return;
          if (event.shiftKey && document.activeElement === controls[0]) {
            event.preventDefault();
            controls.at(-1).focus();
          } else if (!event.shiftKey && document.activeElement === controls.at(-1)) {
            event.preventDefault();
            controls[0].focus();
          }
          return;
        }

        if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
          event.preventDefault();
          if (!modalState) $("#global-search").focus();
        }
        const editingText =
          event.target instanceof Element &&
          event.target.closest("input, textarea, select, [contenteditable='true']");
        if (
          event.key.toLowerCase() === "n" &&
          !event.ctrlKey &&
          !event.metaKey &&
          !event.altKey &&
          !editingText &&
          !modalState
        ) {
          event.preventDefault();
          openTransaction();
        }
      });

      window.addEventListener("resize", () => {
        clearTimeout(resizeTimer);
        resizeTimer = setTimeout(() => {
          if (window.innerWidth > 900 && state.mobileOpen) toggleSidebar(false);
          paintChart();
        }, 100);
      });

      // ---------------------------------------------------------------------------
      // 12. Запуск приложения. Ничего загружать с сервера не требуется.
      // ---------------------------------------------------------------------------

      for (const placeholder of $$("[data-icon]")) {
        placeholder.outerHTML = icon(
          placeholder.dataset.icon,
          Number(placeholder.dataset.size) || 18,
        );
      }
      loadData();
      renderPage();
    </script>
  <script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a38c1efb19cf0d2b',t:'MTc4OTAxOTA2OA=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>