/*
 * dnsbutton.dev
 *
 * The design is deliberately the same as dnsleak.dev's: same palette, same
 * neutrals, same shell width, same three radii, same prose treatment. They are
 * sister products from the same company and should look it.
 *
 * This is dnsbutton.dev's own copy of that design, not a shared one. The
 * workspace rule is that repos never import code, assets, brand or copy from
 * each other — when two need the same thing, each keeps its own copy. So if a
 * token changes over there, it changes here too, by hand, and nothing silently
 * breaks in the meantime.
 *
 * Plain CSS on purpose. dnsleak.dev is a Next.js app because it has a live test
 * to serve; this is three static pages inside a Swift repository, and adding a
 * node toolchain next to build.sh would be a dependency bought for nothing.
 */

/* Self-hosted, because the privacy policy on this site says no request leaves
   for a third party and a Google Fonts link would make that false. */
@font-face {
  font-family: 'Geist';
  src: url('fonts/Geist-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Geist';
  src: url('fonts/Geist-Medium.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: 'Geist';
  src: url('fonts/Geist-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('fonts/GeistMono-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

:root {
  --font-sans: 'Geist', ui-sans-serif, system-ui, sans-serif;
  --font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, monospace;

  /* The menu bar and menu are drawings of macOS, so they are set in macOS's
     own font. `-apple-system` resolves to SF Pro on any Apple device — which
     is every machine this app runs on — and costs nothing to load. Shipping
     the SF Pro webfont instead would be both a 100 KB download for a
     decorative block and a licence this site does not have; elsewhere the
     visitor's own UI font is the right stand-in for system chrome anyway. */
  --font-system: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI',
                 system-ui, sans-serif;

  --color-canvas: #ffffff;
  --color-surface: #f6f8fa;
  --color-border: #e4e8ed;
  --color-ink: #22262e;
  --color-body: #4a5058;
  /* #7b828c measured 3.88:1 on white — under AA for the 13–14px it is used
     at (the eyebrow, the footer, the note under the button). Darkened until
     it passes as normal text rather than only as large text. */
  --color-muted: #6a717b;

  /* The one accent. Spend it sparingly: the primary button, the mark, links in
     prose. Nothing else. Same value as the app's brand blue, typed in fresh
     here — three copies now, and they change together by hand. */
  --color-accent: #0a7cff;
  --color-accent-fill: #0b6fe0;
  --color-accent-soft: #e8f2ff;

  /* Link blue is not the mark blue. #0a7cff is 3.93:1 on white — fine for a
     mark, under AA for body text — so prose links take the darker fill blue
     at 4.8:1. In dark mode the lifted accent already clears it. */
  --color-link: #0b6fe0;

  /* One width, named once. */
  --width-shell: 48rem;

  /* Three radii, and only three. */
  --radius-control: 0.5rem;
  --radius-block: 0.75rem;
  --radius-card: 1rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-canvas: #0f1115;
    --color-surface: #16191f;
    --color-border: #262a31;
    --color-ink: #e9ecef;
    --color-body: #b9c0c8;
    --color-muted: #7f8894;
    --color-accent: #3d97ff;
    --color-accent-fill: #2b7fe6;
    --color-accent-soft: #14243a;
    /* The lifted accent already clears AA on the dark canvas, so links take
       it directly rather than the darker fill blue light mode needs. */
    --color-link: #3d97ff;
  }
}

html { color-scheme: light dark; }

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

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--color-canvas);
  color: var(--color-body);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Every focusable thing gets a ring without anyone having to remember it.
   :where() keeps specificity at zero, so a component that wants its own
   treatment still wins. */
:where(a, button, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 0.25rem;
}

/* Orphan control once, rather than remembered case by case. Headings get
   `balance`, which evens lines across the block; body copy gets `pretty`,
   which only stops a word being stranded on the last line. */
p, li, dd { text-wrap: pretty; }
h1, h2, h3, h4 { text-wrap: balance; }

/* One container. Chrome and content share it, so the mark, the copy and the
   footer sit on the same left and right edges. */
.shell {
  width: 100%;
  max-width: var(--width-shell);
  margin-inline: auto;
  padding-inline: 1.25rem;
}

/* ---------------------------------------------------------------- chrome */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--color-canvas);
  border-bottom: 1px solid var(--color-border);
}

