/* The period chart, the filter chips, how a list appears.
   One of the stylesheets index.html loads IN ORDER: a rule here may rely on coming
   after the files before it. */

/* --- the period chart --- */
.chart {
  padding: calc(12px + var(--safe-top)) 0 8px;
  /* pan-y lets the browser have the vertical scroll and leaves the horizontal to us. That
     alone is not enough: the page being dragged is suppressed in touchmove, where we fix
     the gesture's direction and cancel the scroll for our own movements. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;      /* a long press does not summon the system menu */
}

.chart__period {
  display: block;
  margin: 0 auto;
  color: var(--hint);
  font-size: 14px;
}
.chart__period::after { content: " ⌄"; }
/* While a choice is fixed, the header shows it and works as the clear button. */
.chart__period.is-pinned { color: var(--text); }
.chart__period.is-pinned::after { content: " ✕"; color: var(--hint); }

/* The month's head: the faces lie one on another (chart.js). The total and its caption
   are the first face; in this month the balance is the second (app.js, the pair). A tap
   changes them with a short slide and a fade inside the block — the one going drifts
   left and dissolves, the one coming drifts in from the right and appears (back the
   other way). A slide across the whole width, cut off by the screen's edges, looked
   strange (the owner, 04.10). */
.chart--feed .chart__head { margin-top: 6px; }
.chart--feed .chart__reel { display: grid; }
/* The two never show through each other: the one going is gone in .12 s, the one coming
   starts appearing only then (two half-transparent numbers on one spot read as a mess).
   A transition is set by the state being entered: shown — late and slower, hidden —
   at once and quick. */
.chart--feed .chart__face {
  grid-area: 1 / 1;
  min-width: 0;
  text-align: center;
  transition: opacity .2s ease .12s, transform .34s cubic-bezier(.3,.8,.3,1);
}
.chart--feed .chart__face + .chart__face,
.chart--feed .shows-balance .chart__face:first-child {
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease, transform .34s cubic-bezier(.3,.8,.3,1);
}
.chart--feed .chart__face + .chart__face { transform: translateX(28px); }
.chart--feed .shows-balance .chart__face:first-child { transform: translateX(-28px); }
.chart--feed .shows-balance .chart__face + .chart__face {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition: opacity .2s ease .12s, transform .34s cubic-bezier(.3,.8,.3,1);
}
.chart--feed .chart__amount { line-height: 42px; }
/* A face's figure — the number and its caption — is the tap target, as wide as the
   number, not the whole row. */
.chart__figure {
  display: inline-block;
  vertical-align: top;
  -webkit-tap-highlight-color: transparent;
}
/* Two dots under the figure, still while the faces slide; the lit one moves. The row's
   height is kept in every month — the dots show only in the paired one — so a swipe
   between months never moves the chart. */
.chart__pager {
  display: flex;
  justify-content: center;
  gap: 6px;
  height: 12px;
  align-items: center;
  visibility: hidden;
}
.is-paired .chart__pager { visibility: visible; }
.chart__pager i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--hint);
  opacity: .35;
  transition: opacity .2s ease, background-color .2s ease;
}
.chart--feed .is-paired:not(.shows-balance) .chart__pager i:first-child,
.chart--feed .shows-balance .chart__pager i:last-child { background: var(--text); opacity: 1; }

/* A track of three months: previous, current, next. A finger drags it and the neighbouring
   month rides in to replace the current one — instead of the figures being swapped
   instantly. */
.chart__viewport { overflow: hidden; }
.chart__track {
  display: flex;
  width: 300%;
  will-change: transform;
  /* The track's height follows whichever slide is on screen. The neighbouring months lie on
     top, out of the flow: otherwise a month of five weeks stretched to six because the
     neighbour has six, and an empty row was left under the calendar. The owner noticed this
     as «in some months the block rises by itself the way it should». */
  position: relative;
  align-items: flex-start;
}
.chart__slide { width: 33.3333%; padding: 0 16px; }
.chart__slide:nth-child(2) { margin-left: 33.3333%; }
.chart__slide:first-child,
.chart__slide:last-child { position: absolute; top: 0; }
.chart__slide:first-child { left: 0; }
.chart__slide:last-child { left: 66.6667%; }
.chart__slide.is-blank { opacity: 0; }

