/* Kissa site, dressed like the app: Palette's colours (Shared/Chrome.swift),
   the chrome's pixel face, and the frames cards and buttons are cut from
   (Chrome/, KissaKit ChromeFrame). Pixel art is shown at whole multiples.

   KissaChrome (Chrome/KissaChrome.ttf, drawn in this repo, Art/CHROME-FONT.md)
   is ten pixels to the em: set it at 20, 30 or 40 px only, two, three or four
   screen pixels to each of its own. Body text stays in the system face, as in
   the app. */
@font-face {
  font-family: "Kissa Chrome";
  src: url("/fonts/kissa-chrome.ttf") format("truetype");
  font-display: swap;
}

:root {
  color-scheme: dark;

  --space-1: 4px;   --space-2: 8px;   --space-3: 12px;  --space-4: 16px;
  --space-5: 24px;  --space-6: 32px;  --space-7: 48px;  --space-8: 64px;
  --space-9: 96px;

  --text-sm: 14px;  --text-base: 16px; --text-lg: 18px;

  /* Palette, darkest wood first. */
  --ink:      #1C120C;  /* pixel outlines, text on gold, the footer */
  --page:     #261711;  /* behind everything */
  --wood-900: #291A12;  /* wells */
  --wood-800: #3B241A;  /* a card's lower lip, dividers */
  --wood-700: #523626;  /* a card's face */
  --wood-600: #6B4835;  /* a card's lit rim */
  --wood-300: #C2A382;  /* secondary text */
  --cream:    #F5EBD9;  /* primary text */
  --gold:     #F2B84A;  /* the one action colour: links, the button */
  --gold-700: #B07A1E;
  --moss:     #8FAD6B;  /* what is so: free, yours */
  --sky:      #8CA8C4;  /* how far along: the vote's bars */

  /* Frames, at two screen pixels to each of theirs, as the app draws them. */
  --frame-card: url("/img/frame-card.png") 2 2 3 2 fill / 4px 4px 6px 4px stretch;
  --frame-well: url("/img/frame-well.png") 1 fill / 2px stretch;
  --frame-wood: url("/img/frame-wood.png") 2 2 3 2 fill / 4px 4px 6px 4px stretch;
  --frame-gold: url("/img/frame-gold.png") 2 2 3 2 fill / 4px 4px 6px 4px stretch;

  --shadow: 0 10px 24px rgba(10, 5, 3, .5);

  /* Motion: two springs, as linear() curves sampled from a damped spring.
     One bounces, for what the pointer touches; one settles, for what
     arrives. Pixel art itself moves as sprites do, a whole pixel a frame,
     in steps(). */
  --spring: linear(0, 0.119, 0.385, 0.686, 0.941, 1.112, 1.193, 1.201, 1.162, 1.102, 1.043, 0.996, 0.969, 0.958, 0.961, 0.971, 0.984, 0.995, 1.003, 1.008, 1.009, 1.007, 1.005, 1.002, 1);
  --spring-time: 580ms;
  --settle: linear(0, 0.049, 0.163, 0.305, 0.449, 0.582, 0.696, 0.789, 0.862, 0.917, 0.955, 0.982, 0.999, 1.009, 1.014, 1.015, 1.015, 1.013, 1.011, 1.009, 1.007, 1.005, 1.003, 1.002, 1);
  --settle-time: 590ms;

  --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-pixel: "Kissa Chrome", system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--page);
  color: var(--cream);
  font: 400 var(--text-base)/1.5 var(--font);
}
img { display: block; max-width: 100%; height: auto; }
.pixel { image-rendering: pixelated; }
a { color: var(--gold); text-underline-offset: 3px; }
a:hover { color: var(--cream); }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
p { margin: 0; }

/* The pixel face: never bold, never slanted, never scaled off its grid. */
h1, h2, h3, .brand, .chip, .button, .steps li::before {
  font-family: var(--font-pixel);
  font-weight: 400;
  font-synthesis: none;
  line-height: 1.4;
}
h1, h2, h3 { margin: 0; }
h3 { font-size: 20px; }

/* Glyphs: the app's PixelGlyphs, two screen pixels to each of theirs. */
.glyph { width: auto; height: 16px; fill: currentColor; shape-rendering: crispEdges; flex: none; }
.label { display: flex; align-items: center; gap: var(--space-2); }
.label .glyph { color: var(--moss); }
.features .label .glyph { color: var(--gold); }