.site-header .shell {
  height: 4rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.logo {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--color-ink);
  text-decoration: none;
}
@media (min-width: 640px) {
  .logo { font-size: 1.125rem; }
}
/* The mark is the app icon: a white tile with the blue dot, the same thing
   people will have in their Dock. White on a light canvas needs an edge, and
   the edge is a shadow rather than a border — which is how macOS renders an
   app icon, and keeps the mark itself to two colours. */
.logo-mark {
  height: 1.35em;
  width: 1.35em;
  flex-shrink: 0;
  border-radius: 0.3em;
  box-shadow:
    0 0 0 0.5px rgb(0 0 0 / 0.10),
    0 1px 2px rgb(0 0 0 / 0.12);
}
@media (prefers-color-scheme: dark) {
  /* On a dark canvas the tile separates on its own; the ring would read as a
     grey outline around a white shape. */
  .logo-mark { box-shadow: 0 1px 3px rgb(0 0 0 / 0.5); }
}
.logo:hover { color: var(--color-accent); }

.site-nav {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
}
@media (min-width: 640px) {
  .site-nav { gap: 1.25rem; }
}
.site-nav a {
  color: var(--color-body);
  text-decoration: none;
}
.site-nav a:hover { color: var(--color-ink); }

main { flex: 1; }

.site-footer {
  border-top: 1px solid var(--color-border);
  margin-top: 4rem;
}
/* Stacked and centred on phones, where the lines wrap anyway and a
   left-aligned set reads as an unfinished column. */
.site-footer .footer-links {
  padding-block: 1.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
  font-size: 0.875rem;
  color: var(--color-muted);
}
.site-footer .footer-legal {
  padding-bottom: 1.5rem;
  text-align: center;
  font-size: 0.75rem;
  color: var(--color-muted);
}
@media (min-width: 640px) {
  .site-footer .footer-links {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0 1.5rem;
    text-align: left;
  }
  .site-footer .footer-legal { text-align: left; }
}
.site-footer p { margin: 0; }
.site-footer a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.site-footer a:hover { color: var(--color-body); }

/* ------------------------------------------------------------------- hero */
/*
 * The app, drawn in CSS: a menu bar with the app's glyph, and its menu open
 * underneath. Menu colours are their own tokens rather than the page's — a
 * macOS menu is a floating surface with its own contrast, and reusing
 * --color-surface made it read as a card sitting in the page.
 */

:root {
  --menu-bg: #ffffff;
  --menu-ink: #1c1c1e;
  --menu-dim: #8a8f98;
  --menu-sep: #e3e5e8;
  --menu-bar: #fbfcfd;
  --bar-ink: #1c1c1e;
  /* The desktop behind the chrome. It has to be darker than the bar and the
     menu, or in light mode the whole mock is three shades of the same
     off-white and the menu bar has nothing to sit on — which is exactly what
     it was: bar #f7f8f9 on surface #f6f8fa. */
  --desktop: #e7eaee;
}
@media (prefers-color-scheme: dark) {
  :root {
    --menu-bg: #2c2c2e;
    --menu-ink: #f2f2f7;
    --menu-dim: #98999e;
    --menu-sep: #3d3d40;
    --menu-bar: #1c1c1e;
    --bar-ink: #ffffff;
    --desktop: #101216;
  }
}

.hero { margin-bottom: 3rem; }

.stage {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  background: var(--desktop);
  overflow: hidden;
}

