/*
 * tresi.app, the public site.
 *
 * Shared by the landing page and every legal/support page under it. Three
 * constraints shaped this file:
 *
 *  1. No JavaScript, anywhere. The site is served to people who are deciding
 *     whether to hand a finance app their bank connection, so it ships under a
 *     CSP with `script-src 'none'` (see deploy/Caddyfile). Theme switching is
 *     `prefers-color-scheme` only, and every bit of motion below is CSS.
 *
 *  2. No external requests. No CDN, no webfont, no analytics. The font stack
 *     names Nunito first to match the app, then falls straight through to the
 *     platform's rounded UI face when it is absent, which is the common case.
 *
 *  3. The palette is copied from `crates/tresi-ui/assets/tokens.css`, not
 *     reinvented. It is duplicated rather than imported because the app's
 *     sheet is keyed on `[data-theme]` set by a boot script, and there is no
 *     script here to set it. If a colour changes there, change it here too.
 *
 * The visual idea: the whole site is a paper receipt. Torn zigzag edges,
 * dotted leader lines, tabular figures, a total at the bottom. It is the one
 * object the product is actually about, and it makes a page of terms of
 * service look like it belongs to the same app as the bunny.
 */

/* ---- Tokens --------------------------------------------------------------- */

:root {
  color-scheme: light;

  --c-bg: #fdfbf7;
  --c-surface: #ffffff;
  --c-surface-sunken: #f6f2ec;
  --c-border: #e8e0d5;
  --c-border-strong: #d6cabb;

  --c-text: #2e2a26;
  --c-text-muted: #6f665d;
  --c-text-faint: #9c9188;

  --c-accent: #7c6bd4;
  --c-accent-soft: #ece8fb;
  --c-accent-text: #ffffff;

  --c-mint: #5fb49c;
  --c-mint-soft: #e2f3ee;
  --c-peach: #e8956b;
  --c-peach-soft: #fbeadf;
  --c-rose: #d47a8c;
  --c-rose-soft: #fae7eb;
  --c-butter: #e0b357;
  --c-butter-soft: #fbf1dc;

  --c-bunny-fill: #fffdfa;
  --c-bunny-line: #cdbfae;

  --font-sans: "Nunito", ui-rounded, "SF Pro Rounded", system-ui, -apple-system,
    "Segoe UI", sans-serif;
  --font-numeric: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono",
    monospace;

  --radius-md: 12px;
  --radius-lg: 20px;
  --radius-full: 999px;

  --shadow-sm: 0 1px 2px rgb(46 42 38 / 6%);
  --shadow-md: 0 6px 20px rgb(46 42 38 / 9%);

  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  --touch-target: 44px;

  /* Receipt geometry. One place, because the tear mask, the page padding and
   * the drop shadow all have to agree about where the paper ends. */
  --tooth-w: 22px;
  --tooth-h: 11px;
  --paper: 46rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --c-bg: #191714;
    --c-surface: #232019;
    --c-surface-sunken: #14120f;
    --c-border: #3a352d;
    --c-border-strong: #4f483d;

    --c-text: #f2ede5;
    --c-text-muted: #a89e92;
    --c-text-faint: #7a7167;

    --c-accent: #9d8ef0;
    --c-accent-soft: #2b2545;
    --c-accent-text: #17141f;

    --c-mint: #74c9b0;
    --c-mint-soft: #1c2f2a;
    --c-peach: #f0a87f;
    --c-peach-soft: #33251c;
    --c-rose: #e295a5;
    --c-rose-soft: #331f25;
    --c-butter: #eec570;
    --c-butter-soft: #332b18;

    --c-bunny-fill: #37312a;
    --c-bunny-line: #7d7365;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
    --shadow-md: 0 6px 20px rgb(0 0 0 / 45%);
  }
}

/* ---- Base ----------------------------------------------------------------- */

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

