/* The app's font. It has one heavy style, so we say it covers every weight. Then the browser
   does not draw a fake, even bolder copy of it. It has no Russian letters: those use the
   system font. */
@font-face {
  font-family: 'Sol';
  src: url('fonts/solheavy.ttf') format('truetype');
  font-weight: 100 900;
  font-display: swap;
}

/* Colors for light mode. Dark mode changes them below. */
:root {
  --background: #f6f7f6;
  --text: #1c2321;
  --text-soft: #5b6662;
  --accent: #0f766e;
  --label-background: #ffffff;
  --label-shadow: rgba(28, 35, 33, 0.25);
  /* The shadows of cards, pills and months. They are moved right and down, and a bit smaller
     than their item, so they never show above it or to its left. A card's or pill's shadow
     reaches 24px to the right and 26px down. A month's shadow reaches 14px down: it ends
     exactly where the next month starts. */
  --shadow: 12px 14px 16px -4px var(--label-shadow);
  --month-shadow: 8px 8px 11px -5px var(--label-shadow);
  --red: #dc2626;
  --yellow: #b45309;
  /* How far the back button of an open card is from the screen edges. */
  --back-gap: 20px;
  /* How long a pill takes to change color when it is switched on or off. */
  --pill-fade: 250ms;
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #111614;
    --text: #e8eeeb;
    --text-soft: #a3aea9;
    --accent: #5eead4;
    --label-background: #1b2220;
    --label-shadow: rgba(0, 0, 0, 0.6);
    --red: #f87171;
    --yellow: #facc15;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--background);
  color: var(--text);
  font-family:
    'Sol',
    system-ui,
    -apple-system,
    'Segoe UI',
    Roboto,
    sans-serif;
  /* Sol is always heavy. Bold makes the system font (used for Russian letters) look close to it. */
  font-weight: 700;
  /* Stops double-tap zoom on phones. */
  touch-action: manipulation;
  /* Stops the page from bouncing or reloading when pulled down in the installed app. */
  overscroll-behavior: none;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

/* While the app gets ready (cards, words in the right language, font), it stays hidden, so
   people never see it change. The app removes the "loading" class when it is ready. If that
   never happens (for example a script fails to load), the page shows after 4 seconds anyway. */
html.loading main {
  visibility: hidden;
  animation: show-anyway 0s 4s forwards;
}

@keyframes show-anyway {
  to {
    visibility: visible;
  }
}

main {
  min-height: 100%;
  display: grid;
  /* Keep content away from the phone's notch and rounded corners. */
  padding: max(24px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
    max(24px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
}

.screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  text-align: center;
}

.screen[hidden] {
  display: none;
}

.logo {
  width: 128px;
  height: 128px;
}

.logo-small {
  width: 72px;
  height: 72px;
}

.app-name {
  margin: 0 0 12px;
  font-size: 2.5rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accent);
}

/* The small copyright line at the bottom of the login screen. The free space above it and
   above the logo is shared, so the logo, name and button stay in the middle. */
#login > :first-child {
  margin-top: auto;
}

.legal {
  margin-top: auto;
  color: var(--text-soft);
  font-size: 0.8125rem;
}

.legal a {
  color: inherit;
}

.message {
  max-width: 32ch;
  margin: 0;
  color: var(--text-soft);
  line-height: 1.5;
}

#main {
  justify-content: space-between;
}

/* The Total and the spending goal: one group, in the middle of the free space. */
.card-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  width: 100%;
  margin: auto 0;
}

/* A card, like the cards in the iisu app. The card is painted with its border gradient, and
   an inner fill sits on top of it. The fill has a hole in the shape of the badge, so the badge
   shows the same gradient as the border: one surface, with no edge between them.
   Each card's sizes and colors come from components.js. The values here are only a fallback. */
