/* l4f.cc
 *
 * Dark by default, light on request. The page background is the mask wallpaper
 * the old site used; everything readable sits on a translucent panel above it.
 * There is exactly one box component (.panel) and one bright object (.pill).
 */

@font-face {
  font-family: Poppins;
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/poppins-600.woff2) format("woff2");
}
@font-face {
  font-family: Poppins;
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/static/fonts/poppins-700.woff2) format("woff2");
}

/* ---------------------------------------------------------------- tokens */

:root {
  --bg: #0d1117;
  --panel: rgba(13, 17, 23, .82);
  --chrome: rgba(13, 17, 23, .75);
  --raised: #161b22;
  --text: #e6edf3;
  --muted: #8b949e;
  --line: #30363d;
  --line-strong: #6e7681;
  --accent: #3fb950;
  --accent-ink: #04120a;
  --danger: #f85149;
  --kofi: #f57873;
  --pill: #ffffff;
  --pill-ink: #16181d;
  --pill-muted: #6a737d;
  --shadow: 0 8px 28px rgba(0, 0, 0, .45);
  --mask-filter: none;
  --mask-opacity: 1;

  --head: Poppins, system-ui, -apple-system, "Segoe UI", sans-serif;
  --body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "Cascadia Code", "JetBrains Mono", Consolas, "Liberation Mono", monospace;

  --wide: 1080px;
  --read: 880px;
  --radius: 12px;
  color-scheme: dark;
}

/* Light values live in one place and are applied by two selectors: the explicit
   opt-in, and the OS preference when the visitor has not chosen. */
:root[data-theme="light"] {
  --bg: #ffffff;
  --panel: rgba(255, 255, 255, .86);
  --chrome: rgba(246, 248, 250, .85);
  --raised: #f6f8fa;
  --text: #1f2328;
  --muted: #656d76;
  --line: #d0d7de;
  --line-strong: #8c959f;
  --accent: #1a7f37;
  --accent-ink: #ffffff;
  --danger: #cf222e;
  --shadow: 0 8px 28px rgba(31, 35, 40, .15);
  --mask-filter: invert(1);
  --mask-opacity: .3;
  color-scheme: light;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]):not([data-theme="light"]) {
    --bg: #ffffff;
    --panel: rgba(255, 255, 255, .86);
    --chrome: rgba(246, 248, 250, .85);
    --raised: #f6f8fa;
    --text: #1f2328;
    --muted: #656d76;
    --line: #d0d7de;
    --line-strong: #8c959f;
    --accent: #1a7f37;
    --accent-ink: #ffffff;
    --danger: #cf222e;
    --shadow: 0 8px 28px rgba(31, 35, 40, .15);
    --mask-filter: invert(1);
    --mask-opacity: .3;
    color-scheme: light;
  }
}

/* ------------------------------------------------------------------ base */

* { margin: 0; padding: 0; box-sizing: border-box; }

/* The browser's own [hidden] rule loses to any author `display`, so a hidden
   .btn, .chk or .btn-row would stay on screen. The scripts toggle `hidden`
   everywhere, and mean it. */
[hidden] { display: none !important; }

html { scroll-padding-top: 84px; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }

body {
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* The wallpaper. Fixed to the viewport via a pseudo-element rather than
   background-attachment: fixed, which iOS ignores outright and Android scrolls
   badly; `cover` on a long document would also blow the mask up to the full
   page height instead of the screen. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: #000 url(/static/img/wallpaper.jpg) center top / cover no-repeat;
  filter: var(--mask-filter);
  opacity: var(--mask-opacity);
}