.chart__amount {
  text-align: center;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}

.chart__delta {
  text-align: center;
  font-size: 13px;
  color: var(--hint);
  min-height: 20px;
}

/* Room on the right for the scale: the maximum and the average stand at their own heights. */
.chart__plot { position: relative; padding-right: 46px; margin-top: 14px; }
/* A month with no spending: the same height as a full one, but instead of the scale and the
   axis there is a single dashed line along the bottom. An empty field with the axis «1 9 16
   24 31» read as a broken chart, though there is nothing there to break. */
.chart__plot--blank {
  height: var(--plot-h, 150px);
  border-bottom: 1px dashed var(--line);
  margin-bottom: 18px;
}
/* The running total (chartline.js). The SVG is stretched to its box, so every stroke
   keeps its own width (non-scaling), and the dot and the guide are HTML on top. */
.chart__line { display: block; width: 100%; height: var(--plot-h, 150px); overflow: visible; }
.chart__area { fill: var(--accent); opacity: .12; }
.chart__stroke {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.4;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.chart__limit {
  stroke: var(--hint);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  opacity: .7;
  vector-effect: non-scaling-stroke;
}
.chart__dot,
.chart__guide {
  position: absolute;
  left: calc((100% - 46px) * var(--x, 1));
  pointer-events: none;
}
.chart__dot {
  top: calc(var(--plot-h, 150px) * var(--y, 1));
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2.5px var(--bg);
  transform: translate(-50%, -50%);
  transition: left .12s ease, top .12s ease;
}
/* «Now»: a quiet halo, not a blinking beacon. */
.chart__dot.is-today::after {
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  background: var(--accent);
  opacity: .18;
}
.chart__guide {
  display: none;
  top: 0;
  width: 1px; height: var(--plot-h, 150px);
  background: var(--hint);
  opacity: .45;
  transform: translateX(-50%);
}
.chart__guide.is-on { display: block; }
@media (prefers-reduced-motion: reduce) { .chart__dot { transition: none; } }

.chart__scale {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 46px;
  font-size: 11px;
  color: var(--hint);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.chart__scale span {
  position: absolute;
  left: 8px;
  transform: translateY(-50%);
  white-space: nowrap;
}
.chart__scale-limit { color: var(--hint); }

.chart__axis { position: relative; height: 16px; margin-top: 2px; margin-right: 46px; }

/* The balance's face: the check against the bank stands where the chart stood
   (reconcile.js, balanceCheck). At least as tall as the chart with its axis, so turning
   the face does not move the list under it. */
.chart--feed .bal-check { display: none; }
.chart--feed .shows-balance .chart__plot,
.chart--feed .shows-balance .chart__axis { display: none; }
.chart--feed .shows-balance .bal-check {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: calc(var(--plot-h, 150px) + 18px);
  margin-top: 14px;
}
.bal-check__title { font-size: 13px; color: var(--hint); }
.bal-check__row { display: flex; gap: 8px; }
.bal-check__row .sheet__input { flex: 1 1 auto; min-width: 0; }
.bal-check__go {
  padding: 0 16px;
  border-radius: var(--r-field);
  background: var(--accent-face);
  color: var(--on-accent);
  font-weight: 600;
}
.chart__tick {
  position: absolute;
  transform: translateX(-50%);
  font-size: 11px;
  color: var(--hint);
}

/* --- the filter chips --- */
.chips {
  display: flex;
  gap: 8px;
  padding: 8px 16px 12px;
  overflow-x: auto;
  /* contain — so that a strip scrolled to its end does not hand the gesture to its parent
     (which used to slide the whole panel sideways); touch-action — so the browser drives
     the strip itself, with the familiar inertia, rather than through our handlers. */
  overscroll-behavior-x: contain;
  touch-action: pan-x pan-y;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

/* The last chip fades out instead of being cut off square.
   Of six filters three were off-screen, the row scrolled four hundred pixels, and
   nothing said so: a chip sliced down the middle reads as a layout bug, while a fading
   one reads as «there is more here». The same strip elsewhere — the categories in the
   entry sheet — is cut the same way, so the rule covers both.
   Both edges fade, and deck.js sets the widths: an edge with nothing beyond it goes sharp
   again. A fade that promises nothing is worse than no fade — and a chip clipped square
   against a sharp edge reads as a bug rather than as «scroll me». The left one matters as
   much as the right: switching a filter on inserts «Reset» at the head of the row and
   scrolls the chosen chip into view, so the rescue button is the one that ends up half
   off. */
.chips, .cats {
  --fade-l: 0px;
  --fade-r: 0px;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fade-l),
    #000 calc(100% - var(--fade-r)), transparent);
  mask-image: linear-gradient(to right, transparent, #000 var(--fade-l),
    #000 calc(100% - var(--fade-r)), transparent);
}
.chips::-webkit-scrollbar { display: none; }

.chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: 60vw;
  padding: 8px 13px;
  /* A rounded square, one family with the keys and the icons — not a pill. */
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--edge);      /* one contour for every block on the screen */
  color: var(--text);
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  transition: background-color .15s ease, color .15s ease;
}
/* The chevron says «opens a choice»; it points down, the way the list will drop. */
.chip__more { transform: rotate(90deg); opacity: .5; margin: 0 -3px 0 -2px; }
.chip--reset { color: var(--hint); }
.chip__label { overflow: hidden; text-overflow: ellipsis; }
.chip__x {
  flex: 0 0 auto;
  /* The padding makes the cross reachable by finger without making the chip itself bigger. */
  margin: -8px -7px -8px 0;
  padding: 8px 9px 8px 2px;
  font-size: 11px;
  opacity: .65;
}
/* Active filter wears the accent, not an inverted card: the same colour marks
   «chosen» in the editor chips, on the pad tick and on the «+» button. */
.chip.is-active {
  /* A set filter is tinted, not shouting: the list under it already changed, the chip
     only has to say by what. */
  background: color-mix(in srgb, var(--accent) 15%, var(--surface));
  color: var(--accent-ink);
  font-weight: 600;
}

/* A bare icon (the search at rest, the sorting): square-ish, one tap target like the rest. */
.chip--icon { padding: 8px 11px; }
/* An icon with a word (the period, a search with its word) sits a little tighter to it. */
.chip svg + .chip__label { margin-left: -2px; }

/* --- the list appearing ---

The list fills in from the top down rather than appearing whole: swapping fifty rows in an
instant reads as a flicker, and the eye has nothing to hold on to.

The delay grows only for the first rows (see STAGGER_MAX in feed.js): at a step of thirty
milliseconds the fiftieth row would wait a second and a half, and it is off-screen anyway. */
/* The opacity comes up faster than the offset travels: a row becomes itself almost at once
   and only reaches its place a little later. That way the movement is visible while the row
   does not hang half-transparent for long. */
@keyframes pour {
  from { opacity: 0; transform: translateY(8px); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
.pour {
  animation: pour .24s cubic-bezier(.2, .7, .3, 1) both;
}


/* An operation that has just been written arrives highlighted and fades. The response lands
   where the person is looking after writing it — and holds nothing up. */
@keyframes flash {
  from { background: color-mix(in srgb, var(--accent) 26%, var(--surface)); }
  to   { background: var(--surface); }
}
.op--flash { animation: flash 1.1s ease-out both; }

/* A tab's screen arrives from the side of the tapped tab (app.js, showTab): short, and
   never in the way — the screen is live from the first frame. */
@keyframes tab-in {
  from { opacity: 0; transform: translateX(var(--tab-from, 0px)); }
  to   { opacity: 1; transform: none; }
}
.tab-in { animation: tab-in .18s cubic-bezier(.2, .8, .3, 1) both; }

@media (prefers-reduced-motion: reduce) {
  .pour, .op--flash, .tab-in { animation: none; }
}

/* Over all time the chart above is hidden, and the row of filters ends up under the cog —
   which stays where it is (position: fixed). The row is moved down by exactly the cog's
   height: otherwise the search, which stands first, is physically out of reach. */