.skip { position: absolute; left: -9999px; }
.skip:focus { left: var(--space-4); top: var(--space-4); background: var(--gold); color: var(--ink); padding: var(--space-2) var(--space-4); }

.wrap { max-width: 1040px; margin: 0 auto; padding: 0 var(--space-4); }

/* Header */
.site-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-4) 0; }
.brand { display: flex; align-items: center; gap: var(--space-3); color: var(--cream); text-decoration: none; font-size: 20px; }
.brand:hover { color: var(--cream); }
.brand img { width: 40px; height: 40px; border-radius: 9px; }
.site-nav ul { display: flex; gap: var(--space-5); margin: 0; padding: 0; list-style: none; font-size: var(--text-sm); }
.site-nav a { color: var(--wood-300); text-decoration: none; }
/* Header and footer links underline in a pixel rule, drawn on hover. */
.site-nav a, .site-footer a {
  padding-bottom: 2px;
  background: linear-gradient(currentColor, currentColor) no-repeat 0 100% / 0 2px;
}
.site-nav a:hover, .site-footer a:hover { background-size: 100% 2px; }
.site-nav a:hover { color: var(--cream); }

/* Hero */
.hero { display: grid; gap: var(--space-7); align-items: center; padding: var(--space-6) 0 var(--space-8); }
.hero > div { position: relative; }
.hero h1 { font-size: 30px; margin-bottom: var(--space-4); }
.hero h1 em { font-style: normal; color: var(--gold); }
.lede { max-width: 34em; color: var(--wood-300); font-size: var(--text-lg); margin-bottom: var(--space-6); }
.cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4); }
.facts { color: var(--wood-300); font-size: var(--text-sm); }
.phone { justify-self: center; width: 280px; border: 2px solid var(--wood-600); border-radius: 36px; box-shadow: var(--shadow); }

/* Buttons and chips: raised wood for what only says how things are, gold
   for the one thing to press. */
.chip, .button {
  display: inline-block;
  border-style: solid;
  border-image: var(--frame-wood);
  image-rendering: pixelated;
  padding: var(--space-1) var(--space-3) var(--space-2);
  font-size: 20px;
  color: var(--cream);
  text-decoration: none;
}
.button { border-image: var(--frame-gold); color: var(--ink); }
.button:hover { color: var(--ink); }
/* A button rises off the page under the pointer, its hard shadow left
   where it stood, and drops a pixel when pressed, as in the app. */
@media (hover: hover) {
  .button:not(:disabled):hover { transform: translateY(-2px); filter: brightness(1.06) drop-shadow(0 2px 0 var(--ink)); }
}
.button:not(:disabled):active { transform: translateY(2px); filter: none; }

/* Sections */
.section { padding: var(--space-8) 0; border-top: 2px solid var(--wood-800); }
.section h2 { font-size: 20px; margin-bottom: var(--space-6); }
.section-intro { max-width: 34em; color: var(--wood-300); margin: calc(-1 * var(--space-4)) 0 var(--space-6); }
.muted { color: var(--wood-300); }

/* How it plays: each number on a raised wooden tile. */
.steps { display: grid; gap: var(--space-6); margin: 0; padding: 0; list-style: none; counter-reset: step; }
.steps li { counter-increment: step; display: grid; grid-template-columns: 36px 1fr; gap: var(--space-1) var(--space-4); }
.steps li::before {
  content: counter(step);
  grid-row: span 2;
  display: grid; place-items: center;
  width: 36px; height: 38px;
  border-style: solid;
  border-image: var(--frame-wood);
  image-rendering: pixelated;
  padding-bottom: 2px;
  font-size: 20px;
  color: var(--cream);
}
.steps h3 { padding-top: var(--space-1); }
.steps p { color: var(--wood-300); }

/* Cards: an ink outline, lit along the top and left, a lower lip in the
   dark. The frame paints the face too. */
.cards { display: grid; gap: var(--space-4); margin: 0; padding: 0; list-style: none; }
.card {
  border-style: solid;
  border-image: var(--frame-card);
  image-rendering: pixelated;
  padding: var(--space-4) var(--space-5) var(--space-5);
}
.card h3 { margin-bottom: var(--space-1); }
.card p { color: var(--wood-300); }