main { flex: 1; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

h1, h2, h3, h4 { font-family: var(--head); font-weight: 700; line-height: 1.2; }
h1 { font-size: clamp(1.9rem, 4vw, 2.6rem); }
h2 { font-size: clamp(1.35rem, 2.6vw, 1.7rem); font-weight: 600; margin-bottom: 10px; }
h3 { font-size: 1.05rem; font-weight: 600; }
/* A lede directly under a heading supplies its own spacing. */
h2 + .lede { margin-top: -4px; }
p + p, p + ul, p + ol, p + pre, p + .callout { margin-top: 12px; }

img { max-width: 100%; height: auto; }
small, .small { font-size: .875rem; }
.muted { color: var(--muted); }
.mono { font-family: var(--mono); }
.ok { color: var(--accent); }
.no { color: var(--danger); }

code {
  font-family: var(--mono);
  font-size: .9em;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  word-break: break-word;
}

/* On a phone a command wraps. Sideways-scrolling code is unreadable on a 390px
   screen and, worse, silently truncated - you cannot tell there is more. Leading
   indentation survives `pre-wrap`, so YAML and TOML still read correctly. */
pre {
  font-family: var(--mono);
  font-size: .8125rem;
  line-height: 1.7;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 13px 14px;
  margin: 12px 0;
  max-width: 100%;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
pre code { background: none; border: 0; padding: 0; font-size: inherit; }
pre .c { color: var(--muted); }

.sr-only, .skip {
  position: absolute; width: 1px; height: 1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip:focus {
  position: fixed; top: 10px; left: 10px; z-index: 300;
  width: auto; height: auto; clip: auto;
  padding: 10px 16px; background: var(--raised);
  border: 1px solid var(--accent); border-radius: 8px; color: var(--text);
}

.wrap { max-width: var(--wide); margin: 0 auto; padding: 0 20px; }
.read { max-width: var(--read); }

/* ========================================================================
   Layout. Mobile first: every rule below is the phone, and the `min-width`
   queries add space back as the screen grows. Nothing here uses `max-width`,
   so the narrow case can never be an afterthought that the wide case
   accidentally overrides.

   Breakpoints, and what each one buys:
     520px  two-column label/value rows
     620px  key/value lists become two columns
     640px  result panel puts the QR beside the link instead of under it
     760px  the three-up lists sit side by side
     860px  the quick-join dialog becomes a window instead of a sheet
    1100px  the navigation fits on one line, footer goes three-up
    1240px  the hero moves left so the wallpaper's face is uncovered
   ======================================================================== */

/* ----------------------------------------------------------------- panel */
/* The only box on the site. */

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(18px, 4.5vw, 32px);
  box-shadow: var(--shadow);
  /* A panel is a container for prose and code; it must never be widened by
     something inside it. This is the single most common source of a phone
     scrollbar. */
  min-width: 0;
  overflow-wrap: break-word;
}
@supports (backdrop-filter: blur(6px)) { .panel { backdrop-filter: blur(6px); } }
.panel + .panel { margin-top: 20px; }

.rule {
  margin: 30px 0 18px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
}
.rule:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.lede { color: var(--muted); max-width: 62ch; margin-top: 6px; }

/* ---------------------------------------------------------------- header */

header.bar {
  position: sticky; top: 0; z-index: 100;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
}
@supports (backdrop-filter: blur(10px)) { header.bar { backdrop-filter: blur(10px); } }

.bar-inner {
  max-width: 1320px; margin: 0 auto; padding: 0 16px;
  min-height: 60px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.wordmark {
  font-family: var(--head); font-weight: 700;
  font-size: 1.2rem; color: var(--text);
  margin-right: auto; padding: 10px 0;
  min-width: 0;
}
.wordmark:hover { color: var(--text); text-decoration: none; }

/* Phone: a stacked list that the Menu button reveals. Gated on `.js`, so with
   scripting off the links are simply all there. */
nav.links { display: flex; list-style: none; flex-wrap: wrap; align-items: center; gap: 2px; }
.js nav.links { display: none; order: 5; flex-basis: 100%; flex-direction: column; align-items: stretch; padding-bottom: 10px; }
.js nav.links.open { display: flex; }
nav.links li { list-style: none; min-width: 0; }
nav.links a {
  display: block; padding: 12px;
  font-family: var(--head); font-weight: 600; font-size: .9375rem;
  color: var(--muted); border-radius: 6px;
}
nav.links a:hover { color: var(--text); text-decoration: none; background: var(--raised); }
nav.links a[aria-current="page"] { color: var(--text); box-shadow: inset 0 -2px 0 var(--accent); }

.menu { position: static; }
.menu > summary {
  list-style: none; cursor: pointer; padding: 12px;
  font-family: var(--head); font-weight: 600; font-size: .9375rem;
  color: var(--muted); border-radius: 6px;
  display: flex; align-items: center; gap: 6px;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::after {
  content: ""; width: 6px; height: 6px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}
.menu[open] > summary::after { transform: translateY(1px) rotate(225deg); }
.menu > summary:hover { color: var(--text); background: var(--raised); }
.menu ul {
  list-style: none; margin-top: 4px;
  background: var(--raised); border: 1px solid var(--line);
  border-radius: 8px; padding: 6px;
}
.menu ul a { display: block; padding: 10px; font-size: .9375rem; color: var(--text); border-radius: 6px; }
.menu ul a:hover { background: var(--panel); text-decoration: none; }

.menu-btn {
  order: 3; min-height: 40px; padding: 8px 12px;
  font: 600 .875rem var(--head); color: var(--muted);
  background: none; border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
}
.menu-btn:hover { color: var(--text); border-color: var(--line-strong); }
/* Without the script the links are already visible, so the control that would
   reveal them would be a lie. */
.menu-btn { display: none; }
.js .menu-btn { display: inline-block; }

.theme-toggle { display: inline-flex; order: 2; }
.theme-toggle button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  background: none; border: 1px solid var(--line); border-radius: 8px;
  color: var(--muted); cursor: pointer;
}
.theme-toggle button:hover { color: var(--text); border-color: var(--line-strong); }
.theme-toggle svg { width: 18px; height: 18px; fill: currentColor; }
.theme-toggle .to-light { display: inline; }
.theme-toggle .to-dark { display: none; }
:root[data-theme="light"] .theme-toggle .to-light { display: none; }
:root[data-theme="light"] .theme-toggle .to-dark { display: inline; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]):not([data-theme="light"]) .theme-toggle .to-light { display: none; }
  :root:not([data-theme="dark"]):not([data-theme="light"]) .theme-toggle .to-dark { display: inline; }
}

/* --------------------------------------------------------------- content */

.wrap { max-width: var(--wide); margin: 0 auto; padding: 0 16px; width: 100%; }
.read { max-width: var(--read); }
.page { padding-top: 24px; }

/* --------------------------------------------------------------- footer */

footer.bar {
  margin-top: 48px;
  background: var(--chrome);
  border-top: 1px solid var(--line);
  padding: 28px 0 24px;
}
@supports (backdrop-filter: blur(10px)) { footer.bar { backdrop-filter: blur(10px); } }
.foot {
  max-width: 1320px; margin: 0 auto; padding: 0 16px;
  display: grid; grid-template-columns: 1fr; gap: 26px;
}
.foot > div { min-width: 0; }
.foot .tagline { color: var(--muted); font-size: .9375rem; margin-top: 6px; }
.foot h4 {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); font-weight: 600; margin-bottom: 10px;
}
.foot dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 14px; font-size: .9375rem; }
.foot dt { color: var(--muted); }
.foot dd { min-width: 0; overflow-wrap: anywhere; }

