/* Rubby's page, in the app's default look: the Rubby theme (the app's
   themes.css), plain black with the duck's yellow, its monospace type, thin
   borders, rounded cards, and Rubby talking in the app's own speech
   bubbles (their tail at his beak). Flat: no shadows, blur or gradient
   fills; just a faint grid behind everything, fading out at the edges.
   Motion is short, and off with reduced motion, as in the app. */

:root {
  color-scheme: dark;
  --bg: #121212;
  --bg-subtle: #0c0c0c;
  --surface: #1a1a1a;
  --bg-input: #202020;
  --bg-hover: #262626;
  --border: #2b2b2b;
  --border-strong: #3a3a3a;
  --fg: #f2f0eb;
  --fg-muted: #b4b4b4;
  --fg-subtle: #8f8f8f;
  --accent: #ffcf4d;
  --accent-hover: #ffd96e;
  --on-accent: #121212;
  --code: #ffa36b;
  --water: #8fc8e0;
  --danger: #ff8a7a;
  --grid: #ffffff0c;
  --font:
    ui-monospace, 'SF Mono', SFMono-Regular, Menlo, 'Cascadia Mono', Consolas, 'Ubuntu Sans Mono', 'Ubuntu Mono',
    'DejaVu Sans Mono', monospace;
  --radius: 12px;
  --motion: 120ms ease-out;
  --fade: 240ms ease-out;
  --gutter: 16px;
}

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

html {
  background: var(--bg);
  scroll-behavior: smooth;
  scroll-padding-top: 72px;
}

/* The page's black is on html, so the grid (on body, behind its content)
   shows above it. */
body {
  position: relative;
  margin: 0;
  color: var(--fg);
  font: 15px/1.6 var(--font);
  -webkit-font-smoothing: antialiased;
}

/* The grid behind everything: square lines, fading out toward all four
   edges of the window. */
body::before {
  content: '';
  position: fixed;
  z-index: -1;
  inset: 0;
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-position: center center;
  background-size: 40px 40px;
  pointer-events: none;
  mask-image:
    linear-gradient(to right, transparent, #000 18%, #000 82%, transparent),
    linear-gradient(to bottom, transparent, #000 18%, #000 82%, transparent);
  mask-composite: intersect;
  -webkit-mask-composite: source-in;
}
body > * {
  position: relative;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: var(--accent);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

h1,
h2,
h3 {
  margin: 0;
  line-height: 1.2;
  font-weight: 700;
}
h2 {
  margin-bottom: 12px;
  font-size: 22px;
}

code,
kbd {
  font: inherit;
  font-size: 0.92em;
}
code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--bg-input);
  color: var(--code);
}
kbd {
  display: inline-block;
  min-width: 1.9em;
  padding: 0 6px;
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: 6px;
  background: var(--bg-input);
  color: var(--fg);
  line-height: 1.6;
  text-align: center;
}

.wrap {
  width: min(1080px, 100% - 2 * var(--gutter));
  margin-inline: auto;
}

/* Buttons, as the app's: quiet surfaces with a thin rim; the one that buys
   is in the duck's yellow. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 42px;
  padding: 0 18px;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background: var(--bg-input);
  color: var(--fg);
  font-weight: 600;
  text-decoration: none;
  transition:
    background var(--motion),
    border-color var(--motion),
    transform var(--motion);
}
.button:hover {
  border-color: var(--fg-subtle);
  background: var(--bg-hover);
  text-decoration: none;
}
.button:active {
  transform: scale(0.97);
}
.button.primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}
.button.primary:hover {
  background: var(--accent-hover);
}
button.button {
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
/* On its way to checkout. */
.button[aria-busy='true'] {
  opacity: 0.7;
  cursor: progress;
}
.button.small {
  min-height: 34px;
  padding: 0 12px;
  font-size: 14px;
}
.button:focus-visible,
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The top bar: the app's tab bar, sticky, solid. */
.top {
  position: sticky;
  z-index: 10;
  top: 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg-subtle);
}
.top-row {
  display: flex;
  align-items: center;
  gap: 20px;
  height: 56px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--fg);
  font-size: 17px;
  font-weight: 700;
}
.brand:hover {
  text-decoration: none;
}
.nav {
  display: flex;
  gap: 18px;
  margin-left: auto;
}
.nav a {
  color: var(--fg-muted);
  font-size: 14px;
}
.nav a:hover {
  color: var(--fg);
  text-decoration: none;
}
/* Signing in (on the account pages) and buying, at the header's end. */
.top-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.top-actions .button {
  white-space: nowrap;
}
/* Without the sections, they keep to the end anyway (legal.html). */
.brand + .top-actions {
  margin-left: auto;
}
/* The speaker: the app's sounds on or off (sounds.js shows it). */
.button.sound-toggle {
  width: 34px;
  padding: 0;
  color: var(--fg-muted);
}
.sound-toggle svg {
  flex: none;
}
.sound-toggle:hover {
  color: var(--fg);
}
.sound-toggle[hidden],
.sound-toggle[aria-pressed='true'] .mute,
.sound-toggle[aria-pressed='false'] .waves {
  display: none;
}

