:root {
  /* --- Neutrals -------------------------------------------------------------
     Warm, not grey. The envelopes are paper; against the old blue-grey #f5f6f8
     they read as slightly orange rather than as warm on their own terms. Same
     lightness as before, hue rotated to the paper's. */
  --bg: #f4f2ed;
  --card: #ffffff;
  --border: #e5e2da;
  --text: #1f2028;
  --muted: #6f6a60;

  /* --- Paper ----------------------------------------------------------------
     One family, lightest to darkest, used only by the envelopes. Named by role
     so a card never reaches for a literal again. */
  --paper: #fffdf7;
  --paper-empty: #fdfbf4;
  --paper-flap: #f6efdd;
  --paper-rule: #ece3cf;
  --paper-edge: #e7dfcb;
  --paper-ink: #3f3a2d;
  --paper-ink-soft: #7d6a44;
  --paper-ink-faint: #a3906a;
  /* #bdae8c was prettier and unreadable: 2.1:1 on the empty envelope. This is
     the faintest warm tone that still clears 3:1 there. */
  --paper-ink-ghost: #9a8760;

  /* --- Meaning --------------------------------------------------------------
     Blue is the only cold colour and it means "control": navigation, primary
     action, the week you are in. Money out and money in have a warm and a cool
     reading of the same darkness so they stay distinguishable in greyscale. */
  --primary: #2f6fed;
  --primary-dark: #2456bf;
  --primary-wash: #eaf0fe;
  --green: #17a86b;
  --red: #e5484d;
  --amber: #d99a1b;
  --sum-out: #a33a2e;
  --sum-in: #2f6a45;

  --radius: 12px;
  --shadow: 0 1px 2px rgba(31,32,40,.04), 0 1px 3px rgba(31,32,40,.06);
  /* The page texture, reused at card scale inside an envelope so the two share
     one visual language rather than two unrelated ones. */
  --hatch: repeating-linear-gradient(45deg, rgba(140,116,66,.045) 0 1px, transparent 1px 7px);
  --hatch-cross: repeating-linear-gradient(-45deg, rgba(140,116,66,.03) 0 1px, transparent 1px 7px);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  /* A cross-hatch at ~4% — the same 45° hairline the envelope paper uses, so the
     page reads as the surface the envelopes lie on. Kept under 5% so body text
     contrast is untouched; it is texture, not decoration. */
  background-color: var(--bg);
  background-image: var(--hatch), var(--hatch-cross);
  background-attachment: fixed;
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  min-height: 100vh;
}
#app { min-height: 100vh; display: flex; flex-direction: column; }

/* --- Top bar / nav --- */
/* The three parts add up to more than a narrow window, so the row has to say
   which of them gives. The brand does not (it is the identity and it is short),
   the tabs scroll, and the address truncates — otherwise the surplus becomes a
   horizontally scrolling page, which moves the whole app sideways when someone
   reaches for a control near the right edge. */
.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 20px; background: var(--card); border-bottom: 1px solid var(--border);
}
/* The unconfirmed-address notice. Warm rather than red: nothing is wrong yet,
   the address simply has not been confirmed, and an alarm colour for a normal
   state is how people learn to ignore the colour. */
.verify-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 10px 20px; background: var(--paper-flap); border-bottom: 1px solid var(--paper-edge);
  color: var(--paper-ink);
}
.verify-banner .btn { flex: 0 0 auto; }

/* The par-avion border, the one piece of the envelope metaphor outside the
   cards. Kept to 3px and to the palette's own blue and red rather than postal
   primaries, so it reads as a considered edge and not as a sticker. */
.airmail {
  height: 3px; flex: 0 0 auto;
  background: repeating-linear-gradient(
    -45deg,
    var(--primary) 0 7px,
    transparent 7px 14px,
    var(--red) 14px 21px,
    transparent 21px 28px
  );
  opacity: .55;
}
/* nowrap because the localised names are two words ("4 Конверта") and the
   topbar is a flex row: without it the label wraps under itself and the
   mark ends up beside a two-line block. */
