/* Visor download site. The look is borrowed from the app it distributes:
   near-black glass surfaces, a monospace face for everything, Anthropic coral
   as the one accent, and the app's meaning colours used only where they carry
   the same meaning. Light and dark both derive from the custom properties
   below and follow the system through prefers-color-scheme. Nothing external
   loads: the monospace stack is all system fonts. */

:root {
  /* Surfaces and text. Dark is the default. */
  --bg-base: #08080b;
  --bg-glow: #15151c;
  --panel-bg: rgba(255, 255, 255, 0.035);
  --panel-border: rgba(255, 255, 255, 0.10);
  --top-highlight: rgba(255, 255, 255, 0.11);

  --fg: rgba(255, 255, 255, 0.92);
  --dim: rgba(235, 235, 245, 0.62);
  --faint: rgba(235, 235, 245, 0.45);

  /* The notch silhouette's own glass fill and resting edge, straight from the
     app: pill-bg and the beam hairline. */
  --pill-bg: rgba(0, 0, 0, 0.88);
  --beam-hairline: rgba(255, 255, 255, 0.22);

  /* Accents. Coral is the primary and is identical in both themes. The others
     are meaning colours: amber for attention or a caveat, green for a
     positive, ultraviolet for the voice section, orange for a session count.
     Amber and green shift darker in light mode so they stay legible, matching
     what the app does. */
  --accent: rgb(217, 120, 87);
  --accent-ink: #180d07;
  --accent-soft: rgba(217, 120, 87, 0.14);
  --accent-line: rgba(217, 120, 87, 0.5);

  --amber: rgb(255, 178, 36);
  --amber-line: rgba(255, 178, 36, 0.4);
  --amber-fill: rgba(255, 178, 36, 0.07);

  --green: rgb(48, 209, 88);
  --voice: rgb(170, 110, 255);
  --voice-pure: rgb(127, 0, 255);
  --voice-line: rgba(127, 0, 255, 0.45);
  --voice-fill: rgba(127, 0, 255, 0.08);
  --count: rgb(255, 159, 10);

  --mono: "Cascadia Mono", Consolas, ui-monospace, monospace;
}

/* Light theme, reachable two ways.
 *
 * The media query is scoped to :root:not([data-theme]) so it applies only while
 * nobody has chosen. The moment the switcher writes data-theme, the system
 * preference stops being consulted at all: data-theme="dark" then simply falls
 * through to the :root defaults above, and data-theme="light" takes the block
 * below. That is why there is no [data-theme="dark"] rule; adding one would be
 * a second copy of the dark values with nothing to do.
 *
 * The light values are written twice, and there is no way around that in plain
 * CSS: a media query and an attribute selector cannot share one rule body. THE
 * TWO BLOCKS MUST BE EDITED TOGETHER. If they drift, the site looks one way
 * before you touch the switcher and another way after, which is a bug nobody
 * finds by looking at either block alone.
 *
 * Keeping the media query at all is deliberate: it means the site still follows
 * the system with JavaScript off, where the switcher cannot run. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --bg-base: #eceef3;
    --bg-glow: #ffffff;
    --panel-bg: rgba(255, 255, 255, 0.7);
    --panel-border: rgba(0, 0, 0, 0.10);
    --top-highlight: rgba(255, 255, 255, 0.9);

    --fg: rgba(0, 0, 0, 0.88);
    --dim: rgba(0, 0, 0, 0.58);
    --faint: rgba(0, 0, 0, 0.42);

    --pill-bg: rgba(255, 255, 255, 0.88);
    --beam-hairline: rgba(0, 0, 0, 0.22);

    --accent-soft: rgba(217, 120, 87, 0.16);

    --amber: rgb(166, 106, 0);
    --amber-line: rgba(166, 106, 0, 0.45);
    --amber-fill: rgba(255, 178, 36, 0.14);

    --green: rgb(22, 130, 58);
    --voice: rgb(108, 0, 216);
    --count: rgb(178, 96, 0);
  }
}

/* The same values, applied when the switcher has been set to light. Keep in
   sync with the block above. */