.menubar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 1.75rem;
  padding-inline: 0.625rem;
  background: var(--menu-bar);
  border-bottom: 1px solid var(--color-border);
  font-family: var(--font-system);
  font-size: 0.75rem;
  letter-spacing: -0.005em;
  /* macOS draws menu bar text white in dark mode and near-black in light —
     full contrast either way, not the grey this used. */
  color: var(--bar-ink);
  user-select: none;
}

/* Highlighted the way macOS highlights the menu extra whose menu is open. */
.menubar-app {
  display: inline-flex;
  align-items: center;
  padding: 0.1875rem;
  border-radius: 0.3125rem;
  color: var(--menu-ink);
}
/* An open status item takes a subtle fill, the way macOS draws it — not the
   accent. Blue here reads as a brand badge stuck to the menu bar rather than
   as the menu being open. */
.menubar-app.is-open {
  background: rgb(255 255 255 / 0.16);
  color: var(--bar-ink);
}
@media (prefers-color-scheme: light) {
  .menubar-app.is-open { background: rgb(0 0 0 / 0.09); }
}

.menubar-left, .menubar-right {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.menubar-right { gap: 0.8125rem; }
.menubar-left { gap: 0.625rem; }
.menubar-apple { width: 0.8rem; height: 0.9rem; display: block; }
/* Furniture. Named so it is obvious these are stand-ins, not our menus. */
.menubar-word { opacity: 0.85; }
/* The active application's name is the one in bold, as macOS draws it. */
.menubar-word:first-of-type { font-weight: 600; opacity: 0.9; }
@media (max-width: 519px) {
  .menubar-word { display: none; }
}

.menubar-glyph { width: 0.9rem; height: 0.9rem; display: block; }
/* Stand-ins for the system's own menu extras. Shapes rather than invented
   glyphs — eight made-up icons would be eight more things to look at, and the
   only item here meant to hold the eye is ours. Their size and spacing are
   load-bearing as well as cosmetic: together they push the app's item in from
   the right edge, which is what leaves its menu room to open. */
.menubar-dot {
  width: 0.8125rem;
  height: 0.8125rem;
  border-radius: 2px;
  background: currentColor;
  /* Knocked well back. They are placeholders, and the menu bar text beside
     them is at full contrast on purpose — if these sat at the same weight
     they would read as real menu extras competing with the one item that
     matters. */
  opacity: 0.22;
  flex-shrink: 0;
}
@media (prefers-color-scheme: light) {
  /* Dark-on-light needs less opacity to reach the same recessive weight. */
  .menubar-dot { opacity: 0.16; }
}
.menubar-time { font-variant-numeric: tabular-nums; }

/* The menu hangs off the status item itself rather than being placed next to
   it, so the two cannot drift apart at any width — an earlier version aligned
   them with a percentage and the item sat at 78%, where a 280px menu had
   nowhere to open. The trailing furniture in the bar is what leaves it room. */
.menubar-app { position: relative; }

.stage { overflow: visible; }
.menubar {
  border-radius: calc(var(--radius-card) - 1px) calc(var(--radius-card) - 1px) 0 0;
}

/* A spacer: the menu is absolutely positioned, so nothing else gives the
   stage a height. Measured against the menu rather than guessed — at 30.5rem
   the last row hung 14px through the bottom edge. */
.stage-body { min-height: 33rem; }

.stage-body:empty { padding: 0; }

.menu {
  position: absolute;
  top: calc(100% + 0.3125rem);
  left: 0;
  z-index: 2;
  width: 17.5rem;
  padding: 0.3125rem;
  border-radius: 0.625rem;
  background: var(--menu-bg);
  border: 1px solid var(--menu-sep);
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.18), 0 2px 6px rgb(0 0 0 / 0.10);
  font-family: var(--font-system);
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: -0.005em;
  color: var(--menu-ink);
  user-select: none;
}