.socials { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.socials a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--line-strong); color: var(--muted);
}
.socials a:hover { color: var(--text); border-color: var(--text); text-decoration: none; }
.socials svg { width: 18px; height: 18px; fill: currentColor; }

.kofi {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--kofi); color: #fff;
  font: 600 .875rem var(--head);
  padding: 11px 16px; border-radius: 7px; min-height: 44px;
  box-shadow: 1px 1px 0 rgba(0, 0, 0, .2);
}
.kofi:hover { opacity: .85; color: #fff; text-decoration: none; }
.kofi svg { width: 18px; height: 18px; fill: currentColor; }

/* ------------------------------------------------------------------ home */

.hero { display: flex; align-items: center; padding: 24px 0 8px; }
.hero .wrap { max-width: 1320px; width: 100%; }
.hero .panel { width: 100%; }
.hero h1 { font-size: clamp(1.45rem, 6vw, 2rem); }

/* The pill. On a phone it is two stacked full-width controls; the row is the
   enhancement, not the default. */
.pill {
  display: flex; flex-direction: column; gap: 8px;
  background: var(--pill); border-radius: 10px;
  padding: 8px; margin-top: 16px;
  border: 1px solid transparent;
}
:root[data-theme="light"] .pill { border-color: var(--line-strong); box-shadow: 0 1px 2px rgba(31,35,40,.1); }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]):not([data-theme="light"]) .pill { border-color: var(--line-strong); box-shadow: 0 1px 2px rgba(31,35,40,.1); }
}
.pill.bad { border-color: var(--danger); }
.pill input {
  width: 100%; min-width: 0;
  font-family: var(--mono);
  font-size: 16px;                    /* under 16px iOS zooms the page on focus */
  padding: 12px 14px;
  border: 0; background: none; color: var(--pill-ink);
  text-overflow: ellipsis;
}
.pill input::placeholder { color: var(--pill-muted); }
.pill input:focus { outline: none; }
.pill button, .pill .btn {
  width: 100%;
  font: 600 .9375rem var(--head);
  padding: 12px 20px; min-height: 44px;
  border: 0; border-radius: 6px;
  background: var(--accent); color: var(--accent-ink); cursor: pointer;
}
.pill button:hover, .pill .btn:hover { filter: brightness(1.08); text-decoration: none; }
.pill.result input { font-weight: 600; user-select: all; }