.label {
  --width: 34rem;
  --height: 0px;
  --border: 3px;
  --radius: 24px;
  --badge-size: 48px;
  --pad-x: 24px;
  --pad-y: 48px;
  --label-border: linear-gradient(to bottom right, #14b8a6, #ec4899);
  /* The badge's round corner has the card's corner size, but never more than the badge allows. */
  --badge-corner: min(var(--radius), calc(var(--badge-size) - var(--border)));
  position: relative;
  /* Keeps the inner fill (below) behind the text but in front of the gradient. */
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: min(100%, var(--width));
  min-height: var(--height);
  margin: 0;
  padding: var(--pad-y) var(--pad-x);
  border: var(--border) solid transparent;
  border-radius: var(--radius);
  background: var(--label-border) border-box;
  /* The shadow falls to the bottom-right. */
  box-shadow: var(--shadow);
}

/* The inner fill. Its mask shows it everywhere except the badge: to the right of the badge,
   below the badge, and outside the badge's round bottom-right corner. */
.label::before {
  --hole: calc(var(--badge-size) - var(--border));
  --corner-start: calc(var(--hole) - var(--badge-corner));
  /* The round-corner piece reaches 1px past the badge, over the straight parts. Then no thin
     see-through line shows where they meet, also on phones with uneven pixel sizes. */
  --corner-piece: calc(var(--badge-corner) + 1px);
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: calc(var(--radius) - var(--border));
  background: var(--label-background);
  mask:
    linear-gradient(to right, transparent var(--hole), #000 var(--hole)),
    linear-gradient(to bottom, transparent var(--hole), #000 var(--hole)),
    radial-gradient(
        circle at 0 0,
        transparent calc(var(--badge-corner) - 0.25px),
        #000 calc(var(--badge-corner) + 0.25px)
      )
      var(--corner-start) var(--corner-start) / var(--corner-piece) var(--corner-piece) no-repeat;
}

/* Cards rise into view when their screen appears: the card and its shadow fade in from the
   background, the card moves up a little, and the shadow moves away from it to the
   bottom-right. There is only a start ("from"): each card ends at its own normal look.
   The browser plays it again every time a screen appears. While the app is still loading
   (html.loading) nothing plays, so the cards rise once people can see them. */
@keyframes card-rise {
  from {
    opacity: 0;
    transform: translateY(16px);
    box-shadow: 0 0 0 var(--label-shadow);
  }
}

.label {
  /* How long one card takes, and the wait before the next card starts. */
  --rise-time: 250ms;
  --rise-step: 75ms;
  /* The card's place in the line: 0 starts first. */
  --card-order: 0;
}

html:not(.loading) .label {
  /* "backwards": a card waiting for its turn stays invisible. */
  animation: card-rise var(--rise-time) ease-out calc(var(--card-order) * var(--rise-step))
    backwards;
}

/* On the main screen the cards rise one after the other, from the top down. */
#main [data-card='goal'] {
  --card-order: 1;
}

#main [data-card='logOut'] {
  --card-order: 2;
}

/* People who asked their phone to reduce motion get the cards at once, without movement. */
@media (prefers-reduced-motion: reduce) {
  html:not(.loading) .label {
    animation: none;
  }
}

/* A card without a corner icon has no hole in its fill. */
.label[data-badge='none']::before {
  mask: none;
}

/* Cards are flex boxes, so "hidden" needs saying again for them. */
.label[hidden] {
  display: none;
}

/* An icon used as content takes the color of the text around it. */
[data-icon] svg {
  display: block;
  fill: currentColor;
}

/* The badge only holds the icon. Its color comes from the card's border gradient. */
.label-badge {
  position: absolute;
  top: calc(-1 * var(--border));
  left: calc(-1 * var(--border));
  display: grid;
  place-items: center;
  width: var(--badge-size);
  height: var(--badge-size);
}

.label-badge svg {
  fill: #ffffff;
}

/* A card people can tap. */
.card-button {
  margin: 0;
  color: var(--text);
  font: inherit;
  font-size: 1.125rem;
  cursor: pointer;
}

.card-button:disabled {
  opacity: 0.5;
  cursor: default;
}

.total-value {
  margin: 0;
  font-size: clamp(1.5rem, 6vw, 2.75rem);
  font-weight: 600;
  line-height: 1.25;
  /* Keep the line breaks from the cell and break very long words. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: text;
}

/* The spending goal is red when it is over the limit, and yellow when it gets close. */
/* A Total below 0 is red. */
.total-value[data-negative] {
  color: var(--red);
}

.goal-value[data-level='over'] {
  color: var(--red);
}

.goal-value[data-level='close'] {
  color: var(--yellow);
}

/* A tap on an expandable card opens it full screen (see view.js). It grows from its place to
   the whole screen, above everything, while everything else fades to black. */
.label {
  /* How long opening and closing take. */
  --expand-time: 400ms;
}

.label[data-expandable] {
  cursor: pointer;
}

.label.open {
  position: fixed;
  /* The whole screen, but not under the phone's notch or rounded screen corners. */
  top: env(safe-area-inset-top, 0px);
  right: env(safe-area-inset-right, 0px);
  bottom: env(safe-area-inset-bottom, 0px);
  left: env(safe-area-inset-left, 0px);
  z-index: 20;
  width: auto;
  height: auto;
  cursor: default;
}

/* Keeps the open card's place, so the other cards do not move. */
.card-placeholder {
  flex: none;
}

#backdrop {
  position: fixed;
  inset: 0;
  z-index: 10;
  background: #000;
}

/* The back button of an open card, in the bottom-left corner, above the card. */
#back {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom, 0px) + var(--back-gap));
  left: calc(env(safe-area-inset-left, 0px) + var(--back-gap));
  z-index: 30;
}