:root[data-theme="light"] {
  --bg-base: #eceef3;
  --bg-glow: #ffffff;
  --panel-bg: rgba(255, 255, 255, 0.7);
  --panel-border: rgba(0, 0, 0, 0.10);
  --top-highlight: rgba(255, 255, 255, 0.9);

  --fg: rgba(0, 0, 0, 0.88);
  --dim: rgba(0, 0, 0, 0.58);
  --faint: rgba(0, 0, 0, 0.42);

  --pill-bg: rgba(255, 255, 255, 0.88);
  --beam-hairline: rgba(0, 0, 0, 0.22);

  --accent-soft: rgba(217, 120, 87, 0.16);

  --amber: rgb(166, 106, 0);
  --amber-line: rgba(166, 106, 0, 0.45);
  --amber-fill: rgba(255, 178, 36, 0.14);

  --green: rgb(22, 130, 58);
  --voice: rgb(108, 0, 216);
  --count: rgb(178, 96, 0);
}

* { box-sizing: border-box; }

/* The theme switcher. Fixed to the top right, clear of the notch silhouette
   which owns the top centre. z-index 20 puts it over the notch, because the
   notch is decoration and this takes clicks.

   Its own colours come from the same tokens as everything else, so it restyles
   itself the instant a choice is made and never needs a second definition. */
.theme-switch {
  position: fixed;
  top: 14px;
  right: 16px;
  z-index: 20;
  display: flex;
  gap: 2px;
  padding: 4px;
  border-radius: 999px;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  box-shadow: 0 1px 0 var(--top-highlight) inset;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.theme-btn {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--faint);
  cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease;
}

.theme-btn svg { width: 15px; height: 15px; }

.theme-btn:hover { color: var(--fg); }

/* The chosen one. Coral, the page's only accent, so the switcher agrees with
   the download button about what "this is the active thing" looks like. */
.theme-btn[aria-pressed="true"] {
  color: var(--accent);
  background: var(--accent-soft);
}

.theme-btn:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
}

@media (max-width: 620px) {
  .theme-switch { top: 10px; right: 10px; padding: 3px; }
  .theme-btn { width: 27px; height: 27px; }
}

/* The hidden attribute has to win over any display an element is given
   elsewhere, otherwise a control that JavaScript has not enabled yet still
   renders. The copy buttons in the tip jar depend on this. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background:
    radial-gradient(1200px 720px at 50% -12%, var(--bg-glow) 0%, rgba(0, 0, 0, 0) 58%),
    var(--bg-base);
  background-attachment: fixed;
  color: var(--fg);
  font-family: var(--mono);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* The notch silhouette hanging from the top edge, drawn as the same open-walled
   outline the app builds: concave fillets at the top, slanted walls, a rounded
   bottom. The body is black glass, and a coral segment travels the lit edge. */
.notch {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 380px;
  max-width: 62vw;
  height: auto;
  z-index: 10;
}

.notch-body { fill: var(--pill-bg); }

.notch-highlight {
  stroke: var(--top-highlight);
  stroke-width: 1.2;
}

.notch-hairline {
  fill: none;
  stroke: var(--beam-hairline);
  stroke-width: 1;
}

/* A single lit segment that travels the outline, the way the app runs a beam
   around the notch edge. pathLength normalises the outline to 100 units so the
   dash maths is independent of the drawn size. */
.notch-beam {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 16 84;
  stroke-dashoffset: 0;
  filter: drop-shadow(0 0 3px rgba(217, 120, 87, 0.7));
  animation: beam-travel 4.5s linear infinite;
}

@keyframes beam-travel {
  to { stroke-dashoffset: -100; }
}

main {
  max-width: 860px;
  margin: 0 auto;
  padding: 140px 24px 40px;
}

.hero { text-align: center; }

/* The h1 is the logo now, Visor stacked over the wordmark, so it sizes itself
   rather than carrying type rules. This also fixes a real overflow: the old
   rule clamped the type to a 52px floor, and the name set at 52px was wider
   than a 320px viewport minus the page padding, so the narrowest phones
   scrolled sideways by about 8px. Artwork has no floor. It scales to whatever
   width it is given, so the bug cannot come back at any width or any name. */
/* A plain text heading, which on this site means the download page. It lost
   every type rule it had when the homepage h1 became h1.logo, and fell back to
   the browser default of 2em, so "Download" rendered at 32px under a hero built
   for something much larger. Scoped to h1 rather than to a class because any
   future plain h1 wants this too; h1.logo overrides what it needs below. */