.opt { margin-top: 14px; }
.opt > summary {
  cursor: pointer; list-style: none;
  font-size: .9375rem; color: var(--muted);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 0; min-height: 32px;
}
.opt > summary::-webkit-details-marker { display: none; }
.opt > summary::before { content: "+"; font-family: var(--mono); }
.opt[open] > summary::before { content: "\2212"; }
.opt > summary:hover { color: var(--text); }
.opt .body { margin-top: 8px; }

.field { margin-bottom: 16px; }
label { display: block; font-size: .875rem; color: var(--muted); margin-bottom: 6px; }
input[type=text], input[type=url], input[type=password], input[type=search],
input[type=number], input[type=datetime-local], input[type=email], input[type=tel],
textarea, select {
  width: 100%; max-width: 100%;
  font: inherit; font-size: 16px;
  min-height: 44px; padding: 10px 12px;
  background: var(--raised); color: var(--text);
  border: 1px solid var(--line-strong); border-radius: 8px;
}
input[aria-invalid="true"] { border-color: var(--danger); }
.hint { font-size: .8125rem; color: var(--muted); margin-top: 6px; }
.err { color: var(--danger); font-size: .875rem; margin-top: 6px; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.notice {
  border-left: 3px solid var(--danger);
  padding: 10px 14px; margin-bottom: 16px;
  background: var(--raised); border-radius: 0 8px 8px 0;
}
.notice.good { border-left-color: var(--accent); }

.services {
  margin-top: 16px; font-size: .875rem; color: var(--muted);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; padding: 10px 16px;
}
@supports (backdrop-filter: blur(6px)) { .services { backdrop-filter: blur(6px); } }
.services a { color: var(--muted); display: inline-block; padding: 2px 0; }
.services a:hover { color: var(--accent); }
.services a + a::before { content: "\00b7"; padding: 0 8px; opacity: .55; }

/* Result: label above value on a phone, side by side once there is room. */
.rows { display: grid; grid-template-columns: 1fr; gap: 2px; margin-top: 18px; font-size: .9375rem; }
.rows dt { color: var(--muted); }
.rows dd { min-width: 0; margin-bottom: 12px; }
.rows dd:last-child { margin-bottom: 0; }
.rows .trunc { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.result-top { display: flex; flex-direction: column; gap: 18px; align-items: flex-start; }
.result-top > div { width: 100%; min-width: 0; }
/* Dark-on-light in both themes: a good share of scanners fail on inverted codes. */
.qr { flex-shrink: 0; background: #fff; padding: 8px; border-radius: 8px; width: 128px; }
.qr svg { display: block; width: 100%; height: auto; }

.copy { display: none; }
.js .copy { display: inline-flex; }
.back { display: inline-block; margin-top: 20px; font-size: .9375rem; }

/* Your links: the list static/js/links.js builds under the form from this
   browser's storage. A rule between entries, and the long link cut to one line
   as in the result above. */
.mylinks { list-style: none; margin-top: 12px; }
.mylinks li { min-width: 0; padding: 12px 0; border-top: 1px solid var(--line); }
.mylinks .trunc { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

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

/* Numbered steps: the numeral sits above the text on a phone, in the gutter
   once there is a gutter to put it in. */
.steps { list-style: none; counter-reset: step; }
.steps > li { counter-increment: step; position: relative; padding-top: 46px; }
.steps > li + li { margin-top: 26px; }
.steps > li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  font: 700 .95rem var(--head);
  color: var(--accent); border: 1px solid var(--accent); border-radius: 50%;
}
.steps h3 { margin-bottom: 6px; }
.steps p { color: var(--muted); }

/* Key/value: stacked by default. */
.kv { display: grid; grid-template-columns: 1fr; }
.kv dt {
  font-family: var(--head); font-weight: 600; font-size: .9375rem;
  padding: 14px 0 0; border-top: 1px solid var(--line);
  min-width: 0; overflow-wrap: anywhere;
}
.kv dd { padding: 4px 0 14px; color: var(--muted); min-width: 0; }
.kv dt:first-of-type { border-top: 0; padding-top: 0; }
.kv dd > :first-child { margin-top: 0; }

.callout { border-left: 3px solid var(--accent); padding: 4px 0 4px 16px; margin: 18px 0; }
.callout.warn { border-left-color: var(--danger); }
.callout h3 { margin-bottom: 6px; }
.callout p, .callout li { color: var(--muted); }

.ticks { list-style: none; }
.ticks li { position: relative; padding: 5px 0 5px 18px; color: var(--muted); }
.ticks li::before { content: "\00b7"; position: absolute; left: 4px; color: var(--accent); font-weight: 700; }

/* Three related lists: stacked, separated by rules rather than card borders. */
.cols { display: grid; grid-template-columns: 1fr; }
.cols > div { min-width: 0; padding: 20px 0 0; border-top: 1px solid var(--line); margin-top: 20px; }
.cols > div:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* Screenshots as a swipeable strip. */
.strip {
  display: flex; gap: 14px;
  overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: 12px; margin-top: 16px;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
.strip figure { scroll-snap-align: start; flex: 0 0 86%; min-width: 0; }
.strip img { display: block; width: 100%; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); }
.strip figcaption { font-size: .8125rem; color: var(--muted); margin-top: 8px; }

.table-wrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line); border-radius: 8px; margin-top: 16px;
  max-width: 100%;
}
table { width: 100%; border-collapse: collapse; min-width: 480px; font-size: .9375rem; }
th, td { padding: 11px 13px; text-align: left; border-bottom: 1px solid var(--line); vertical-align: top; }
tr:last-child td { border-bottom: 0; }
thead th { background: var(--raised); font-family: var(--head); font-weight: 600; white-space: nowrap; }
td { color: var(--muted); }
td:first-child { color: var(--text); font-family: var(--mono); font-size: .875rem; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 11px 20px;
  font: 600 .9375rem var(--head);
  border: 0; border-radius: 8px; cursor: pointer;
  background: var(--accent); color: var(--accent-ink);
  max-width: 100%;
}
.btn:hover { filter: brightness(1.08); text-decoration: none; color: var(--accent-ink); }
.btn-sm { min-height: 36px; padding: 7px 13px; font-size: .8125rem; }
.btn-quiet { background: none; color: var(--text); border: 1px solid var(--line-strong); }
.btn-quiet:hover { color: var(--text); border-color: var(--accent); filter: none; }
.btn-danger { background: none; color: var(--danger); border: 1px solid var(--danger); }
.btn-danger:hover { color: var(--danger); filter: none; }
.btn-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-top: 16px; }

