/* The site's own rules. Everything it draws with is a token from
 * tokens.css — DESIGN.md is the source of truth and this file only
 * arranges what is already there.
 *
 * The whole stylesheet is roughly one screen long on purpose. A minimal
 * site built out of a component library is a contradiction; this is the
 * alternative and it stays small only if it is read now and then. */

*, *::before, *::after { box-sizing: border-box; }

/* `hidden` means hidden, whatever a class says about `display`. The
 * browser's own rule loses to any author rule that sets one — a grid
 * block with the attribute on is drawn anyway, which is how the cabinet
 * link's "press it again" showed while the press was still working. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: var(--font);
  -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); }                    /* underlined by default:  */
a:focus-visible,                               /* colour is never the only */
button:focus-visible,                          /* signal, and there is only */
:focus-visible {                               /* one hue to signal with.  */
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius);
}

h1 { font-size: 1.6rem; font-weight: 600; margin: 0; }
h2 { font-size: 1.15rem; font-weight: 600; color: var(--accent); margin: 0; }
p  { margin: 0; }

.dim   { color: var(--dim); }
.small { font-size: .9rem; }

/* Present to screen readers, absent from the page. Used where the visible
 * element is a shape — the circle's label is the obvious case. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* --- The frame -------------------------------------------------------- */

.shell {
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

.rail {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--step-6);
}

/* --- Header ----------------------------------------------------------- */

.top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step-4);
  padding-block: var(--step-6);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--step-2);
  color: var(--fg);
  text-decoration: none;
  font-weight: 600;
}
.wordmark svg { display: block; width: 1.5rem; height: 1.5rem; }

/* Two controls in the corner: the theme, and the way in. The header
 * carries the way in or the way back to an open account, and each auth
 * page swaps it for its counterpart — so the door the reader is standing
 * in is never also the button beside it. The theme switch sits before it,
 * the same height, so the two read as one row on a phone. */
.top__actions {
  display: flex;
  align-items: center;
  gap: var(--step-2);
  flex-shrink: 0;
}

/* The theme switch: a circle, half water — the only picture the site
 * has of light against dark, and it is the tank again. The half that is
 * filled is the half you are in; press it and the water changes sides.
 * 44px because it is a touch target first. */
.theme {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--pill);
  background: transparent;
  color: var(--fg);
  cursor: pointer;
}
.theme svg { display: block; width: 1.25rem; height: 1.25rem; }

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding-inline: var(--step-6);
  border: 1px solid var(--line);
  border-radius: var(--pill);
  background: transparent;
  color: var(--fg);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* The narrowest phones. Wordmark, theme switch and the way in are one
 * row at every width, and at 320px the row is three pixels too long in
 * English: the gutter and the button's padding each give up half a step
 * rather than the row breaking. Measured, not guessed — 22.5rem is 360px,
 * the width below which the sum stops fitting. */
@media (max-width: 22.5rem) {
  .rail { padding-inline: var(--step-4); }
  .top .button { padding-inline: var(--step-4); }
}

/* --- Hero ------------------------------------------------------------- */

.hero {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--step-12);
  padding-block: var(--step-12) var(--step-16);
  text-align: center;
}

.hero__words { display: grid; gap: var(--step-3); max-width: 30rem; }
.hero__after { display: grid; gap: var(--step-1); }

/* --- The tank: the site's version of the app's one control ------------ */

/* Registered so it can be transitioned. An unregistered custom property
 * is a bag of tokens with no type, and nothing can interpolate it — the
 * fill would jump. Where @property is missing the jump is what happens,
 * which is what reduced motion asks for anyway. */