/* Rubby talking: a small card in the app's colours, its tail at his beak.
   When poked he types his line out, each letter fading in (duck.js). */
.say .letter {
  opacity: 0;
  transition: opacity var(--motion);
}
.say .letter.on {
  opacity: 1;
}
.say {
  position: relative;
  max-width: 460px;
  margin: 0;
  padding: 12px 16px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}
.say::before {
  content: '';
  position: absolute;
  top: 22px;
  width: 11px;
  height: 11px;
  background: var(--surface);
  transform: rotate(45deg);
}
.say-left::before {
  left: -6px;
  border-bottom: 1px solid var(--border-strong);
  border-left: 1px solid var(--border-strong);
}
.say-right::before {
  right: -6px;
  border-top: 1px solid var(--border-strong);
  border-right: 1px solid var(--border-strong);
}
.say-down::before {
  top: auto;
  bottom: -6px;
  left: 40px;
  border-right: 1px solid var(--border-strong);
  border-bottom: 1px solid var(--border-strong);
}
/* He and what he says, side by side. */
.talk {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}
.talk .duck {
  flex: none;
  width: 120px;
  margin-top: -6px;
}

/* Screenshots: the app's window, its rounded corners already in the picture. */
.screen {
  width: 277px;
}

/* Hello. */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  align-items: center;
  gap: 48px;
  padding: 80px 0 64px;
}
.hero h1 {
  font-size: clamp(52px, 9vw, 88px);
  letter-spacing: -0.02em;
}
.lede {
  max-width: 34ch;
  margin: 14px 0 26px;
  color: var(--fg-muted);
  font-size: 18px;
}
.cta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.platforms {
  margin: 14px 0 0;
  color: var(--fg-subtle);
  font-size: 13px;
}
/* The app, and Rubby to its right, a little apart, saying hello from under
   his words: centred under them, their tail pointing down at him. Where
   it's tight, the gap closes up and his words narrow to his width. */
.hero-art {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(12px, 5vw - 30px, 40px);
}
.hero-talk {
  flex: 0 1 auto;
  flex-direction: column;
  align-items: center;
  width: 240px;
}
.hero-talk .say {
  font-size: 14px;
}
.hero-talk .say-down::before {
  left: calc(50% - 6px);
}
/* Live Rubby (duck.js): a button to poke him, his picture until he's loaded. */
.live-duck {
  position: relative;
  display: grid;
  place-items: center;
  width: 221px;
  height: 221px;
  margin: 2px 0 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  cursor: pointer;
}
.live-duck .duck {
  width: 166px;
  margin: 0;
}
.live-duck canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.live-duck.live .duck {
  visibility: hidden;
}

/* The tour: Rubby on one side, the app on the other, swapping sides. */
.tour {
  padding: 8px 0 24px;
}
.chapter {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 48px;
  padding: 56px 0;
  border-top: 1px solid var(--border);
}
.chapter.flip .talk {
  order: 2;
  flex-direction: row-reverse;
}
.chapter.flip .talk h2 {
  text-align: right;
}
.shots,
.clip {
  display: flex;
  justify-content: center;
  gap: 24px;
  margin: 0;
}
/* Two side by side share the column rather than spill out of it. */
.shots .screen {
  min-width: 0;
}
.clip img {
  width: 277px;
}
/* The bubble's clip, on a plain desktop: the window shrinks into its corner. */
.clip.desktop img {
  width: 300px;
  padding: 0;
}
.clip.desktop {
  justify-self: center;
  width: 324px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-subtle);
}