h1 {
  font-size: clamp(34px, 6vw, 48px);
  letter-spacing: -0.03em;
  margin: 0;
}

h1.logo {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

/* The width goes on the <picture>, not on the <img> inside it, and that is the
   whole trick. A <picture> has no intrinsic size of its own, so as a flex item
   it sizes to its content: the image's intrinsic width, 664px for the Visor
   SVG. Putting "width: min(100%, 208px)" on the image then resolved that 100%
   against 664px, so the image drew 208px wide at the LEFT edge of a 664px box
   and the mark sat hard left while everything else was centred. Sizing the
   picture and letting the image fill it removes the second box entirely.

   The wordmark never showed the bug because its intrinsic 246px is under its
   own 360px cap, so the two boxes happened to agree. It gets the same treatment
   anyway: it was one asset swap away from the identical fault.

   Intrinsic width and height stay on the <img> tags so the page reserves the
   right box before either file arrives and the hero does not jump. */
h1.logo picture {
  display: block;
}

h1.logo img {
  display: block;
  width: 100%;
  height: auto;
}

.visor { width: min(100%, 208px); }
.logo-word { width: min(100%, 360px); }

.tagline {
  font-size: clamp(19px, 3.2vw, 28px);
  color: var(--dim);
  margin: 14px 0 0;
}

.lede {
  max-width: 620px;
  margin: 28px auto 0;
  font-size: 17px;
  color: var(--dim);
}

.cta {
  margin-top: 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.download {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--mono);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.01em;
  text-decoration: none;
  padding: 14px 30px;
  border-radius: 12px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2) inset;
  transition: transform 0.12s ease, filter 0.12s ease;
}

.download:hover { transform: translateY(-1px); filter: brightness(1.06); }
.download:focus-visible { outline: 3px solid var(--fg); outline-offset: 3px; }

.meta { color: var(--faint); font-size: 14px; }

/* Sits directly under the download button on the download page, in amber
   because it is a caveat rather than a failure: the page is not broken, it just
   is not for the device you are holding. */
.note-mobile {
  max-width: 460px;
  margin: 18px auto 0;
  padding: 10px 14px;
  border: 1px solid var(--amber-line);
  background: var(--amber-fill);
  border-radius: 10px;
  color: var(--amber);
  font-size: 14px;
}

/* A 64 character hash has to be able to break anywhere, or it decides the
   page's minimum width and every phone scrolls sideways. */
.checksum {
  max-width: 620px;
  margin: 10px auto 0;
}

.checksum code {
  display: block;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--dim);
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 10px 12px;
  overflow-wrap: anywhere;
  word-break: break-all;
  user-select: all;
  -webkit-user-select: all;
}

/* A positive, used sparingly: the price. */
.ok { color: var(--green); }

a.back {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid var(--accent-line);
}
a.back:hover { border-bottom-color: var(--accent); }

/* Glass panels: near-black in dark, white glass in light, both with a faint
   top highlight drawn as an inset hairline. */
.feature,
.voice-group,
.voice-table,
.voice-availability,
.download-band {
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  box-shadow: 0 1px 0 var(--top-highlight) inset;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* Two per row, not three. With four cards a 220px minimum gives three across
   and one stranded on its own line, which reads as a card that failed to load
   rather than as a deliberate layout. A 300px minimum cannot fit three inside
   the 812px content column (3 x 300 + 2 x 28 of gap is 956), so it settles on
   two, and drops to one below about 620px of viewport. No media query needed:
   the minimum does the work, and it keeps working if a fifth card is added. */
.features {
  margin-top: 110px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 28px;
}

.feature {
  border-radius: 16px;
  padding: 26px;
}

.feature h2 { margin: 0 0 10px; font-size: 18px; letter-spacing: -0.01em; }
.feature p { margin: 0; color: var(--dim); font-size: 15px; }

/* The changelog preview on the homepage: a compact what's-new box between the
   features and the voice section. It is deliberately plainer than the feature
   cards, because it is a ledger, not a pitch. */
.changelog {
  margin-top: 110px;
  max-width: 720px;
  margin-left: auto;
  margin-right: auto;
  border-top: 2px solid var(--accent-line);
  padding-top: 36px;
  text-align: center;
}

.changelog h2 {
  font-size: 28px;
  letter-spacing: -0.02em;
  margin: 0 0 12px;
}

.changelog-lede {
  margin: 0 auto 28px;
  max-width: 560px;
  color: var(--dim);
  font-size: 17px;
}

.changelog-list {
  margin: 0 auto;
  padding: 0;
  list-style: none;
  max-width: 620px;
  text-align: left;
  display: grid;
  gap: 14px;
}

.changelog-list li {
  padding-left: 18px;
  position: relative;
  color: var(--dim);
  font-size: 15px;
  line-height: 1.55;
}

.changelog-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

.changelog-list strong {
  color: var(--fg);
  font-weight: 600;
}

.changelog-more {
  margin: 28px 0 0;
}

.changelog-more a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid var(--accent-line);
}