body {
  margin: 0;
  background-color: var(--c-bg);
  /* Faint dotted grid, like the ruling on a paper ledger. Two radial
   * gradients rather than an image keeps the zero-request rule intact. */
  background-image: radial-gradient(
    var(--c-border) 1px,
    transparent 1px
  );
  background-size: 26px 26px;
  background-position: -13px -13px;
  color: var(--c-text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1,
h2,
h3 {
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

a {
  color: var(--c-accent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}

a:hover {
  text-decoration-style: dotted;
}

:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Keyboard users land here first. It stays off-screen until focused. */
.skip {
  position: absolute;
  left: 50%;
  top: 0;
  translate: -50% -150%;
  z-index: 10;
  padding: 0.6rem 1rem;
  background: var(--c-accent);
  color: var(--c-accent-text);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  text-decoration: none;
  font-weight: 700;
  transition: translate 160ms var(--ease-out);
}

.skip:focus {
  translate: -50% 0;
}

/* ---- Shell ---------------------------------------------------------------- */

.wrap {
  width: min(100% - 2rem, var(--paper));
  margin-inline: auto;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-block: 1.25rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--c-text);
  text-decoration: none;
  font-weight: 800;
  font-size: 1.15rem;
  letter-spacing: 0.02em;
}

.brand__mark {
  width: 1.85rem;
  height: 1.85rem;
  flex: none;
}

.nav {
  display: flex;
  align-items: center;
  gap: 0.35rem 1rem;
  flex-wrap: wrap;
  font-size: 0.9rem;
}

.nav a {
  color: var(--c-text-muted);
  text-decoration: none;
  padding: 0.35rem 0;
}

.nav a:hover,
.nav a[aria-current="page"] {
  color: var(--c-accent);
}

.nav a[aria-current="page"] {
  font-weight: 700;
}

/* ---- The paper ------------------------------------------------------------
 *
 * A masked element cannot cast a `box-shadow`: the shadow is painted outside
 * the element's box and the mask clips it away. The wrapper exists purely to
 * carry a `drop-shadow` filter, which follows the masked silhouette (teeth
 * included) instead of the rectangle. */

.paper-wrap {
  filter: drop-shadow(var(--shadow-md));
  margin-block: 0 2.5rem;
}

.paper {
  background: var(--c-surface);
  padding: calc(var(--tooth-h) + 2rem) clamp(1.15rem, 5vw, 3rem)
    calc(var(--tooth-h) + 2rem);

  /* Zigzag both ends, straight sides. Three mask layers: teeth along the top
   * edge, teeth along the bottom edge, and a solid block covering everything
   * between them. Mask layers composite additively, so the union is the
   * paper. `-webkit-mask` is still required for older WebKit on iOS. */
  --tear-top: conic-gradient(
    from 135deg at top,
    #0000,
    #000 1deg 89deg,
    #0000 90deg
  );
  --tear-bottom: conic-gradient(
    from -45deg at bottom,
    #0000,
    #000 1deg 89deg,
    #0000 90deg
  );
  --tear-body: linear-gradient(#000 0 0);

  -webkit-mask:
    var(--tear-top) top / var(--tooth-w) var(--tooth-h) repeat-x,
    var(--tear-bottom) bottom / var(--tooth-w) var(--tooth-h) repeat-x,
    var(--tear-body) center / 100% calc(100% - 2 * var(--tooth-h)) no-repeat;
  mask:
    var(--tear-top) top / var(--tooth-w) var(--tooth-h) repeat-x,
    var(--tear-bottom) bottom / var(--tooth-w) var(--tooth-h) repeat-x,
    var(--tear-body) center / 100% calc(100% - 2 * var(--tooth-h)) no-repeat;
}

/* ---- Receipt furniture ---------------------------------------------------- */

.stamp {
  font-family: var(--font-numeric);
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-text-faint);
  text-align: center;
  margin: 0;
}

.rule {
  border: 0;
  border-top: 1.5px dashed var(--c-border-strong);
  margin-block: 1.5rem;
}

/* Dotted-leader rows, the way an itemised receipt sets them. The leader is a
 * flexible spacer with a dotted bottom border, so it stretches to fill
 * whatever the label and the value leave behind at any width. */
.items {
  margin: 0;
  font-family: var(--font-numeric);
  font-size: 0.9rem;
}

.item {
  display: flex;
  align-items: baseline;
  gap: 0.25rem;
  padding-block: 0.35rem;
}

/* The rows are `dt` + two `dd`, and a `dd` carries a 40px inline start margin
 * by default, which would push every value out of its column. */
.item dd {
  margin: 0;
}

.item__k {
  color: var(--c-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.78rem;
}

.item__dots {
  flex: 1 1 1.5rem;
  min-width: 1.5rem;
  border-bottom: 1.5px dotted var(--c-border-strong);
  translate: 0 -0.28em;
}

.item__v {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-weight: 700;
}

.item--total {
  margin-top: 0.5rem;
  padding-top: 0.9rem;
  border-top: 1.5px dashed var(--c-border-strong);
  font-size: 1.05rem;
}

.item--total .item__k {
  font-size: 0.85rem;
  color: var(--c-text);
  font-weight: 700;
}

.item--total .item__v {
  color: var(--c-mint);
  font-size: 1.35rem;
}

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

.hero {
  text-align: center;
}

.hero__bunny {
  width: clamp(7rem, 22vw, 9.5rem);
  margin-inline: auto;
}

.hero h1 {
  font-size: clamp(2rem, 7vw, 3rem);
  margin: 0.75rem 0 0.5rem;
}

.hero__lede {
  font-size: clamp(1.05rem, 2.6vw, 1.2rem);
  color: var(--c-text-muted);
  margin: 0 auto;
  max-width: 34rem;
}

.hero .items {
  max-width: 26rem;
  margin-inline: auto;
  text-align: left;
}

/* ---- Buttons -------------------------------------------------------------- */

.cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin-top: 1.75rem;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-target);
  padding: 0 1.5rem;
  border: 1.5px solid transparent;
  border-radius: var(--radius-full);
  background: var(--c-accent);
  color: var(--c-accent-text);
  font: inherit;
  font-weight: 700;
  text-decoration: none;
  transition:
    transform 140ms var(--ease-spring),
    background-color 140ms var(--ease-out);
}

.button:hover {
  transform: translateY(-2px);
}

.button:active {
  transform: scale(0.97);
}

.button--quiet {
  background: transparent;
  color: var(--c-text);
  border-color: var(--c-border-strong);
}

.button--quiet:hover {
  border-color: var(--c-accent);
  color: var(--c-accent);
}

.note {
  font-size: 0.85rem;
  color: var(--c-text-faint);
  margin: 1rem 0 0;
}

/* A class rather than a `style` attribute, because the CSP that serves this
 * site has no `unsafe-inline` in `style-src` and would drop it silently. */
.note--center {
  text-align: center;
}

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

.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.card {
  background: var(--c-surface-sunken);
  border: 1.5px solid var(--c-border);
  border-radius: var(--radius-lg);
  padding: 1.15rem 1.25rem;
}

.card h3 {
  margin: 0.5rem 0 0.35rem;
  font-size: 1.05rem;
}

.card p {
  margin: 0;
  font-size: 0.94rem;
  color: var(--c-text-muted);
}

/* The little coloured pill above each card title. Tone is carried by the
 * token, so dark mode follows without a second rule. */
.tag {
  display: inline-block;
  font-family: var(--font-numeric);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-full);
  background: var(--c-accent-soft);
  color: var(--c-accent);
  font-weight: 700;
}

.tag--mint {
  background: var(--c-mint-soft);
  color: var(--c-mint);
}
.tag--peach {
  background: var(--c-peach-soft);
  color: var(--c-peach);
}
.tag--rose {
  background: var(--c-rose-soft);
  color: var(--c-rose);
}
.tag--butter {
  background: var(--c-butter-soft);
  color: var(--c-butter);
}

/* ---- "Never" list ---------------------------------------------------------
 * The differentiators are all negatives, so they get a list that leads with a
 * struck-through marker instead of a bullet. */

.nevers {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.9rem;
}

.nevers li {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  gap: 0.75rem;
  align-items: start;
}

.nevers li::before {
  content: "\2715";
  grid-row: span 2;
  width: 1.6rem;
  height: 1.6rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: var(--c-rose-soft);
  color: var(--c-rose);
  font-size: 0.75rem;
  font-weight: 700;
  translate: 0 0.15rem;
}

.nevers strong {
  display: block;
}

.nevers span {
  color: var(--c-text-muted);
  font-size: 0.94rem;
}

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

.steps {
  counter-reset: step;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.1rem;
}

.steps li {
  display: grid;
  grid-template-columns: 2rem 1fr;
  gap: 0.9rem;
  align-items: start;
}

.steps li::before {
  counter-increment: step;
  content: counter(step);
  /* Without this the marker occupies row 1 of column 1 and the description
   * falls into row 2 of the same 2rem column, which wraps it to one word per
   * line. The `.nevers` markers span for the same reason. */
  grid-row: span 2;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: var(--c-accent-soft);
  color: var(--c-accent);
  font-family: var(--font-numeric);
  font-weight: 700;
}

.steps strong {
  display: block;
}

.steps span {
  color: var(--c-text-muted);
  font-size: 0.94rem;
}

/* ---- Prose (legal and support pages) --------------------------------------- */

.prose {
  max-width: 38rem;
  margin-inline: auto;
}

.prose h1 {
  font-size: clamp(1.7rem, 5vw, 2.3rem);
  margin: 0 0 0.35rem;
}

.prose h2 {
  font-size: 1.25rem;
  margin: 2.25rem 0 0.6rem;
  padding-top: 1.25rem;
  border-top: 1.5px dashed var(--c-border-strong);
}

.prose h2:first-of-type {
  margin-top: 1.75rem;
}

.prose h3 {
  font-size: 1rem;
  margin: 1.5rem 0 0.4rem;
}

.prose ul,
.prose ol {
  padding-left: 1.2rem;
}

.prose li {
  margin-block: 0.4rem;
}

.prose li::marker {
  color: var(--c-accent);
}

.dates {
  font-family: var(--font-numeric);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-text-faint);
  margin: 0 0 1.5rem;
}