/* A cafe sits in a well sunk into its card, at whole pixels: the frame is
   outside the picture, so the picture keeps its 128 or 256. */
.cafe { display: grid; grid-template-columns: 132px 1fr; gap: var(--space-4); align-items: center; padding: var(--space-4); }
.cafe img {
  box-sizing: content-box;
  width: 128px; height: 128px;
  border-style: solid;
  border-image: var(--frame-well);
}
.price { display: inline-flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); color: var(--wood-300); font-size: var(--text-sm); }
.price.free { color: var(--moss); font-weight: 600; }
.price .glyph { height: 16px; }

/* Everywhere on iPhone */
.split { display: grid; gap: var(--space-7); align-items: center; }
.features { display: grid; gap: var(--space-5); margin: 0; padding: 0; list-style: none; }
.features h3 { margin-bottom: var(--space-1); }
.features p { color: var(--wood-300); }

/* FAQ and text pages */
.faq { display: grid; gap: var(--space-6); max-width: 40em; }
.faq h3 { margin-bottom: var(--space-2); }
.faq p + p { margin-top: var(--space-3); }
.faq p { color: var(--wood-300); }

.page { max-width: 40em; padding: var(--space-6) 0 var(--space-9); }
.page h1 { font-size: 30px; margin-bottom: var(--space-3); }
.page h2 { font-size: 20px; margin: var(--space-7) 0 var(--space-3); }
.page p, .page li { color: var(--wood-300); }
.page p + p, .page p + ul, .page ul + p { margin-top: var(--space-3); }
.page ul { padding-left: var(--space-5); margin: 0; }
.page li + li { margin-top: var(--space-2); }
.page .updated { color: var(--wood-300); font-size: var(--text-sm); }
.contact { margin-top: var(--space-5); font-size: 20px; font-family: var(--font-pixel); }

.fact-sheet { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-2) var(--space-5); margin: 0; }
.fact-sheet dt { color: var(--wood-300); }
.fact-sheet dd { margin: 0; color: var(--cream); }
.press-images { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: var(--space-4); margin: 0; padding: 0; list-style: none; }
.page .press-images { padding-left: 0; }
.page .press-images li + li { margin-top: 0; }
.press-images img {
  box-sizing: content-box;
  width: 128px; height: 128px; object-fit: cover; object-position: top;
  border-style: solid; border-image: var(--frame-well); image-rendering: pixelated;
  margin-bottom: var(--space-2);
}
.press-images img:not(.pixel) { image-rendering: auto; }
.press-images a { font-size: var(--text-sm); }

/* Footer */
.site-footer { background: var(--ink); color: var(--wood-300); font-size: var(--text-sm); padding: var(--space-7) 0; }
.site-footer .wrap { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-4); }
.site-footer ul { display: flex; gap: var(--space-5); margin: 0; padding: 0; list-style: none; }
.site-footer a { color: var(--wood-300); text-decoration: none; }
.site-footer a:hover { color: var(--cream); }

@media (min-width: 768px) {
  .hero { grid-template-columns: 1fr 320px; padding: var(--space-8) 0 var(--space-9); }
  .hero h1 { font-size: 40px; }
  .phone { width: 320px; }
  .section { padding: var(--space-9) 0; }
  .section h2 { font-size: 30px; }
  .steps { grid-template-columns: repeat(3, 1fr); }
  .cards.two { grid-template-columns: repeat(2, 1fr); }
  .split { grid-template-columns: 1fr 280px; }
  .page h1 { font-size: 40px; }
}

@media (min-width: 1000px) {
  .cafe { grid-template-columns: 260px 1fr; gap: var(--space-5); }
  .cafe img { width: 256px; height: 256px; }
}

/* Any other picture sunk into the page, as on the 404. */
.well { box-sizing: content-box; border-style: solid; border-image: var(--frame-well); }