.changelog-more a:hover { border-bottom-color: var(--accent); }

.states {
  margin: 72px 0 0;
  text-align: center;
}

.states img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 14px;
}

.states figcaption {
  margin-top: 16px;
  font-size: 15px;
  color: var(--dim);
}

.voice {
  margin-top: 110px;
  text-align: center;
}

/* The whole section is one honest sentence until voice input actually exists,
   so it is set large enough to be the point of the section rather than a note
   under a heading. Amber because it is a caveat, the same amber the app uses
   for a question waiting on you. */
.voice-soon {
  margin: 18px 0 0;
  font-size: clamp(30px, 5vw, 46px);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--amber);
}

.voice > h2 {
  font-size: 28px;
  letter-spacing: -0.02em;
  margin: 0 0 12px;
  text-align: center;
}

.voice-lede {
  max-width: 620px;
  margin: 0 auto 40px;
  text-align: center;
  color: var(--dim);
  font-size: 17px;
}

.voice-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 24px;
}

/* The voice section carries the app's voice colour, ultraviolet, on its own
   accents. */
.voice-group {
  border-radius: 16px;
  padding: 22px 24px;
  border-top: 2px solid var(--voice-line);
}

.voice-group h3 {
  margin: 0 0 8px;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--voice);
}

.voice-group p { margin: 0; color: var(--dim); font-size: 15px; }

.voice-table {
  width: 100%;
  margin-top: 40px;
  border-collapse: collapse;
  border-radius: 16px;
  overflow: hidden;
}

.voice-table caption {
  text-align: left;
  color: var(--dim);
  font-size: 13px;
  padding: 16px 20px 0;
}

.voice-table th,
.voice-table td {
  text-align: left;
  padding: 14px 20px;
  border-top: 1px solid var(--panel-border);
  font-size: 15px;
  vertical-align: top;
}