/* Pulled-out reassurance, used sparingly. Left bar rather than a box, so it
 * does not compete with the cards on the landing page. */
.aside {
  border-left: 3px solid var(--c-mint);
  background: var(--c-mint-soft);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  padding: 0.9rem 1.1rem;
  margin: 1.25rem 0;
  font-size: 0.94rem;
}

.aside p {
  margin: 0;
}

.aside--warn {
  border-left-color: var(--c-peach);
  background: var(--c-peach-soft);
}

/* Tables carry the data-collection disclosures. Wide ones scroll in their own
 * container; the page itself must never scroll sideways. */
.table-scroll {
  overflow-x: auto;
  margin: 1.25rem 0;
  border: 1.5px solid var(--c-border);
  border-radius: var(--radius-md);
}

/* `table-layout: fixed` rather than the default auto. Auto layout sizes
 * columns to their content's preferred width, which for a cell holding a
 * sentence is the whole sentence unwrapped, so the disclosure tables grew past
 * the prose column and had to be scrolled sideways to be read even on a wide
 * screen. Fixed layout divides the available width evenly and wraps instead.
 * The min-width is the floor below which wrapping stops helping and the
 * `.table-scroll` container takes over. */
table {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
  min-width: 30rem;
  font-size: 0.9rem;
}