/* The city vote (vote/): a card per city, its teaser sunk in a well. */
.ballot { display: grid; gap: var(--space-4); margin: var(--space-6) 0 var(--space-4); padding: 0; list-style: none; }
.page .ballot { padding-left: 0; }
.page .ballot li + li { margin-top: 0; }
.choice { display: grid; gap: var(--space-4); align-items: start; }
.choice img { width: 192px; height: 144px; }
.choice h3 { margin-bottom: var(--space-1); }
.choice .controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); margin-top: var(--space-3); }
.choice .price { margin-top: 0; font-size: var(--text-base); }
.bar { box-sizing: content-box; height: 8px; margin-top: var(--space-3); border-style: solid; border-image: var(--frame-well); }
.bar { overflow: hidden; }
.bar span { display: block; width: var(--share, 0%); height: 100%; background: var(--sky); }
.count { margin-top: var(--space-1); font-size: var(--text-sm); }
.status { min-height: 1.5em; color: var(--cream); }
button.button, button.linkish { font-family: var(--font-pixel); cursor: pointer; background: none; }
button.button { line-height: 1.4; }
button.button:disabled, button.linkish:disabled { cursor: progress; opacity: .6; }
button.linkish { border: 0; padding: 0; font-size: 20px; color: var(--gold); text-decoration: underline; text-underline-offset: 3px; }
@media (min-width: 640px) {
  .choice { grid-template-columns: 196px 1fr; }
}

/* Motion, after joshwcomeau.com, cut to the pixel grid. Only for those who
   have not asked for less: everything above is the page at rest, and
   motion.js checks the same setting. */

/* A glyph, a step's number or the icon hops when pointed at: two pixels
   up and back, a frame at a time. */
@keyframes hop {
  0%, 80%, 100% { translate: none; }
  20%, 60% { translate: 0 -2px; }
  40% { translate: 0 -4px; }
}

/* The hero arrives in order: heading, words, then the phone. */
@keyframes rise { from { opacity: 0; translate: 0 12px; } }

/* A sparkle on the heading's own grid, a pixel of its face (--px, set by
   motion.js) to each of the sparkle's: a pixel, a cross, a bigger cross,
   and back. The shadows are the pixels round the middle one. */
@keyframes twinkle {
  0%   { background: var(--gold); box-shadow: none; }
  20%, 60% {
    background: var(--cream);
    box-shadow: var(--px) 0 var(--gold), calc(-1 * var(--px)) 0 var(--gold),
                0 var(--px) var(--gold), 0 calc(-1 * var(--px)) var(--gold);
  }
  40% {
    background: var(--cream);
    box-shadow: var(--px) 0 var(--cream), calc(-1 * var(--px)) 0 var(--cream),
                0 var(--px) var(--cream), 0 calc(-1 * var(--px)) var(--cream),
                calc(2 * var(--px)) 0 var(--gold), calc(-2 * var(--px)) 0 var(--gold),
                0 calc(2 * var(--px)) var(--gold), 0 calc(-2 * var(--px)) var(--gold);
  }
  80%  { background: var(--gold); box-shadow: none; }
  100% { background: transparent; box-shadow: none; }
}
.sparkles { position: absolute; inset: 0; pointer-events: none; }
.sparkle { position: absolute; width: var(--px); height: var(--px); animation: twinkle 900ms steps(1, end) both; }

/* Stars thrown up from a counted vote (motion.js burst). */
.bursts { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 10; }
.burst { position: absolute; line-height: 0; }
.burst svg { width: 16px; height: 14px; fill: currentColor; shape-rendering: crispEdges; }

@media (prefers-reduced-motion: no-preference) {
  .button { transition: transform var(--spring-time) var(--spring), filter var(--spring-time) var(--spring); }
  .button:active { transition-duration: 40ms; }

  .site-nav a, .site-footer a { transition: background-size 160ms steps(4, jump-end); }

  .hero h1, .hero .lede, .hero .cta, .hero .phone { animation: rise 700ms var(--settle) both; }
  .hero .lede { animation-delay: 90ms; }
  .hero .cta { animation-delay: 180ms; }
  .hero .phone { animation-delay: 270ms; }

  /* Cards and steps below the fold rise in as they scroll into view.
     motion.js marks them; without it they are simply there. */
  .reveal {
    transition: opacity 400ms ease-out, translate var(--settle-time) var(--settle);
    transition-delay: calc(var(--i, 0) * 80ms);
  }
  .reveal:not(.shown) { opacity: 0; translate: 0 16px; }

  /* The vote's bars grow from where they stood, or from nothing. */
  .bar span { transition: width var(--settle-time) var(--settle); }
  @starting-style { .bar span { width: var(--from, 0%); } }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .card:hover .glyph, .features li:hover .glyph, .steps li:hover::before, .brand:hover img {
    animation: hop 400ms steps(1, end);
  }
}