.brand { font-weight: 700; font-size: 18px; display: flex; align-items: center; gap: 8px; white-space: nowrap; }
/* The mark is an image now, not an emoji: give it a box rather than a
   font-size, or it inherits the brand's 18px and renders at a quarter of the
   size it was drawn for. */
.brand .logo { width: 26px; height: 26px; flex: 0 0 auto; }
.nav-tabs { display: flex; gap: 4px; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.nav-tabs::-webkit-scrollbar { display: none; }
.nav-tabs button { flex: 0 0 auto; }
.nav-tabs button {
  background: transparent; border: none; padding: 8px 14px; border-radius: 8px;
  font-size: 14px; color: var(--muted); cursor: pointer;
}
.nav-tabs button.active { background: var(--primary-wash); color: var(--primary-dark); font-weight: 600; }
.topbar-right { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 0 1 auto; }
.topbar-right .muted { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar-right select { border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; background: white; }

main { flex: 1; padding: 24px; max-width: 1080px; margin: 0 auto; width: 100%; }

/* --- Generic --- */
.card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 18px 20px; box-shadow: var(--shadow);
}
.grid { display: grid; gap: 16px; }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) { .grid.cols-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .grid.cols-4, .grid.cols-2 { grid-template-columns: 1fr; } }

h1 { font-size: 22px; margin: 0 0 4px; }
h2 { font-size: 17px; margin: 0 0 12px; }
.muted { color: var(--muted); font-size: 13px; }
.section { margin-bottom: 28px; }
.row { display: flex; gap: 10px; align-items: center; }
.row.between { justify-content: space-between; }
.spacer { flex: 1; }