.voice-table thead th {
  border-top: none;
  color: var(--fg);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.voice-table td:first-child { white-space: normal; width: 42%; }

/* On a phone this table is wider than the screen and used to push the whole
   page sideways. Below this width the phrase chips wrap, the cells sit tighter,
   and whatever width is still left over scrolls inside the table's own box
   rather than the page's. Nothing changes above it. */
@media (max-width: 620px) {
  .voice-table {
    display: block;
    overflow: auto;
  }

  .voice-table code { white-space: normal; }

  .voice-table th,
  .voice-table td { padding: 12px 14px; }
}
.voice-table td { color: var(--dim); }

.voice-table code {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: 6px;
  padding: 1px 6px;
  white-space: nowrap;
}

/* The availability note is a caveat: part of it is still being wired up. That
   is attention, so it takes amber. */
.voice-availability {
  margin-top: 32px;
  padding: 20px 24px;
  border-left: 3px solid var(--amber);
  background: var(--amber-fill);
  border-radius: 12px;
  color: var(--dim);
  font-size: 15px;
}

.download-band {
  margin-top: 110px;
  text-align: center;
  padding: 60px 24px;
  border-radius: 20px;
}

.download-band h2 { font-size: 28px; letter-spacing: -0.02em; margin: 0 0 8px; }

/* The horizontal margin is auto, not 0, and that is load-bearing rather than
   decorative. This selector is a class plus an element, so it outranks a plain
   .class rule on any paragraph inside the band no matter which is written
   later. With 0 here, a narrower paragraph such as .support had its own
   "margin: ... auto" silently overridden and sat left-aligned in the band while
   its text centred inside itself, which reads as a mysterious 161px offset
   rather than as a specificity problem. auto costs nothing on a full-width
   paragraph, where there is no free space to distribute. */
.download-band p { color: var(--dim); margin: 0 auto 28px; }

/* A quiet personal aside sitting under the download button, not a banner:
   smaller and dimmer than the download copy, in the faint text token. The words
   "tip jar" are coral but are not a link any more: the jar itself sits directly
   underneath, so there is nowhere to send a click. */
.download-band .support {
  max-width: 440px;
  margin: 24px auto 0;
  color: var(--faint);
  font-size: 13px;
  line-height: 1.6;
}

.support .tip { color: var(--accent); }

/* The tip jar. Still understated, still below the download button, and narrow
   enough that it reads as a footnote to the aside above rather than a second
   call to action. */
.tipjar {
  max-width: 440px;
  margin: 14px auto 0;
  padding: 16px 18px;
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  background: var(--panel-bg);
  box-shadow: 0 1px 0 var(--top-highlight) inset;
  /* Centred so the chain labels line up with the card block leading the jar.
     The addresses themselves stay left-aligned, set on .tipjar-address: a
     44-character string that breaks mid-word looks broken when its last line is
     centred under the first, and an address is read character by character to
     check it, which wants a straight left edge. */
  text-align: center;
}

/* Two chains, presented as a pair. The separator keeps them visibly distinct so
   the label above each address is never read against the wrong one. */
.tipjar-chain + .tipjar-chain {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--panel-border);
}

/* Named explicitly so nobody sends the wrong asset to the wrong chain. */
.tipjar-label {
  margin: 0 0 8px;
  color: var(--faint);
  font-size: 12px;
  letter-spacing: 0.02em;
}

.tipjar-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
}

/* The address is the point of the block, so it keeps full contrast while the
   text around it stays faint. It wraps inside its own box on a narrow screen
   instead of pushing the page sideways, and one click selects the whole string
   because a partial wallet address is worse than none. */
.tipjar-address {
  flex: 1 1 260px;
  min-width: 0;
  max-width: 100%;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: 8px;
  padding: 8px 10px;
  overflow-wrap: anywhere;
  word-break: break-all;
  text-align: left;
  user-select: all;
  -webkit-user-select: all;
}

.tipjar-copy {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent-line);
  border-radius: 8px;
  padding: 8px 12px;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.tipjar-copy:hover { background: var(--accent-soft); }
.tipjar-copy:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }

/* Visible confirmation that the copy actually happened, in the same green the
   page uses for anything that went right. */
.tipjar-copy.is-copied {
  color: var(--green);
  border-color: var(--green);
  background: transparent;
}

/* The card option leads the jar now, so it takes a rule underneath rather than
   above: the separator belongs between it and the addresses either way, and a
   border-top on the first child would draw a stray line against the jar's own
   inside edge. */
/* Padding on both sides of the content, not just below it. With padding only on
   the bottom the button sat hard against the jar's top edge and floated in the
   space above the rule, which reads as a block that failed to centre rather than
   as a deliberate lead item. Equal padding gives it the same air top and bottom
   and centres it in its own band. */
.tipjar-card {
  margin-bottom: 16px;
  padding: 10px 0 20px;
  border-bottom: 1px solid var(--panel-border);
  text-align: center;
}

.tipjar-card-note {
  margin: 8px 0 0;
  color: var(--faint);
  font-size: 12px;
}

/* The one thing in this block that is meant to be clicked. It deliberately does
   not get the tinted box the addresses sit in, because that box means "select
   and copy me" here. It reads as a link instead: coral, underlined, and slightly
   larger than the labels around it. The underline is drawn on the accent line in
   both themes and fills in to solid accent on hover, so it is visibly a link at
   rest without shouting. */
/* A filled button, not an underlined link. This is the option most visitors can
   actually act on, and it was previously the quietest thing in the block, which
   had the prominence exactly backwards. It borrows the download button's shape
   at a smaller size rather than inventing a third button style, so it reads as
   "an action" without competing with the download above it.

   It does not take the tinted box the addresses use, because that box means
   "select and copy me" everywhere else on this page. */