th,
td {
  text-align: left;
  padding: 0.65rem 0.85rem;
  border-bottom: 1px solid var(--c-border);
  vertical-align: top;
}

thead th {
  background: var(--c-surface-sunken);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--c-text-muted);
}

tbody tr:last-child td {
  border-bottom: 0;
}

code {
  font-family: var(--font-numeric);
  font-size: 0.88em;
  background: var(--c-surface-sunken);
  border: 1px solid var(--c-border);
  border-radius: 5px;
  padding: 0.1em 0.35em;
}

/* Anything still bracketed is a decision nobody has made yet. Marking it in
 * the stylesheet means an unfilled placeholder is impossible to publish by
 * accident: it is bright pink on the live page. */
.todo {
  background: var(--c-butter-soft);
  border-bottom: 2px dotted var(--c-butter);
  padding: 0 0.2em;
  font-weight: 700;
}

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

.footer {
  padding: 2.5rem 0 3.5rem;
  text-align: center;
  font-size: 0.88rem;
  color: var(--c-text-muted);
}

.footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  justify-content: center;
  margin-bottom: 1.25rem;
}

.footer a {
  color: var(--c-text-muted);
}

.footer a:hover {
  color: var(--c-accent);
}

.footer p {
  margin: 0.4rem 0;
  max-width: 34rem;
  margin-inline: auto;
}

.footer .stamp {
  margin-top: 1.5rem;
}