button.btn {
  background: var(--primary); color: white; border: none; border-radius: 8px;
  padding: 9px 16px; font-size: 14px; cursor: pointer; font-weight: 600;
}
button.btn:hover { background: var(--primary-dark); }
button.btn.secondary { background: #eef1f5; color: var(--text); }
button.btn.secondary:hover { background: #dfe3e9; }
button.btn.danger { background: #fde8e8; color: var(--red); }
button.btn.danger:hover { background: #fbd4d4; }
button.btn.small { padding: 5px 10px; font-size: 13px; }
/* State toggles are labelled with the state they are in, not with the action —
   "Active" that you click to deactivate. The colour carries the same
   information as the label so the row reads at a glance. */
button.btn.state-on { background: #e6f7ef; color: var(--green); }
button.btn.state-on:hover { background: #d3f0e2; }
button.btn.state-off { background: #eef1f5; color: var(--muted); }
button.btn.state-off:hover { background: #dfe3e9; }
button.link { background: none; border: none; color: var(--primary); cursor: pointer; font-size: 13px; padding: 0; }

input, select {
  border: 1px solid var(--border); border-radius: 8px; padding: 9px 10px; font-size: 14px;
  width: 100%; background: white; color: var(--text);
}
label { font-size: 13px; color: var(--muted); display: block; margin-bottom: 4px; }
.field { margin-bottom: 12px; }
form.inline { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
form.inline .field { margin-bottom: 0; min-width: 130px; flex: 1; }
/* Buttons are content-width, not column-width: the 130px floor above is meant
   for inputs, and applying it to a pair of buttons wraps them onto two lines.
   align-items: flex-end on the form then lines the pair up with the inputs. */
form.inline .form-actions { display: flex; gap: 8px; flex: 0 0 auto; min-width: 0; }

/* A card whose form ends in a row of actions. Cards sharing a grid row are the
   same height while their forms are not — the transfer form's "manual rate"
   label wraps, making its second row taller — so left to themselves the action
   rows land 31px apart. Pinning them to the bottom of the card lines them up
   across every card in the row, and keeps doing so when a form gains a field. */
.form-card { display: flex; flex-direction: column; }
.form-card > form { flex: 1; display: flex; flex-direction: column; }
.form-card .inline-fields { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.form-card .inline-fields .field { margin-bottom: 0; min-width: 130px; flex: 1; }
.form-card .form-actions { display: flex; gap: 8px; margin-top: auto; padding-top: 14px; }

.tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.tag.income { background: #e6f7ef; color: var(--green); }
.tag.expense { background: #fdeceb; color: var(--red); }
.tag.transfer { background: #eef1fb; color: #5457c9; }
.tag.pending { background: #fdf3e2; color: var(--amber); }
.tag.goal { background: #f1ecfb; color: #7c3aed; }

/* --- Envelopes ------------------------------------------------------------
   Drawn as paper envelopes seen from the back: body, then a flap folded down
   over it. Two tones of one paper plus a hairline do the whole job — no shadow
   stack, no gradients.

   The weekly figure is NOT printed on each envelope. It is the same number in
   all four by spec, so it is stated once above the row; repeating it four times
   was the thing that made the old cards read as a rendering mistake. */
.envelopes-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin-bottom: 18px; }

/* The amount is printed on each envelope, as an amount is on a real one. It is
   the same figure in all four by spec — that is the spec, not a bug to design
   around. */
.envelope .allocation {
  font-size: 22px; font-weight: 700; line-height: 1.1; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; margin: 2px 0 12px;
}
.envelope .allocation.negative { color: var(--sum-out); }
.envelope .allocation.positive { color: var(--sum-in); }

.envelope {
  position: relative; overflow: hidden; text-align: left;
  padding: 60px 16px 16px; min-height: 232px;
  background: var(--paper); border: 1px solid var(--paper-edge); border-radius: 3px 3px 10px 10px;
  box-shadow: 0 1px 1px rgba(92,74,38,.05), 0 8px 18px -8px rgba(92,74,38,.18);
}
/* The flap: a triangle with its base on the top edge and its apex pointing down
   at the centre, which is what the back of an envelope looks like.
   Not a rotated square — a 45° diamond wide enough to span a 216px card is
   ~108px deep, twice the fold we want, and clipping it to a shallower strip
   fills the strip edge to edge instead of tapering. clip-path draws the real
   shape at any base-to-depth ratio.
   The hairline is a second, 1px larger triangle behind in the edge colour:
   clip-path cuts a border off with the box, so the fold line has to be a
   layer rather than a border. */
.envelope .flap {
  position: absolute; top: 0; left: 0; right: 0; height: 53px; z-index: 0; pointer-events: none;
  background: var(--paper-edge); clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.envelope .flap::after {
  content: ''; position: absolute; top: 0; left: 1px; right: 1px; height: 52px;
  background: var(--paper-flap); clip-path: polygon(0 0, 100% 0, 50% 100%);
}
/* Paper grain: one repeating hairline at 5% — cheaper and calmer than noise. */
.envelope::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0; opacity: .5;
  background-image: var(--hatch);
}
/* Content sits above flap and grain. The flap is excluded: giving it
   position:relative here is what once knocked the "now" badge out of the card. */
.envelope > *:not(.flap) { position: relative; z-index: 1; }
.envelope .week { font-size: 13px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--paper-ink-soft); }
.envelope .range { font-size: 11px; color: var(--paper-ink-faint); font-variant-numeric: tabular-nums; }
/* The current week is the one the user acts in; the app's own blue says so,
   and stays the only cold colour on a warm card. */
.envelope.current { outline: 2px solid var(--primary); outline-offset: -1px; }
.envelope .now {
  position: absolute; top: 13px; right: 15px; z-index: 2;
  font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--primary);
}
/* An empty week is an unsealed envelope — dashed, flap lifted, no shadow. A
   blank card with a dash reads as a failure to render; this reads as a week
   with nothing in it. */
.envelope.empty { background: var(--paper-empty); border-style: dashed; box-shadow: none; }
.envelope.empty .flap { opacity: .45; }
.envelope.empty .week { color: var(--paper-ink-ghost); }
/* The line under the amount when a week's overspend was shared out. Small and
   quiet: it explains a figure, it is not a figure itself. */
/* The month switcher on the envelopes screen. Same shape as the calendar's, so
   the two screens page the same way. */
.month-nav { display: flex; align-items: center; gap: 6px; }
.month-nav .month-label { min-width: 132px; text-align: center; font-weight: 600; font-size: 14px; }
/* The breakdown under an envelope's figure: the month's share, what was entered
   for this week, and anything carried between weeks. Read down, they add up to
   the number above — which is the whole point of printing them. */
.envelope .breakdown { margin-top: 6px; }
.envelope .breakdown > li:first-child { color: var(--paper-ink-soft); }
.envelope .nothing { font-size: 13px; color: var(--paper-ink-ghost); }
.envelope .items { list-style: none; padding: 0; margin: 0; font-size: 13px; }
.envelope .items li { padding: 9px 0; border-top: 1px solid var(--paper-rule); display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.envelope .items li:first-child { border-top: none; padding-top: 0; }
.envelope .items .when { display: block; font-size: 10px; color: var(--paper-ink-faint); font-variant-numeric: tabular-nums; margin-top: 2px; }
/* On paper the amount is ink, not a pill: the coloured tag fought the card. */
.envelope .items .sum { font-size: 13px; font-weight: 500; color: var(--sum-out); white-space: nowrap; flex: 0 0 auto; }
.envelope .items .sum.income { color: var(--sum-in); }
/* The note wraps to two or three lines in a card this narrow. Without
   align-items above, the amount pill is a stretched flex item: it grows to the
   row's full height, its 999px radius turns it into an ellipse, and the text is
   stranded at the top. It must keep its own height and align with the note's
   first line. min-width lets the note wrap instead of shoving the amount out. */
.envelope .items li > :first-child { min-width: 0; }
.envelope .items li > .tag { flex: 0 0 auto; }

/* --- Goal reminder on the envelopes screen ---
   The .items list styling above is scoped to .envelope, so this list needs its
   own or it renders with default bullets. */
#goal-reminder .items { list-style: none; padding: 0; margin: 10px 0 0; font-size: 13px; }
#goal-reminder .items li { padding: 6px 0; border-top: 1px solid var(--border); display: flex; justify-content: space-between; gap: 8px; align-items: center; }
#goal-reminder .items li:first-child { border-top: none; }

/* --- Tables / lists --- */
table { width: 100%; border-collapse: collapse; font-size: 14px; }
table th { text-align: left; color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; padding: 8px 6px; border-bottom: 1px solid var(--border); }
table td { padding: 10px 6px; border-bottom: 1px solid var(--border); }
table tr:last-child td { border-bottom: none; }
/* Action cells hold a flex row of buttons in a wrapper div, so the cell itself
   stays a table-cell and keeps vertical-align. nowrap stops the pair breaking
   onto two lines when the column is squeezed. */
table td.actions { white-space: nowrap; }
table td.actions > .row { justify-content: flex-end; }

/* --- Auth screen --- */
.auth-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
.auth-card { max-width: 380px; width: 100%; }
.auth-card .logo-big { display: block; width: 60px; height: 60px; margin: 0 auto 8px; }
.auth-card h1 { text-align: center; }
.auth-card .tagline { text-align: center; margin-bottom: 20px; }
/* A button that reads as a link: "Forgot your password?" is a secondary route
   out of the sign-in form and would shout as a third button next to two real
   ones. */
.link-btn {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--primary); font-size: 13px; text-decoration: underline;
}
.link-btn:hover { color: var(--primary-dark); }

/* The counterpart of .error-msg for something that went right — "a link is on
   its way". Red for that would read as a failure. */
.notice-msg {
  background: #e8f3ec; color: #2f6a45; border-radius: 8px;
  padding: 9px 12px; font-size: 13px; margin-bottom: 12px;
}

.auth-toggle { display: flex; gap: 8px; margin: 16px 0; }
.auth-toggle button { flex: 1; }
/* Google renders its own button into #google-signin, so there is nothing here
   to style but the space around it — Google's branding guidelines do not allow
   restyling the button itself. */
/* The three provider buttons are drawn by three different parties — Google's
   script, Apple's script, and us — so nothing lines them up unless we do. All
   three are pinned to the same width and centred; 320 is what Google's and
   Apple's own widgets are asked for, and the only one we control follows. */
#google-signin, #appleid-signin { display: flex; justify-content: center; margin-top: 8px; }
/* Microsoft has no drop-in button widget, so this one is ours. Its guidelines
   ask for the four-square logo beside the label on a neutral surface, which is
   what the .btn.secondary surface already is. */
.provider-btn { display: flex; align-items: center; justify-content: center; gap: 10px; width: 320px; max-width: 100%; margin: 8px auto 0; border: 1px solid var(--border); }
.ms-logo { width: 16px; height: 16px; flex: 0 0 auto; display: inline-grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 1.5px; }
.ms-logo::before, .ms-logo::after { content: ''; }
/* Four squares: two drawn by the pseudo-elements, two by background layers. */
.ms-logo { background:
    linear-gradient(#7fba00, #7fba00) 100% 0 / 7.25px 7.25px no-repeat,
    linear-gradient(#ffb900, #ffb900) 100% 100% / 7.25px 7.25px no-repeat; }
.ms-logo::before { background: #f25022; }
.ms-logo::after { background: #00a4ef; grid-row: 2; grid-column: 1; }
.or-separator { display: flex; align-items: center; gap: 10px; margin: 16px 0 12px; color: var(--muted); font-size: 12px; }
.or-separator::before, .or-separator::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.error-msg { background: #fdeceb; color: var(--red); padding: 8px 12px; border-radius: 8px; font-size: 13px; margin-bottom: 12px; }
.success-msg { background: #e6f7ef; color: var(--green); padding: 8px 12px; border-radius: 8px; font-size: 13px; margin-bottom: 12px; }

/* --- Charts ---------------------------------------------------------------
   Marks stay thin, grid and axes stay recessive, and identity never rests on
   colour alone: the legend is always up, planned months are hatched as well as
   coloured, and the same numbers exist as a table below. */
.chart-card { margin-top: 16px; }
.chart { width: 100%; height: auto; display: block; }
.chart-legend { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; font-size: 12px; color: var(--muted); }
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend i { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.chart-legend i.planned {
  background: repeating-linear-gradient(45deg, var(--muted) 0 2px, transparent 2px 4px);
  border: 1px solid var(--border);
}

/* --- Calendar --- */
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.cal-cell { border: 1px solid var(--border); border-radius: 8px; padding: 6px; min-height: 84px; background: white; font-size: 12px; }
.cal-cell.empty { background: transparent; border: none; }
.cal-cell.today { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary) inset; }
.cal-cell .day-num { font-weight: 700; font-size: 13px; }
.cal-cell .day-balance { color: var(--muted); font-size: 11px; margin: 2px 0 4px; }
.cal-cell .day-item { border-radius: 5px; padding: 1px 4px; margin-bottom: 2px; font-size: 11px; }
.cal-cell .day-item.income { background: #e6f7ef; color: var(--green); }
.cal-cell .day-item.expense { background: #fdeceb; color: var(--red); }
.cal-cell .day-item.transfer { background: #eef1fb; color: #5457c9; }
.cal-weekday { text-align: center; font-size: 12px; color: var(--muted); font-weight: 600; padding-bottom: 4px; }

.goal-bar { height: 8px; background: #eef1f5; border-radius: 999px; overflow: hidden; margin: 6px 0; }
.goal-bar > div { height: 100%; background: var(--primary); }