/* What an open card shows as it opens: its parts rise in one after the other (--rise-order,
   set by view.js), once the card fills the screen ("settled"). When it closes ("closing"),
   they sink away in the reverse order (--sink-order): the same movement, backwards. */
@keyframes card-sink {
  to {
    opacity: 0;
    transform: translateY(16px);
    box-shadow: 0 0 0 var(--label-shadow);
  }
}

.label.open:not(.settled) .rise-in {
  visibility: hidden;
}

.label.open.settled .rise-in {
  animation: card-rise var(--rise-time) ease-out calc(var(--rise-order) * var(--rise-step))
    backwards;
}

.label.open.settled.closing .rise-in,
#back.sinking {
  animation: card-sink var(--rise-time) ease-in calc(var(--sink-order) * var(--rise-step)) both;
}

@media (prefers-reduced-motion: reduce) {
  .label.open.settled .rise-in,
  .label.open.settled.closing .rise-in,
  #back.sinking {
    animation: none;
  }
}

/* The open Total card: the number at the top as a header, with a small caption above it,
   and the parts of the Total below it as pills. They only show while the card is open. */
.label.open[data-card='total'],
.label.open[data-card='goal'] {
  justify-content: flex-start;
  /* Room at the bottom for the back button (its height, --back-size, is set by view.js). */
  padding-bottom: calc(var(--back-gap) + var(--back-size) - var(--border));
}

/* The pills sit in the middle of the free space between the number and the back button,
   so the space above them and below them is the same. */
.label.open .pills {
  margin: auto 0;
}

.total-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.total-caption {
  display: none;
  color: var(--text-soft);
  font-size: clamp(0.875rem, 3vw, 1.375rem);
}

.pills {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 100%;
  margin-top: 32px;
}

.label.open .total-caption {
  display: block;
}

.label.open .pills {
  display: flex;
}

/* A pill, like the "Playtime" pill in the iisu app: a dark outer part with a thin light
   border and the name in white, and a white inner part with the value. Black when the part
   counts in the Total (on), grey when it does not (off). It casts the same shadow as cards.
   Each pill's sizes and colors come from components.js. */
.pill {
  display: flex;
  align-items: center;
  gap: 12px;
  width: min(100%, var(--pill-width));
  height: var(--pill-height);
  padding: var(--pill-pad-y) var(--pill-pad-y) var(--pill-pad-y) var(--pill-pad-x);
  border: var(--pill-border) solid var(--pill-border-color);
  border-radius: 999px;
  background-color: var(--pill-on);
  color: #ffffff;
  font: inherit;
  font-size: 1rem;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: background-color var(--pill-fade) ease-in-out;
}

.pill[aria-pressed='false'] {
  background-color: var(--pill-off);
}

/* The pill's icon, left of the name, in the same color as the name. */
.pill-icon {
  flex: none;
  fill: currentColor;
}