/* Below 640px the item is near the right edge and there is no room to the
   right of it, so the menu opens leftward instead — which is also what macOS
   does when a menu would run off the screen. */
@media (max-width: 639px) {
  .menu { left: auto; right: 0; width: min(17.5rem, 78vw); }
  .stage-body { min-height: 27rem; }
  .menubar-word { display: none; }
}

.menu-row {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1875rem 0.4375rem;
  border-radius: 0.25rem;
  white-space: nowrap;
}

/* The status lines under the switch. Disabled items, because they report
   rather than command — which is what the app does too. */
.menu-row.is-dim { color: var(--menu-dim); }

.menu-tick {
  width: 0.875rem;
  flex-shrink: 0;
  font-size: 0.75rem;
  text-align: center;
}

.menu-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }

.menu-chev, .menu-key {
  flex-shrink: 0;
  color: var(--menu-dim);
  font-size: 0.75rem;
  padding-left: 0.5rem;
}
.menu-chev { font-size: 0.9375rem; line-height: 1; }

.menu-head {
  padding: 0.375rem 0.4375rem 0.125rem 1.5rem;
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--menu-dim);
}

.menu-sep {
  height: 1px;
  background: var(--menu-sep);
  margin: 0.3125rem 0.4375rem;
}

/* The app icon leads the copy, on the same left edge as the heading beneath
   it. The artwork is full-bleed, so the corner radius and the shadow sit on
   the tile rather than on a transparent margin around it. A white tile on a
   white page needs an edge, and the edge is a shadow — which is how macOS
   renders an app icon, and keeps the mark to the two colours it is drawn
   with. */
.hero-icon {
  display: block;
  width: 112px;
  height: 112px;
  border-radius: 25px;
  margin-bottom: 2.25rem;
  box-shadow:
    0 0 0 0.5px rgb(0 0 0 / 0.08),
    0 10px 30px rgb(0 0 0 / 0.14),
    0 2px 6px rgb(0 0 0 / 0.08);
}
@media (min-width: 640px) {
  .hero-icon { width: 144px; height: 144px; border-radius: 32px; margin-bottom: 2.75rem; }
}
@media (prefers-color-scheme: dark) {
  .hero-icon {
    box-shadow: 0 10px 34px rgb(0 0 0 / 0.6), 0 2px 6px rgb(0 0 0 / 0.4);
  }
}

/* -------------------------------------------------------------------- cta */

.cta { margin: 2.25rem 0 0; }

/* Apple's badge shape, drawn here rather than using their artwork: the real
   badge is a fixed image with its own usage rules, and this has to invert
   cleanly between the two schemes. */
.appstore {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.625rem 1.125rem;
  border-radius: var(--radius-control);
  background: var(--color-ink);
  color: var(--color-canvas);
  text-decoration: none;
  font-family: var(--font-system);
  transition: transform 150ms ease, opacity 150ms ease;
}
.appstore:hover { opacity: 0.88; }
.appstore:active { transform: scale(0.985); }

.appstore-logo { width: 1.4rem; height: 1.7rem; flex-shrink: 0; }

.appstore-text { display: flex; flex-direction: column; line-height: 1.15; }
.appstore-small { font-size: 0.6875rem; letter-spacing: 0.01em; opacity: 0.9; }
.appstore-big { font-size: 1.0625rem; font-weight: 500; letter-spacing: -0.02em; }

/* Says plainly that the button does not work yet. It goes when the link does. */
.cta-note {
  margin: 0.875rem 0 0;
  font-size: 0.875rem;
  color: var(--color-muted);
}

/* ------------------------------------------------------------------ type */

/* Mono carries anything the machine said: addresses, commands, file paths. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.3;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin: 0;
}

.page-title {
  margin: 0.75rem 0 0;
  font-size: 1.875rem;
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--color-ink);
}
@media (min-width: 640px) {
  .page-title { font-size: 2.25rem; }
}

.lead {
  margin: 1rem 0 0;
  font-size: 1.125rem;
  color: var(--color-body);
}

.page-main {
  padding-block: 2rem;
}
/* The hero is centred and the copy under it is not. Tighter top padding on the
   home page because the icon already carries the space. */