/* A copy button sits ABOVE its code block, never over it. Floating it on top
   works until the viewport is narrow, and then it covers the very characters
   you were trying to read - and the block scrolls underneath it, so you cannot
   move the text out from under it either. */
.copywrap { display: flex; flex-direction: column; align-items: stretch; margin: 12px 0; }
.copywrap .copy { align-self: flex-end; margin-bottom: 6px; }
.copywrap pre { margin: 0; }

.tag {
  display: inline-block; font: 600 .6875rem var(--head);
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent); border: 1px solid var(--accent);
  border-radius: 999px; padding: 2px 8px; vertical-align: middle;
}

/* ------------------------------------------------------------ statistics */

.figures { display: flex; flex-wrap: wrap; gap: 20px 32px; margin-top: 16px; }
.figures div { min-width: 0; }
.figures b { display: block; font: 700 1.6rem var(--head); font-variant-numeric: tabular-nums; line-height: 1.1; }
.figures span { font-size: .8125rem; color: var(--muted); }

.bars { list-style: none; display: grid; gap: 7px; margin-top: 14px; }
.bars li {
  display: grid; grid-template-columns: minmax(0, 8ch) 1fr auto;
  gap: 8px; align-items: center; font-size: .875rem;
}
.bars .lbl { font-family: var(--mono); font-size: .75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bars .bar { background: var(--raised); border-radius: 3px; height: 14px; overflow: hidden; min-width: 0; }
.bars .bar > span { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.bars .n { color: var(--muted); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------- VoIPC quick-join sheet */

/* A phone gets a full-screen sheet; dragging a 92vw panel around a 390px
   screen is not a feature. */
dialog.vq {
  padding: 0; margin: 0; inset: 0;
  position: fixed;
  width: 100%; height: 100vh; height: 100dvh;
  max-width: none; max-height: none;
  background: var(--panel); color: var(--text);
  border: 0; border-radius: 0;
  overflow: hidden; resize: none;
  display: flex; flex-direction: column;
  overscroll-behavior: contain;
}
dialog.vq::backdrop { background: rgba(0, 0, 0, .66); }
.vq-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px 8px 12px;
  background: var(--raised); border-bottom: 1px solid var(--line);
  font-size: .8125rem;
  touch-action: none;                  /* or the browser scrolls instead of dragging */
  flex-wrap: wrap;
}
.vq-bar .sp { flex: 1; min-width: 0; }
.vq-body { flex: 1; position: relative; background: #000; }
.vq-body iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.vq-foot { padding: 9px 12px 12px; font-size: .8125rem; color: var(--muted); display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }


/* ----------------------------------------------------------------- tools */
/* Mobile first like everything else: these are the phone rules, and the
   min-width blocks further down add columns once there is room. */

.tools { list-style: none; display: grid; gap: 18px; }
.tools li { min-width: 0; padding-top: 18px; border-top: 1px solid var(--line); }
.tools li:first-child { padding-top: 0; border-top: 0; }
.tools a { font-family: var(--head); font-weight: 600; font-size: 1.05rem; }
.tools p { color: var(--muted); font-size: .9375rem; margin-top: 4px; }

.crumb { font-size: .875rem; margin-bottom: 10px; }
.crumb a { color: var(--muted); }
.crumb a:hover { color: var(--accent); }

/* The standing promise at the top of every tool page. */
.privacy { border-left: 3px solid var(--accent); padding-left: 12px; margin-top: 14px; }
.privacy.warn-mark { border-left-color: var(--danger); }
.warn-mark { color: var(--muted); }

/* Label-over-field pairs. One column on a phone; see the 640px block. */
.tool-grid { display: grid; grid-template-columns: 1fr; gap: 0 18px; }
.tool-grid .field { margin-bottom: 14px; }

/* The base input rule cannot cover these two: a file input is a button plus a
   label, and a range is a track — both ignore padding and border. */
input[type=file] {
  width: 100%; max-width: 100%;
  font: inherit; font-size: .9375rem; color: var(--muted);
  padding: 10px 12px; min-height: 44px;
  background: var(--raised);
  border: 1px solid var(--line-strong); border-radius: 8px;
}
input[type=file]::file-selector-button {
  font: 600 .8125rem var(--head);
  margin-right: 10px; padding: 7px 13px; min-height: 30px;
  background: none; color: var(--text);
  border: 1px solid var(--line-strong); border-radius: 6px; cursor: pointer;
}
input[type=file]::file-selector-button:hover { border-color: var(--accent); }
input[type=range] { width: 100%; accent-color: var(--accent); min-height: 32px; }

.chk { display: flex; align-items: center; gap: 8px; margin: 8px 0; }
.chk input { width: auto; min-height: 0; flex-shrink: 0; }
.chk label { margin: 0; color: var(--text); font-size: .9375rem; }
.chks { border: 0; margin: 10px 0; }
.chks legend { font-size: .875rem; color: var(--muted); margin-bottom: 4px; }

/* Camera stage. A fixed ratio so the panel does not jump when the stream
   arrives and the video element suddenly has a size. */
.stage {
  position: relative; aspect-ratio: 4 / 3; width: 100%;
  background: #000; border: 1px solid var(--line); border-radius: 8px;
  overflow: hidden; display: flex; align-items: center; justify-content: center;
}
.stage video { width: 100%; height: 100%; object-fit: contain; }
.stage-idle { color: var(--muted); font-size: .875rem; }

.drop {
  border: 2px dashed var(--line-strong); border-radius: 10px;
  padding: 20px 16px; text-align: center; color: var(--muted);
  transition: border-color .15s, background .15s;
}
.drop.over { border-color: var(--accent); background: var(--raised); }
.drop p + p { margin-top: 10px; }
.drop input[type=file] { width: 100%; }

/* Generated codes are dark-on-white in both themes: a meaningful share of
   scanners fail on an inverted code, so this box never follows the theme. */
.code-out {
  background: #fff; border-radius: 8px; padding: 12px;
  margin-top: 16px; max-width: 420px;
}
.code-out svg, .code-out img { display: block; width: 100%; height: auto; }

.code-label { font-size: .8125rem; color: var(--muted); margin-top: 14px; }
.scan-hit pre { user-select: all; }

.big {
  font: 700 2.4rem var(--mono); letter-spacing: .12em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
  overflow-wrap: anywhere;
}
progress { width: 100%; height: 6px; margin-top: 10px; }

/* An input next to its reveal button. */
.reveal { display: flex; gap: 8px; align-items: flex-start; }
.reveal input, .reveal textarea { flex: 1; min-width: 0; }
.reveal .btn { flex-shrink: 0; }
/* Blurred rather than replaced with dots, so the shape is still visible and it
   un-blurs the moment it has focus. */
.masked { filter: blur(4px); }
.masked:focus { filter: none; }

.hash-val { word-break: break-all; font-size: .8125rem; }
td .btn { white-space: nowrap; }

.pw-list { list-style: none; display: grid; gap: 6px; margin-top: 14px; }
.pw-list li { display: flex; gap: 10px; align-items: center; min-width: 0; }
.pw { flex: 1; min-width: 0; font-size: .9375rem; word-break: break-all; user-select: all; }

.preview { margin-top: 16px; }
.preview img {
  max-width: 100%; height: auto; display: block;
  border: 1px solid var(--line); border-radius: 8px;
  /* A checker so a transparent PNG is visibly transparent. */
  background: repeating-conic-gradient(var(--raised) 0% 25%, transparent 0% 50%) 50% / 16px 16px;
}

.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.tab {
  background: none; border: 0; border-bottom: 2px solid transparent;
  padding: 10px 12px; min-height: 44px; cursor: pointer;
  font: 600 .9375rem var(--head); color: var(--muted);
}
.tab:hover { color: var(--text); }
.tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--accent); }

.tag.warn { color: var(--danger); border-color: var(--danger); }

/* ======================================================================
   Enhancements. Everything above is the phone; everything below adds room.
   ====================================================================== */

@media (min-width: 520px) {
  .rows { grid-template-columns: auto minmax(0, 1fr); gap: 8px 16px; }
  .rows dd { margin-bottom: 0; }
  .strip figure { flex: 0 0 min(560px, 78vw); }
}

@media (min-width: 620px) {
  .kv { grid-template-columns: minmax(120px, 210px) minmax(0, 1fr); }
  .kv dt { padding: 12px 16px 12px 0; }
  .kv dd { padding: 12px 0; border-top: 1px solid var(--line); }
  .kv dt:first-of-type + dd { border-top: 0; }
  .bars li { grid-template-columns: minmax(0, 14ch) 1fr auto; gap: 12px; }
  .bars .lbl { font-size: .8125rem; }
}

@media (min-width: 640px) {
  /* Enough room for a long line, so code goes back to scrolling rather than
     wrapping and the shape of a config file is preserved. */
  pre { white-space: pre; overflow-x: auto; overflow-wrap: normal; padding: 14px 16px; }
  .wrap { padding: 0 20px; }
  .page { padding-top: 40px; }
  .result-top { flex-direction: row; gap: 22px; }
  .result-top > div { width: auto; flex: 1; }
  /* Once there is room for a row, the pill is a row again. */
  .pill { flex-direction: row; gap: 8px; }
  .pill input { flex: 1; }
  .pill button, .pill .btn { width: auto; flex-shrink: 0; padding: 12px 22px; }
  .figures b { font-size: 1.75rem; }
  .steps > li { padding-top: 0; padding-left: 56px; min-height: 40px; }
  .steps > li::before { width: 40px; height: 40px; font-size: 1.05rem; }
  .hero h1 { font-size: clamp(1.5rem, 3.2vw, 2rem); }
  .tool-grid { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
  .tools { grid-template-columns: 1fr 1fr; gap: 22px 28px; }
  .tools li { padding-top: 0; border-top: 0; }
}

@media (min-width: 760px) {
  .cols { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 28px; }
  .cols > div { padding: 0 0 0 28px; border-top: 0; border-left: 1px solid var(--line); margin-top: 0; }
  .cols > div:first-child { padding-left: 0; border-left: 0; }
}

@media (min-width: 860px) {
  /* Big enough to be worth moving around: the sheet becomes a window. */
  dialog.vq {
    inset: auto;
    width: min(960px, 92vw); height: min(640px, 80vh);
    border: 1px solid var(--line-strong); border-radius: 10px;
    resize: both;
    box-shadow: var(--shadow);
  }
  .vq-bar { cursor: grab; flex-wrap: nowrap; font-size: .875rem; }
  .vq-bar:active { cursor: grabbing; }
}

@media (min-width: 1100px) {
  .bar-inner { padding: 0 20px; min-height: 62px; }
  .wordmark { font-size: 1.35rem; }
  .js .menu-btn { display: none; }
  .js nav.links,
  nav.links {
    display: flex; flex-direction: row; align-items: center;
    flex-basis: auto; order: 0; padding-bottom: 0;
  }
  nav.links a { display: inline-block; padding: 9px 9px; font-size: .875rem; }
  .theme-toggle { order: 9; }
  .menu { position: relative; }
  .menu > summary { padding: 9px 11px; font-size: .875rem; display: inline-flex; }
  .menu ul {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 120;
    min-width: 220px; margin-top: 0; box-shadow: var(--shadow);
  }
  .menu ul a { padding: 9px 10px; font-size: .875rem; }
  .foot { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto; gap: 32px; padding: 0 20px; }
  footer.bar { margin-top: 64px; padding: 36px 0 32px; }
}

@media (min-width: 1240px) {
  /* The face in the wallpaper lives in the right half, so this is the width at
     which the form can move aside and let it show. */
  .hero { min-height: calc(100vh - 62px - 220px); padding: clamp(32px, 8vw, 88px) 0; }
  .hero .panel { max-width: 560px; }
  .services { max-width: 560px; }
}

/* ------------------------------------------------------------- utilities */
/* These exist because the site's own Content-Security-Policy is
   `style-src 'self'`, which blocks style attributes as well as <style>
   elements. Anything that would have been an inline style is a class. */

.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 10px; }
.mt-2 { margin-top: 16px; }
.mt-3 { margin-top: 22px; }
.mt-4 { margin-top: 28px; }
.narrow { max-width: 520px; }
.narrow-s { max-width: 440px; }
.narrow-xs { max-width: 420px; }
.measure { max-width: 62ch; }
.minw { min-width: 260px; }
.anywhere { overflow-wrap: anywhere; }
.nowrap { white-space: nowrap; }
.inline-flex { display: inline-flex; }
.indent { margin-left: 18px; }
.tight { padding: 2px 0; }
.flush { margin: 0; padding: 0; }
.spread { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.cell-url {
  display: inline-block; max-width: 34ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: bottom;
}

/* Bar widths. A statistic is a number per render, so the width cannot come from
   a stylesheet rule written ahead of time — but it can come from one of 101,
   which is how the bars stay pixel-accurate under a strict CSP with no inline
   style, no nonce and no script. */
.w0{width:0%}
.w1{width:1%}
.w2{width:2%}
.w3{width:3%}
.w4{width:4%}
.w5{width:5%}
.w6{width:6%}
.w7{width:7%}
.w8{width:8%}
.w9{width:9%}
.w10{width:10%}
.w11{width:11%}
.w12{width:12%}
.w13{width:13%}
.w14{width:14%}
.w15{width:15%}
.w16{width:16%}
.w17{width:17%}
.w18{width:18%}
.w19{width:19%}
.w20{width:20%}
.w21{width:21%}
.w22{width:22%}
.w23{width:23%}
.w24{width:24%}
.w25{width:25%}
.w26{width:26%}
.w27{width:27%}
.w28{width:28%}
.w29{width:29%}
.w30{width:30%}
.w31{width:31%}
.w32{width:32%}
.w33{width:33%}
.w34{width:34%}
.w35{width:35%}
.w36{width:36%}
.w37{width:37%}
.w38{width:38%}
.w39{width:39%}
.w40{width:40%}
.w41{width:41%}
.w42{width:42%}
.w43{width:43%}
.w44{width:44%}
.w45{width:45%}
.w46{width:46%}
.w47{width:47%}
.w48{width:48%}
.w49{width:49%}
.w50{width:50%}
.w51{width:51%}
.w52{width:52%}
.w53{width:53%}
.w54{width:54%}
.w55{width:55%}
.w56{width:56%}
.w57{width:57%}
.w58{width:58%}
.w59{width:59%}
.w60{width:60%}
.w61{width:61%}
.w62{width:62%}
.w63{width:63%}
.w64{width:64%}
.w65{width:65%}
.w66{width:66%}
.w67{width:67%}
.w68{width:68%}
.w69{width:69%}
.w70{width:70%}
.w71{width:71%}
.w72{width:72%}
.w73{width:73%}
.w74{width:74%}
.w75{width:75%}
.w76{width:76%}
.w77{width:77%}
.w78{width:78%}
.w79{width:79%}
.w80{width:80%}
.w81{width:81%}
.w82{width:82%}
.w83{width:83%}
.w84{width:84%}
.w85{width:85%}
.w86{width:86%}
.w87{width:87%}
.w88{width:88%}
.w89{width:89%}
.w90{width:90%}
.w91{width:91%}
.w92{width:92%}
.w93{width:93%}
.w94{width:94%}
.w95{width:95%}
.w96{width:96%}
.w97{width:97%}
.w98{width:98%}
.w99{width:99%}
.w100{width:100%}