.pill-name {
  flex: 1;
  overflow: hidden;
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pill-name::after {
  content: ':';
}

.pill-value {
  display: grid;
  flex: none;
  width: var(--pill-value-width);
  place-items: center;
  align-self: stretch;
  border-radius: 999px;
  background: #ffffff;
  color: #1c2321;
  transition: color var(--pill-fade) ease-in-out;
}

/* A pill that is off: its amount turns to the same grey as the pill, with the same fade. */
.pill[aria-pressed='false'] .pill-value {
  color: var(--pill-off);
}

@media (prefers-reduced-motion: reduce) {
  .pill,
  .pill-value {
    transition: none;
  }
}

/* The open goal card: the months of the goal, one under the other, in an area between the
   number and the back button (32px from each). The area is a box that cuts everything at its
   edges; its edges never move. On the sides it reaches into the card's padding, so the months'
   shadows are not cut. The shadows never reach above a month, so nothing is needed at the top. */
.month-area {
  display: none;
  position: relative;
  flex: 1;
  min-height: 0;
  width: min(100%, 360px);
  margin: 32px 0;
}

.label.open .month-area {
  display: block;
}

.month-room {
  height: 100%;
  margin: 0 -24px;
  padding: 0 24px;
  /* With no month open it scrolls like a normal list. */
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* While a month is open (or the months move), months-view.js places them: no scrolling by hand. */
.month-room.placed {
  overflow-y: hidden;
}

/* Changes that must happen at once, without animation (see months-view.js). */
.month-room.instant .month-column,
.month-room.instant .month,
.month-room.instant .month-arrow {
  transition: none;
}

.month-column {
  /* Room below the last month for its shadow, also when the area scrolls. */
  padding-bottom: 14px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  transition: transform var(--expand-time) ease-in-out;
}

/* A month: a light panel like the side panel in the iisu app, with white parts inside. It keeps
   this light look in dark mode too. */
.month {
  display: flex;
  flex: none;
  flex-direction: column;
  overflow: hidden;
  padding: 6px;
  border: 3px solid #ffffff;
  border-radius: 22px;
  background: #e9ebef;
  color: #1c2321;
  box-shadow: var(--month-shadow);
  transition: height var(--expand-time) ease-in-out;
}

/* The header row: calendar with the month's letter, the month's spending, and the arrow. */
.month-head {
  position: relative;
  z-index: 1;
  display: flex;
  flex: none;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 8px 12px;
  border: 0;
  border-radius: 16px;
  background: #ffffff;
  color: inherit;
  font: inherit;
  font-size: 1.125rem;
  cursor: pointer;
}

.month-head:disabled {
  cursor: default;
}

.month-calendar {
  position: relative;
  display: grid;
  flex: none;
  place-items: center;
}

.month-calendar svg,
.month-arrow,
.movement svg {
  flex: none;
  fill: currentColor;
}

/* The month's first letter, inside the empty part of the calendar. */
.month-letter {
  position: absolute;
  top: 11px;
  right: 0;
  left: 0;
  font-size: 11px;
  line-height: 1;
  text-align: center;
}

.month-amount {
  flex: 1;
  text-align: left;
}

/* A month without movements: two lines and the arrow in grey. */
.month-amount[data-empty],
.month-head:disabled .month-arrow {
  color: #9ca3af;
  fill: #9ca3af;
}

.month-arrow {
  transition: transform var(--expand-time) ease-in-out;
}

.month.open .month-arrow {
  transform: rotate(180deg);
}

/* The list of movements. It only shows once the month has a size of its own ("sized"), so a
   closed month is just its header row. When there is no room for all movements, it scrolls,
   and its scrolling never moves the months. */
.month-list {
  display: none;
  flex: 1;
  min-height: 0;
  margin-top: 6px;
  padding: 0 12px;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: 16px;
  background: #ffffff;
}

.month.sized .month-list {
  display: block;
}

.movement {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid #eef0f2;
}

.movement:last-child {
  border-bottom: 0;
}

.movement-text {
  display: flex;
  flex-direction: column;
  text-align: left;
}

.movement-description {
  color: #5b6662;
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

/* Soft fades over a month that is cut at the top or bottom edge of the area (see
   months-view.js). They show and hide at once, also while months move. */
.month-fade {
  position: absolute;
  right: -24px;
  left: -24px;
  z-index: 2;
  height: 24px;
  opacity: 0;
  pointer-events: none;
}

.month-fade-top {
  top: 0;
  background: linear-gradient(var(--label-background), transparent);
}

.month-fade-bottom {
  bottom: 0;
  background: linear-gradient(transparent, var(--label-background));
}

.month-fade.visible {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .month-column,
  .month,
  .month-arrow {
    transition: none;
  }
}