/* Themes: the picker, and Rubby in the one picked (main.js). */
.themes {
  padding: 64px 0;
  border-top: 1px solid var(--border);
}
.themes-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 48px;
}
.theme-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 28px 0 0;
}
.theme-picker button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  padding: 0 12px 0 8px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--bg-input);
  color: var(--fg-muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition:
    border-color var(--motion),
    color var(--motion);
}
.theme-picker button::before {
  content: '';
  width: 16px;
  height: 16px;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: radial-gradient(circle, var(--dot) 0 3px, var(--swatch) 3.5px);
}
/* Each theme's swatch: its background, and a dot of its accent. */
.theme-picker [data-theme='rubby'] {
  --swatch: #121212;
  --dot: #ffcf4d;
}
.theme-picker [data-theme='gadwall'] {
  --swatch: #24292e;
  --dot: #79b8ff;
}
.theme-picker [data-theme='smew'] {
  --swatch: #ffffff;
  --dot: #0366d6;
}
.theme-picker [data-theme='teal'] {
  --swatch: #121a28;
  --dot: #7aa7ff;
}
.theme-picker [data-theme='mallard'] {
  --swatch: #20271f;
  --dot: #9fd18b;
}
.theme-picker [data-theme='mandarin'] {
  --swatch: #282828;
  --dot: #fabd2f;
}
.theme-picker [data-theme='pekin'] {
  --swatch: #fbf6e6;
  --dot: #2a5db0;
}
.theme-picker [data-theme='cayuga'] {
  --swatch: #24221e;
  --dot: #8fb6e8;
}
.theme-picker [data-theme='blue-swedish'] {
  --swatch: #174580;
  --dot: #c4e6ff;
}
.theme-picker [data-theme='harlequin'] {
  --swatch: #0e2240;
  --dot: #8fc7ff;
}
.theme-picker button:hover {
  color: var(--fg);
}
.theme-picker button[aria-checked='true'] {
  border-color: var(--accent);
  color: var(--fg);
}
.themes .shots .screen {
  transition: opacity var(--motion);
}
.themes .shots .screen.swapping {
  opacity: 0;
}

/* Keys and the small print. */
.facts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  padding: 40px 0 64px;
}
.fact {
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.fact p {
  margin: 0 0 12px;
  color: var(--fg-muted);
}
.keys {
  display: grid;
  gap: 8px;
  margin: 0 0 14px;
}
.keys div {
  display: grid;
  grid-template-columns: 150px 1fr;
  align-items: center;
}
.keys dt,
.keys dd {
  margin: 0;
}
.keys dt {
  white-space: nowrap;
}
.note {
  font-size: 13px;
}

/* Buying. */
.download {
  padding: 64px 0 80px;
  border-top: 1px solid var(--border);
}
.systems {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin: 36px 0 0;
  padding: 0;
  list-style: none;
}
.system {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.system h3 {
  font-size: 18px;
}
.system p {
  margin: 0;
  color: var(--fg-muted);
  font-size: 14px;
}
/* Each card's Buy button fills its width. */
.system-action {
  margin: 14px 0 0;
}
.system-action .button {
  width: 100%;
}
.system:not(.yours) .system-action .button {
  border-color: var(--border-strong);
  background: var(--bg-input);
  color: var(--fg);
}
.system:not(.yours) .system-action .button:hover {
  background: var(--bg-hover);
}
/* The visitor's own system, picked out. */
.system.yours {
  border-color: var(--accent);
}
.system.yours::after {
  content: 'Your system';
  position: absolute;
  top: 18px;
  right: 18px;
  padding: 1px 8px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  color: var(--accent);
  font-size: 12px;
}
/* Why checkout sent the buyer back, if it did. */
.checkout-note {
  min-height: 1.6em;
  margin: 18px 0 0;
  color: var(--code);
  font-size: 14px;
  text-align: center;
}
.checkout-note:empty {
  margin-top: 0;
}
.price-note {
  margin: 18px 0 0;
  color: var(--fg-subtle);
  font-size: 13px;
  text-align: center;
}
.price-note + .price-note {
  margin-top: 6px;
}
/* A button that reads as a link: the switch between pesos and dollars. */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
}
.link-button:hover {
  text-decoration: underline;
}

.foot {
  padding: 28px 0;
  border-top: 1px solid var(--border);
  background: var(--bg-subtle);
  color: var(--fg-subtle);
  font-size: 13px;
}
.foot-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px 24px;
}
.foot p {
  margin: 0;
}

/* His words pop in as they scroll into view (main.js), as his speech
   bubble does in the app. */