/* ---- The bunny ------------------------------------------------------------
 * A trimmed copy of `crates/tresi-ui/assets/bunny.css`: the idle pose, plus
 * one thing the app does not have, which is a hop when a visitor points at
 * it. The app's other six states all mean something about a user's data, and
 * there is no data here for them to describe. */

.bunny {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

.bunny__ear,
.bunny__head,
.bunny__all,
.bunny__eyes,
.bunny__nose {
  transform-box: fill-box;
}

.bunny__body,
.bunny__face,
.bunny__ear-outer,
.bunny__foot {
  fill: var(--c-bunny-fill);
  stroke: var(--c-bunny-line);
  stroke-width: 2;
}

.bunny__ear-inner {
  fill: var(--c-rose-soft);
}

.bunny__eye {
  fill: var(--c-text);
}

.bunny__eye-line {
  fill: none;
  stroke: var(--c-text);
  stroke-width: 2.2;
  stroke-linecap: round;
}

.bunny__cheek {
  fill: var(--c-rose);
  opacity: 0.35;
}

.bunny__nose {
  fill: var(--c-rose);
  transform-origin: center;
}

.bunny__mouth {
  fill: none;
  stroke: var(--c-text-muted);
  stroke-width: 1.6;
  stroke-linecap: round;
}

.bunny__eyes-closed {
  opacity: 0;
}

.bunny__eyes {
  animation: bunny-blink 5.5s var(--ease-out) infinite;
}

.bunny__nose {
  animation: bunny-twitch 4s var(--ease-out) infinite;
}

@keyframes bunny-blink {
  0%,
  92%,
  100% {
    transform: scaleY(1);
  }
  95% {
    transform: scaleY(0.1);
  }
}

@keyframes bunny-twitch {
  0%,
  88%,
  100% {
    transform: translateY(0) scale(1);
  }
  92% {
    transform: translateY(-0.6px) scale(1.12);
  }
}

/* Pointer devices only. On a touchscreen `:hover` sticks after a tap, which
 * would leave the bunny mid-hop until the visitor touched something else. */
@media (hover: hover) {
  .hero__bunny:hover .bunny__all {
    animation: bunny-hop 0.6s var(--ease-spring) 1;
  }
  .hero__bunny:hover .bunny__eyes {
    opacity: 0;
  }
  .hero__bunny:hover .bunny__eyes-closed {
    opacity: 1;
  }
  .hero__bunny:hover .bunny__cheek {
    opacity: 0.55;
  }
  .hero__bunny:hover .bunny__ear--left {
    transform: rotate(-10deg);
  }
  .hero__bunny:hover .bunny__ear--right {
    transform: rotate(10deg);
  }
}

.hero__bunny .bunny__ear,
.hero__bunny .bunny__eyes,
.hero__bunny .bunny__eyes-closed,
.hero__bunny .bunny__cheek {
  transition:
    transform 420ms var(--ease-spring),
    opacity 240ms var(--ease-out);
}

@keyframes bunny-hop {
  0%,
  100% {
    transform: translateY(0);
  }
  40% {
    transform: translateY(-9px);
  }
  70% {
    transform: translateY(1px);
  }
}

/* ---- Motion preference ----------------------------------------------------
 * Same rule as the app: the pose is information, the animation is decoration.
 * Stopping the loops leaves every static transform intact. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 120ms !important;
    scroll-behavior: auto !important;
  }

  .bunny__eyes {
    opacity: 1;
    transform: none;
  }

  .button:hover {
    transform: none;
  }
}

/* ---- Print ----------------------------------------------------------------
 * People do print privacy policies and terms, usually because someone told
 * them to keep a copy. Drop the paper illusion and set it as a document. */
@media print {
  body {
    background: #fff;
    color: #000;
  }

  .paper-wrap {
    filter: none;
  }

  .paper {
    -webkit-mask: none;
    mask: none;
    padding: 0;
    background: #fff;
  }

  .topbar,
  .footer nav,
  .skip,
  .cta {
    display: none;
  }

  .prose {
    max-width: none;
  }

  a {
    color: #000;
  }

  /* A printed page loses every link target, so put the destination in the
   * margin where a reader can actually type it in. */
  .prose a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    word-break: break-all;
  }
}