.tipjar-card-link {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--mono);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.01em;
  text-decoration: none;
  padding: 11px 22px;
  border-radius: 10px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2) inset;
  transition: transform 0.12s ease, filter 0.12s ease;
}

.tipjar-card-link:hover { transform: translateY(-1px); filter: brightness(1.06); }
.tipjar-card-link:focus-visible { outline: 3px solid var(--fg); outline-offset: 3px; }

footer {
  text-align: center;
  color: var(--faint);
  font-size: 13px;
  padding: 40px 24px 60px;
}

footer p { margin: 0; }

/* Social links. Icon only, because the two glyphs are more recognisable than
   the words and a row of labelled buttons would read as a call to action next
   to copy that is deliberately quiet.
 *
 * The accessible name is carried by a visually-hidden span rather than by
 * aria-label, so it survives translation and is what a screen reader announces
 * either way. The SVGs are inline and drawn in currentColor, so one file serves
 * light and dark and no request is made for a 400-byte icon.
 *
 * 40px is the box, not the glyph. The glyph is 18px; the rest is hit target,
 * which is what makes these usable on a phone where they sit at the very foot
 * of the page and a miss means scrolling back. */
.social {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 18px 0 0;
}

.social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  color: var(--faint);
  border: 1px solid transparent;
  transition: color 140ms ease, background-color 140ms ease, border-color 140ms ease;
}

.social svg {
  width: 18px;
  height: 18px;
  display: block;
}

.social a:hover,
.social a:focus-visible {
  color: var(--fg);
  background: var(--panel-bg);
  border-color: var(--panel-border);
}

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

@media (prefers-reduced-motion: reduce) {
  .download,
  .tipjar-copy,
  .tipjar-card-link { transition: none; }
  /* Stop the travelling beam; a single lit segment rests near the top edge. */
  .notch-beam { animation: none; }
}

/* The waitlist. Same glass panel as the feature cards and the download band, so
   it reads as part of the page rather than an embedded widget. */
.waitlist {
  margin-top: 110px;
  padding: 48px 24px;
  border-radius: 20px;
  text-align: center;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  box-shadow: 0 1px 0 var(--top-highlight) inset;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.waitlist h2 { margin: 0 0 10px; font-size: 28px; letter-spacing: -0.02em; }

.waitlist-lede {
  max-width: 520px;
  margin: 0 auto 26px;
  color: var(--dim);
  font-size: 15px;
}

/* Row on a desktop, stack on a phone. wrap does the work rather than a media
   query, so it breaks at whatever width the content actually needs. */
.waitlist-form {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 0 auto;
  max-width: 520px;
}

.waitlist-form input[type="email"] {
  flex: 1 1 260px;
  min-width: 0;
  font-family: var(--mono);
  font-size: 15px;
  color: var(--fg);
  background: var(--bg-base);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 13px 14px;
}

.waitlist-form input[type="email"]::placeholder { color: var(--faint); }

.waitlist-form input[type="email"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent-line);
}

/* Borrows the download button's shape, one size down, so the page has one idea
   of what an action looks like. */
.waitlist-form button {
  flex: 0 0 auto;
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--mono);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
  border: 0;
  border-radius: 10px;
  padding: 13px 24px;
  cursor: pointer;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2) inset;
  transition: transform 0.12s ease, filter 0.12s ease;
}

.waitlist-form button:hover { transform: translateY(-1px); filter: brightness(1.06); }
.waitlist-form button:focus-visible { outline: 3px solid var(--fg); outline-offset: 3px; }
.waitlist-form button[disabled] { opacity: 0.55; cursor: not-allowed; transform: none; filter: none; }

.waitlist-note {
  margin: 16px 0 0;
  color: var(--faint);
  font-size: 13px;
}

/* Outcome states. Green for done and amber for a problem, the same meanings the
   rest of the page and the app itself use for those two colours. */
.waitlist-note.is-done { color: var(--green); }
.waitlist-note.is-error { color: var(--amber); }

/* Available to screen readers, invisible on screen. Used for the email label,
   which the placeholder alone cannot replace, and for the honeypot, which must
   be reachable by a bot but never announced to a person. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