.js .say {
  opacity: 0;
  transform: translateY(6px) scale(0.98);
  transition:
    opacity var(--fade),
    transform var(--fade);
}
.js .say.shown {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .js .say {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .button,
  .theme-picker button,
  .themes .shots .screen {
    transition: none;
  }
}

/* The language switch: the other language, by name, in the header. */
.lang-switch {
  color: var(--fg-muted);
  font-size: 14px;
  white-space: nowrap;
}
.lang-switch:hover {
  color: var(--fg);
}

/* Above an app's name: which family it belongs to. */
.eyebrow {
  margin: 0 0 4px;
  font-size: 14px;
}
.eyebrow a {
  color: var(--fg-muted);
}

/* The hub: Rubby on his own beside the words, then sections of cards. */
.hub-hero {
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
}
.hub-section {
  padding: 48px 0;
}
.section-intro {
  max-width: 62ch;
  margin-bottom: 28px;
}
.section-intro h2 {
  margin: 0 0 8px;
}
.section-intro p {
  margin: 0;
  color: var(--fg-muted);
}

/* The apps: a card each (a screenshot beside its words), then what's next. */
.apps {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 20px;
}
.app-card {
  display: flex;
  gap: 28px;
  padding: 28px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.app-shot {
  flex: none;
}
.app-shot img {
  display: block;
  width: 180px;
  height: auto;
}
.app-body h3 {
  margin: 0 0 8px;
  font-size: 24px;
}
.app-body h3 a {
  color: var(--fg);
}
.app-body p {
  margin: 0 0 12px;
  color: var(--fg-muted);
}
.app-body .tag-line {
  margin: 0 0 4px;
  color: var(--accent);
  font-size: 13px;
}
.ticks {
  display: grid;
  gap: 4px;
  margin: 0 0 14px;
  padding: 0;
  list-style: none;
  color: var(--fg-muted);
  font-size: 14px;
}
.ticks li::before {
  content: '✓  ';
  color: var(--accent);
}
.app-body .price-line {
  color: var(--fg);
}
.app-card.soon {
  flex-direction: column;
  align-items: flex-start;
  border-style: dashed;
  background: transparent;
}
.app-card.soon .duck {
  width: 96px;
  height: auto;
}

/* Cards: the account, the tools, security. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.card-item {
  padding: 20px 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.card-item h3 {
  margin: 0 0 6px;
  font-size: 16px;
}
.card-item p {
  margin: 0;
  color: var(--fg-muted);
  font-size: 14px;
}
.hub-section .more {
  margin: 20px 0 0;
}
.security-talk {
  margin-bottom: 28px;
}

/* Narrow screens: one column, Rubby above what he's showing. The hero goes
   to one column first, where the app and Rubby no longer fit beside its
   words. */
@media (max-width: 1020px) {
  .hero {
    grid-template-columns: 1fr;
    gap: 40px;
    padding-top: 48px;
  }
}
@media (max-width: 860px) {
  .chapter,
  .facts,
  .themes-row,
  .apps {
    grid-template-columns: 1fr;
  }
  .app-card {
    flex-direction: column;
  }
  .app-shot img {
    width: min(220px, 100%);
  }
  .chapter,
  .themes-row {
    gap: 28px;
  }
  .chapter {
    padding: 44px 0;
  }
  .chapter.flip .talk {
    order: 0;
  }
  .systems {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 600px) {
  .nav {
    display: none;
  }
  .top-row {
    gap: 12px;
  }
  .top-actions {
    margin-left: auto;
  }
  /* On a phone Rubby says hello under the app, his words beside him. */
  .hero-art {
    flex-direction: column;
    align-items: center;
    gap: 20px;
  }
  .hero-talk {
    flex-direction: row;
    align-items: flex-end;
    width: auto;
    max-width: 100%;
  }
  .hero-talk .say {
    margin-bottom: 40px;
  }
  .hero-talk .say::before {
    right: -6px;
    bottom: 18px;
    left: auto;
    border-top: 1px solid var(--border-strong);
    border-bottom: 0;
  }
  .live-duck {
    flex: none;
    width: 156px;
    height: 156px;
  }
  .live-duck .duck {
    width: 125px;
  }
  .talk .duck {
    width: 84px;
  }
  .shots {
    flex-wrap: wrap;
  }
  .shots .screen + .screen {
    display: none;
  }
  .keys div {
    grid-template-columns: 140px 1fr;
  }
  .clip.desktop {
    width: min(324px, 100%);
  }
}

/* The smallest phones: the header keeps Rubby's face and the buttons on one
   line; his name stays for screen readers. */
@media (max-width: 420px) {
  /* The header's Buy: the hero's is right below it. */
  .top-actions .button.primary {
    display: none;
  }
  .brand span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* Contact, refunds and terms: plain reading text. */
.legal {
  max-width: 760px;
  padding: 56px 0 80px;
}
.legal h1 {
  margin-bottom: 8px;
  font-size: 28px;
}
.legal section {
  margin-top: 36px;
}
.legal p,
.legal li {
  color: var(--fg-muted);
}
.legal li {
  margin-bottom: 10px;
}
.legal strong {
  color: var(--fg);
}

/* The 404 page: Rubby, lost. */
.missing {
  padding: 96px 0;
}
.missing h1 {
  margin: 0 0 12px;
}

/* Six cards read as two rows of three. */
.cards.tools,
.cards.secure {
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