@property --fill {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.tank {
  --size: clamp(11rem, 42vw, 13.75rem);   /* 220px, the app's diameter */
  --fill: 0;

  position: relative;
  display: grid;
  place-items: center;
  width: var(--size);
  height: var(--size);
  border: 1px solid var(--line);          /* the glass has an edge */
  border-radius: 50%;
  overflow: hidden;
  background: var(--tank);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: --fill 1s cubic-bezier(.33, .78, .38, 1);
}
.tank:focus-visible { border-radius: 50%; }

/* The highlight: one short arc where the light catches the glass, upper
 * left, in front of the water — it is the glass that is lit, not the
 * water — so it is the last thing painted. A mask rather than a border
 * arc, because a border gives a quarter turn with blunt ends and this
 * is fifty degrees with round ones. The tank is a sphere for exactly
 * this one line; nothing else about it is. */
.tank::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--glass);
  -webkit-mask: var(--gloss-arc) center / 100% 100% no-repeat;
  mask: var(--gloss-arc) center / 100% 100% no-repeat;
}
.tank {
  --gloss-arc: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M9.6 35.3A43 43 0 0 1 35.3 9.6' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* The mark is drawn twice, in the two colours that read against the two
 * things it can sit on — as ConnectButton.kt does it. Neither copy moves
 * and neither is nested in the water: they are siblings carrying the same
 * mask, so the one edge that reveals the water reveals the glyph on it,
 * and the two halves meet exactly at the surface with no transform to
 * keep in step. Paint order is tank mark, water, water mark. */
.tank__mark {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  fill: none;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.tank__mark--tank  { stroke: var(--dim); }
.tank__mark--water { stroke: var(--on-aqua); }

.tank__water {
  position: absolute;
  inset: 0;
  background: var(--aqua);
}

/* The surface: one wave across the circle, crests at both edges and a
 * trough in the middle, on a baseline that rises to the right.
 *
 * Everything below that line is opaque, so raising the mask raises the
 * water. The image is three diameters tall for exactly that reason — the
 * mask has to keep covering the circle long after the wave has left the
 * top of it, and outside the image a no-repeat mask is transparent.
 *
 * The wave occupies 45 to 73 of the 100-unit box, which is what sets the
 * two ends of the travel: the circle is dry while the mask sits below
 * .55 and full once it is above -.73, so .6 and -.8 are those bounds
 * with a little margin. Mapping past both edges is what stops the trough
 * cutting a notch out of a circle that is supposed to be full — the same
 * correction ConnectButton.kt makes for the same reason.
 *
 * Shape, not motion: the wave does not travel sideways. A wave that
 * moves on its own is reporting work in progress, which is the app's
 * job; here it only reports the pointer. */
.tank__water,
.tank__mark--water {
  --surface: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 300' preserveAspectRatio='none'%3E%3Cpath d='M0 57 L2.5 56.84 L5 56.94 L7.5 57.3 L10 57.9 L12.5 58.72 L15 59.73 L17.5 60.91 L20 62.2 L22.5 63.58 L25 65 L27.5 66.42 L30 67.8 L32.5 69.09 L35 70.27 L37.5 71.28 L40 72.1 L42.5 72.7 L45 73.06 L47.5 73.16 L50 73 L52.5 72.56 L55 71.86 L57.5 70.9 L60 69.7 L62.5 68.28 L65 66.67 L67.5 64.89 L70 63 L72.5 61.02 L75 59 L77.5 56.98 L80 55 L82.5 53.11 L85 51.33 L87.5 49.72 L90 48.3 L92.5 47.1 L95 46.14 L97.5 45.44 L100 45 L100 300 L0 300 Z' fill='%23000'/%3E%3C/svg%3E");
  --waterline: calc(var(--size) * .6 - var(--fill) * var(--size) * 1.4);
  -webkit-mask: var(--surface) center var(--waterline) / 100% 300% no-repeat;
  mask: var(--surface) center var(--waterline) / 100% 300% no-repeat;
}

/* Empty at rest, and it fills when it is reached for. */
@media (hover: hover) {
  .tank:hover { --fill: 1; }
}
.tank:focus-visible,
.tank:active { --fill: 1; }

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

/* --- Page: anything that is not the hero ------------------------------ */

.page {
  display: grid;
  gap: var(--step-12);
  padding-block: var(--step-8) var(--step-16);
}
.page__head,
.section { display: grid; gap: var(--step-6); }
.page__head { gap: var(--step-3); }

/* Running text — the policy. The section keeps its grid gap between
 * paragraphs, so the only things to add are a third heading level and a
 * list that reads at the measure. h3 is body size and weight alone: the
 * scale has two sizes above body and does not need a third. */
.prose h3 { font-size: 1rem; font-weight: 600; margin: var(--step-2) 0 calc(-1 * var(--step-4)); }
.prose ul { margin: 0; padding-left: var(--step-6); display: grid; gap: var(--step-2); }

/* Monospace only where a string has to be copied exactly — a filename, a
 * checksum. The .85em is the usual optical correction for a monospace
 * face sitting inside body text, not a fourth size in the scale. */
code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .85em;
  color: var(--dim);
  overflow-wrap: anywhere;
}

/* --- Cards ------------------------------------------------------------ */

.cards {
  display: grid;
  gap: var(--step-4);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.card {
  display: grid;
  align-content: start;
  gap: var(--step-4);
  padding: var(--step-6);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* A second, fainter edge a few pixels in: the inner face of the
   * glass. Two hairlines and the ground between them — no fill, no
   * blur, no shadow — is all the thickness a pane needs. */
  outline: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  outline-offset: -4px;
}
.card h2 { font-size: 1.15rem; }
.card__meta { display: grid; gap: var(--step-1); }

/* The one filled thing on this page, because it is the one thing the page
 * is for. Water, so it gets the fill; everything else is glass. */
.button--primary {
  background: var(--aqua);
  border-color: var(--aqua);
  color: var(--on-aqua);
}

/* A control that is there and not yet available — the premium switch
 * while the bought day runs. Faded, not recoloured: the sentence under
 * it carries the reason, as DESIGN.md asks of every state. */
.button:disabled { opacity: .5; cursor: default; }
.button--block { width: 100%; }

/* --- Steps ------------------------------------------------------------ */

.steps {
  display: grid;
  gap: var(--step-12);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}
.steps > li {
  display: grid;
  gap: var(--step-3);
  counter-increment: step;
}
/* Body weight, not a fourth size in the scale — and a plain number, since
 * a number in a circle is a badge and DESIGN.md has none. */
.steps h3 {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
}
.steps h3::before {
  content: counter(step) ". ";
  color: var(--accent);
}

.list {
  display: grid;
  gap: var(--step-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* --- Screenshot slots -------------------------------------------------- */

/* An empty slot is literally an empty tank: --tank ground inside a
 * hairline, with the size it wants printed in it. Replacing one means
 * dropping the file in and swapping the div for an <img>; the slot and
 * its instructions leave with it. */
.shot {
  display: grid;
  gap: var(--step-2);
  justify-items: start;
  margin: 0;
}
.shot__slot {
  display: grid;
  place-content: center;
  gap: var(--step-1);
  width: 100%;
  max-width: var(--shot-w);
  aspect-ratio: var(--shot-ratio);
  /* Without this the slot is 10px wider than a 375px phone. A box with an
   * aspect ratio transfers its content height back into an intrinsic
   * width, so two lines of wrapped text inside a 3:2 box ask for 1.5x
   * their own height — and the auto grid column above it grows to obey,
   * which scrolls the whole page sideways. The layout width never showed
   * it; only the intrinsic one did. */
  min-width: 0;
  padding: var(--step-4);
  text-align: center;
  background: var(--tank);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.shot img {
  display: block;
  width: 100%;
  max-width: var(--shot-w);
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* Two shapes, because a bottom sheet and a whole settings screen do not
 * crop to the same rectangle and forcing one on both would letterbox. */
.shot--wide { --shot-w: 30rem;    --shot-ratio: 3 / 2;  }   /* 480px */
.shot--tall { --shot-w: 18.75rem; --shot-ratio: 9 / 16; }   /* 300px */

/* --- Forms ------------------------------------------------------------ */

/* Narrower than the measure. 44rem of input asks to be read across, and
 * there is nothing in it to read. */
.rail--narrow { max-width: 22rem; }

.auth {
  display: grid;
  align-content: center;
  gap: var(--step-8);
  padding-block: var(--step-12) var(--step-16);
}

/* A title and the line under it belong together, not a whole step apart
 * like the blocks of the page do. */
.auth__head   { display: grid; gap: var(--step-3); }

.form         { display: grid; gap: var(--step-6); }
.form__errors { display: grid; gap: var(--step-1); }
.field        { display: grid; gap: var(--step-2); }
.notes        { display: grid; gap: var(--step-2); }

.field label { font-size: .9rem; color: var(--dim); }

/* A pill, like every other control here — the echo of the circle. Glass:
 * a hairline, and nothing inside it. The focus ring comes from the rule
 * at the top of this file and follows the radius set here. */
.field input {
  width: 100%;
  min-height: 44px;
  padding-inline: var(--step-4);
  border: 1px solid var(--line);
  border-radius: var(--pill);
  background: transparent;
  color: var(--fg);
  font: inherit;
}

/* Chrome fills an autofilled input pale blue, which is a second colour
 * arriving through the back door — and `background` cannot take it back,
 * because the UA sheet wins. An inset shadow the size of the field is
 * the one thing that can, and it is not depth: nothing is drawn outside
 * the box. */
.field input:-webkit-autofill,
.field input:-webkit-autofill:hover,
.field input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--bg) inset;
  -webkit-text-fill-color: var(--fg);
  caret-color: var(--fg);
}

/* --- The level: the site's version of the app's circle ---------------- */

/* The same idiom in the same two colours — tank ground, water fill — and
 * no figure written on it, because DESIGN.md has no badges and the line
 * underneath already says the number. Nothing here animates: on a page
 * load the level is not changing, it is arriving, and the fill is only
 * allowed to move when it reports work. */
.level {
  height: var(--step-3);
  border-radius: var(--pill);
  background: var(--tank);
  overflow: hidden;
}
.level__water {
  display: block;
  height: 100%;
  border-radius: var(--pill);
  background: var(--aqua);
}

/* The title's step, spent on the page's one figure instead of on a second
 * heading — not a fourth size in the scale. */
.balance { display: grid; gap: var(--step-1); }
.balance__sum { font-size: 1.6rem; font-weight: 600; }

/* Field and button on one line, since the field is one number wide.
 *
 * This used to be `align-items: end`, which lines a button up with the
 * bottom of its grid item — and the grid item is the whole field, not
 * the input. That held exactly as long as the field had nothing under
 * the input; the day it gained a line of help text the button dropped
 * below the box it belongs beside.
 *
 * So the field is flattened into this grid and the two controls are
 * placed by hand, in the same row. The help text stays inside the field
 * in the markup, because that is what carries its id and the input's
 * `aria-describedby` to it — the layout is moved, not the structure.
 * Anything else the field holds, a second hint or an error, lands in
 * column one under the input by ordinary auto-placement.
 *
 * Two gaps rather than one: the field's own rhythm down the column, the
 * form's beside it. And no `align-items`, so the button stretches to the
 * row — which is the input's height, and both are 44px pills anyway. */
.form--row {
  grid-template-columns: 1fr auto;
  gap: var(--step-2) var(--step-4);
}

.form--row .field { display: contents; }
.form--row .field > label { grid-column: 1; grid-row: 1; }
.form--row .field > input { grid-column: 1; grid-row: 2; }
.form--row .field > p { grid-column: 1; }
.form--row .button { grid-column: 2; grid-row: 2; }

/* --- Tables ----------------------------------------------------------- */

/* Hairline rows and nothing else: no zebra, no fill behind the header,
 * no border around the outside. The wrapper is what scrolls when a table
 * outgrows a phone, so the page itself never does. */
.scroll {
  overflow-x: auto;
  /* The same correction the screenshot slots need, for the same reason:
   * a grid item's min-width is `auto`, which means "as wide as my
   * content", and a table of nowrap cells is as wide as it likes. The
   * track grows to obey and the whole page scrolls sideways instead of
   * this box doing it. */
  min-width: 0;
}

.table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
.table th {
  font-size: .9rem;
  font-weight: 400;
  color: var(--dim);
}
.table th,
.table td {
  padding: var(--step-3) var(--step-6) var(--step-3) 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.table tr :last-child { padding-right: 0; }
.table tbody tr:last-child td { border-bottom: 0; }
/* A cell whose text is a name rather than a figure — a device's model —
 * and may need a second line on a phone. The rest keep nowrap: a date or
 * a sum broken in two reads as two things. */
.table td.wrap {
  white-space: normal;
  width: 100%;
}

/* A month of the ledger: its name, then its rows. */
.month { display: grid; gap: var(--step-2); }

/* A button that is a link, for an action inside a table row — a pill
 * there would outweigh the row it sits in. The vertical padding is what
 * makes the target the height of the row rather than the height of the
 * word. */
.link {
  padding: var(--step-2) 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.num {
  text-align: right;
  /* Digits of one width, so a column of sums lines up on the comma. */
  font-variant-numeric: tabular-nums;
}

/* --- Footer ----------------------------------------------------------- */

.foot {
  border-top: 1px solid var(--line);   /* the only rule on the page, and
                                        * it earns its place by separating
                                        * the product from the paperwork */
  padding-block: var(--step-8);
}
.foot .rail { display: grid; gap: var(--step-2); }

.foot__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--step-2) var(--step-6);
}