.page-main:has(.hero) {
  padding-top: 2.5rem;
}
@media (min-width: 640px) {
  .page-main { padding-block: 6rem 4rem; }
}

/* Long-form pages. Hand-rolled rather than a typography plugin — there are
   three content pages and they need six elements styled. */
.prose { color: var(--color-body); }
.prose h2 {
  color: var(--color-ink);
  font-size: 1.5rem;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 3rem 0 0.75rem;
}
.prose h3 {
  color: var(--color-ink);
  font-size: 1rem;
  line-height: 1.5;
  font-weight: 600;
  margin: 1.75rem 0 0.375rem;
}
.prose p, .prose ul, .prose ol { margin: 0 0 1rem; }
.prose ul, .prose ol { padding-left: 1.25rem; }
.prose ul { list-style: disc; }
.prose ol { list-style: decimal; }
.prose li { margin-bottom: 0.375rem; }
.prose a {
  color: var(--color-link);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.prose strong { color: var(--color-ink); font-weight: 600; }
.prose code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--color-ink);
  background: var(--color-surface);
  padding: 0.1em 0.35em;
  border-radius: 0.25rem;
  /* Resolver addresses and file paths have no spaces to break at. */
  overflow-wrap: anywhere;
}
.prose pre {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--color-ink);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-block);
  padding: 0.875rem 1rem;
  overflow-x: auto;
  margin: 0 0 1rem;
}
.prose pre code { background: none; padding: 0; font-size: inherit; }

.updated {
  font-size: 0.875rem;
  color: var(--color-muted);
  margin: 1.5rem 0 0;
}

/* --------------------------------------------------------------- blocks */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 3rem;
  padding-inline: 1.75rem;
  border-radius: var(--radius-control);
  font: inherit;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: filter 150ms ease;
}
.btn-primary {
  background: var(--color-accent-fill);
  color: #fff;
  border: 0;
}
.btn-primary:hover { filter: brightness(1.1); }
.btn-primary:active { filter: brightness(0.95); }

/* Outlined, never blue. The accent is rationed to one action; a second blue
   button would stop either reading as the main one. */
.btn-secondary {
  background: var(--color-canvas);
  color: var(--color-ink);
  border: 1px solid var(--color-border);
  transition: background-color 150ms ease, border-color 150ms ease;
}
.btn-secondary:hover {
  background: var(--color-surface);
  border-color: var(--color-muted);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

/* Says the app is not out yet. It is the first thing a visitor needs to know,
   and a page that implies a download that does not exist is worse than a page
   that says "not yet". */
.notice {
  margin-top: 2rem;
  padding: 1rem 1.125rem;
  background: var(--color-accent-soft);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-block);
  color: var(--color-ink);
  font-size: 0.9375rem;
}
.notice p { margin: 0; }

.features,
.prose .features {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  display: grid;
  gap: 0.75rem;
}
@media (min-width: 640px) {
  .features { grid-template-columns: 1fr 1fr; }
}
.features li,
.prose .features li {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: 1rem 1.125rem;
  margin: 0;
}
.features h3 {
  margin: 0 0 0.25rem;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-ink);
}
.features p {
  margin: 0;
  font-size: 0.875rem;
  color: var(--color-body);
}

/* The provider list. Mono, because these are names of machines.
   `.prose ul` sets a disc and an indent and outranks a lone class, so these
   two selectors are written to match it rather than lose to it — the chips
   rendered as a bulleted list the first time. */
.providers,
.prose .providers {
  list-style: none;
  padding: 0;
  margin: 1.25rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.providers li {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--color-body);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-control);
  padding: 0.3rem 0.6rem;
}

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