/* TabbySync — Copyright (c) 2026 Ryan Gulliver. All rights reserved.
   Personal, non-commercial use only. No redistribution. See LICENSE. */

/* style.css — the marketing site's entire design system.
 *
 * No CSS framework, no icon font, no web font. System fonts and inline SVG
 * only, so the page makes zero third-party network requests — a small,
 * deliberate way for "no tracking" to be literally true on this page too,
 * not just in the extension it's advertising.
 *
 * Palette matches the extension's own popup/options/privacy pages, so the
 * site and the product feel like the same thing. Bookmarks = blue accent,
 * Tabs = orange accent — same split used inside the extension's own UI.
 */

:root {
  --bg: #ffffff;
  --panel: #f7f8fa;
  --panel-2: #eef1f5;
  --line: #e3e6ea;
  --text: #1c2330;
  --muted: #5b6472;
  --accent: #2563eb;
  --accent-ink: #ffffff;
  --accent-2: #f97316; /* tabs orange, matches the extension's own tabs card */
  --err: #dc2626;
  --shadow: 0 1px 2px rgba(20, 24, 33, .04), 0 8px 24px rgba(20, 24, 33, .06);
  --radius: 14px;
  --maxw: 1120px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #14171d;
    --panel: #1b1f27;
    --panel-2: #232833;
    --line: #2c313c;
    --text: #e8eaed;
    --muted: #9aa2af;
    --accent: #5b8def;
    --accent-ink: #0b1220;
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 12px 32px rgba(0, 0, 0, .35);
  }
}
:root[data-theme="dark"] {
  --bg: #14171d;
  --panel: #1b1f27;
  --panel-2: #232833;
  --line: #2c313c;
  --text: #e8eaed;
  --muted: #9aa2af;
  --accent: #5b8def;
  --accent-ink: #0b1220;
  --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 12px 32px rgba(0, 0, 0, .35);
}
:root[data-theme="dark"], :root:not([data-theme="light"]) { color-scheme: light dark; }

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code {
  font: 500 .9em/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: .15em .4em;
}

.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 1000;
  background: var(--accent); color: var(--accent-ink);
  padding: 10px 16px; border-radius: 0 0 8px 0;
}
.skip-link:focus { left: 0; }

/* ---------------------------------------------------------------- header */

.site-header {
  position: sticky; top: 0; z-index: 100;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px) saturate(1.2);
  border-bottom: 1px solid var(--line);
}
.header-inner { display: flex; align-items: center; gap: 20px; height: 64px; }
.brand { display: flex; align-items: center; margin-right: auto; }
.brand-logo { height: 28px; width: auto; display: none; }
.logo-light { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logo-light { display: none; }
  :root:not([data-theme="light"]) .logo-dark  { display: block; }
}
:root[data-theme="dark"]  .logo-light { display: none; }
:root[data-theme="dark"]  .logo-dark  { display: block; }
:root[data-theme="light"] .logo-light { display: block; }
:root[data-theme="light"] .logo-dark  { display: none; }
.footer-brand .brand-logo { height: 24px; margin-bottom: 10px; }

.site-nav { display: flex; gap: 26px; }
.site-nav a { color: var(--text); font-weight: 500; font-size: 14.5px; }
.site-nav a:hover { color: var(--accent); text-decoration: none; }

.header-actions { display: flex; align-items: center; gap: 6px; }

.theme-toggle, .nav-toggle {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 10px;
  background: transparent; border: 1px solid transparent; cursor: pointer;
  color: var(--text);
}
.theme-toggle:hover, .nav-toggle:hover { background: var(--panel); border-color: var(--line); }
.theme-toggle .icon { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.icon-moon { display: none; }
:root[data-theme="dark"] .icon-sun,
:root:not([data-theme="light"]) .icon-sun { display: none; }
:root[data-theme="dark"] .icon-moon,
:root:not([data-theme="light"]) .icon-moon { display: block; }
@media (prefers-color-scheme: light) { :root:not([data-theme]) .icon-sun { display: block; } :root:not([data-theme]) .icon-moon { display: none; } }

.nav-toggle { display: none; flex-direction: column; gap: 4px; }
.nav-toggle span { width: 18px; height: 2px; background: currentColor; border-radius: 2px; transition: transform .2s, opacity .2s; }

/* ------------------------------------------------------------------ hero */

.hero { position: relative; overflow: clip; padding: 88px 0 96px; }
.hero-bg {
  position: absolute; inset: -20% -10% auto -10%; height: 620px; z-index: -1;
  background:
    radial-gradient(520px 320px at 15% 10%, color-mix(in srgb, var(--accent) 30%, transparent), transparent 70%),
    radial-gradient(480px 300px at 85% 25%, color-mix(in srgb, var(--accent-2) 22%, transparent), transparent 70%);
  filter: blur(10px);
}
.hero-inner { text-align: center; max-width: 780px; }
.eyebrow {
  display: inline-block; font-size: 13px; font-weight: 600; letter-spacing: .02em;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  padding: 6px 14px; border-radius: 999px; margin-bottom: 22px;
}
.hero h1 { font-size: clamp(32px, 5vw, 52px); line-height: 1.12; margin: 0 0 20px; letter-spacing: -.02em; }
.hero h1 .accent { color: var(--accent); }
.hero-lede { font-size: 18px; color: var(--muted); margin: 0 auto 32px; max-width: 640px; }
.hero-actions { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
.hero-note { margin-top: 18px; font-size: 13.5px; color: var(--muted); }

.btn {
  display: inline-flex; align-items: center; gap: 9px;
  font-weight: 600; font-size: 15px; padding: 13px 24px; border-radius: 11px;
  border: 1px solid transparent; cursor: pointer; transition: transform .15s, box-shadow .15s, background .15s;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn .icon { width: 18px; height: 18px; fill: currentColor; }
.btn-primary { background: var(--accent); color: var(--accent-ink); box-shadow: var(--shadow); }
.btn-primary:hover { box-shadow: 0 6px 20px color-mix(in srgb, var(--accent) 45%, transparent); }
.btn-ghost { background: var(--panel); color: var(--text); border-color: var(--line); }
.btn-ghost:hover { background: var(--panel-2); }

/* ------------------------------------------------------- download row */

/* The three ways to get TabbySync — Chromium, Firefox, Windows — as three
   equal cards rather than one button that guesses. Every card is a real link
   in the delivered HTML; main.js only promotes the one that matches the
   visitor's browser (.is-detected + the badge). Nothing is ever hidden, so a
   wrong guess costs a highlight, not a download.

   Equal width by default, on purpose: the extension and the desktop app are
   not the same kind of thing, but they are all "get it here", and ranking one
   of the two stores above the other would be a claim about the visitor that
   the row deliberately does not make. */
.download-row {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px; margin: 0 auto; max-width: 780px; text-align: left;
}
.dl-card {
  position: relative;
  display: flex; align-items: center; gap: 13px;
  padding: 16px 18px; border-radius: var(--radius);
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); box-shadow: var(--shadow);
  transition: transform .15s, box-shadow .15s, background .15s, border-color .15s;
}
.dl-card:hover {
  text-decoration: none; transform: translateY(-2px);
  background: var(--bg); border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  box-shadow: 0 4px 8px rgba(20, 24, 33, .06), 0 14px 30px rgba(20, 24, 33, .10);
}
.dl-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.dl-icon { flex: none; width: 34px; height: 34px; color: var(--muted); }
.dl-icon svg {
  width: 100%; height: 100%;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
/* The Windows mark is four filled panes, not an outline like the other two. */
.dl-icon-solid svg { fill: currentColor; stroke: none; }

.dl-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.dl-text strong { font-size: 15px; letter-spacing: -.01em; }
.dl-sub { font-size: 12.5px; color: var(--muted); line-height: 1.35; }

/* The one main.js touches. Colour and lift, not size: a card that grew on
   detection would reflow the row under the visitor as the script runs. */
.dl-card.is-detected {
  background: var(--bg);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent), var(--shadow);
}
.dl-card.is-detected .dl-icon { color: var(--accent); }

.dl-badge {
  position: absolute; top: -9px; right: 12px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--accent-ink); background: var(--accent);
  padding: 3px 8px; border-radius: 999px; white-space: nowrap;
}

/* ----------------------------------------------------------- section base */

section { padding: 88px 0; }
.section-title { font-size: clamp(24px, 3.4vw, 34px); letter-spacing: -.01em; margin: 0 0 14px; text-align: center; }
.section-lede { color: var(--muted); font-size: 17px; text-align: center; max-width: 620px; margin: 0 auto 48px; }

/* -------------------------------------------------------------- features */

.features { background: var(--panel); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
/* An odd card count leaves the last row short. A single card stranded against
   the left edge reads as a layout bug rather than a deliberate stop, so centre
   it; a pair is left as-is, since two of three columns still reads as a row. */
.feature-card:last-child:nth-child(3n + 1) { grid-column: 2; }
.feature-card {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 26px; box-shadow: var(--shadow);
  transition: transform .18s, box-shadow .18s;
}
.feature-card:hover { transform: translateY(-3px); box-shadow: 0 4px 8px rgba(20,24,33,.06), 0 16px 32px rgba(20,24,33,.10); }
.feature-icon {
  width: 44px; height: 44px; border-radius: 11px; margin-bottom: 16px;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
.feature-icon svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; stroke-linecap: round; }
.feature-blue .feature-icon  { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
.feature-orange .feature-icon { background: color-mix(in srgb, var(--accent-2) 16%, transparent); color: var(--accent-2); }
.feature-card h3 { font-size: 17px; margin: 0 0 8px; }
.feature-card p { margin: 0; color: var(--muted); font-size: 14.5px; }

/* ------------------------------------------------------------ screenshots */

/* Real captures of the extension (docs/screenshots, regenerated by
   scripts/screenshots.mjs). Every shot ships a light and a dark file and swaps
   them on the same data-theme rules the logo uses, so the pictures match the
   theme the visitor is reading the page in.

   The tab list and Options are full-width browser pages: shrunk into a column
   beside their caption, their own UI text stops being legible, so those get a
   caption stacked above a full-measure figure. Only the popup — a 320px-wide
   window — is narrow enough to sit beside its copy. */
.shot-row, .shot-block { padding: 40px 0; border-bottom: 1px solid var(--line); }
.shot-row:last-child, .shot-block:last-child { border-bottom: none; padding-bottom: 0; }
.shot-row { display: grid; grid-template-columns: 1.5fr 1fr; gap: 44px; align-items: center; }
.shot-copy h3 { font-size: 20px; margin: 0 0 10px; letter-spacing: -.01em; }
.shot-copy p { margin: 0 0 10px; color: var(--muted); font-size: 15px; }
.shot-note { font-size: 13.5px; }
.shot-block .shot-copy { max-width: 78ch; margin: 0 auto 22px; text-align: center; }
.shot-figure {
  margin: 0; border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; background: var(--panel); box-shadow: var(--shadow);
}
.shot-figure-narrow { max-width: 300px; margin-inline: auto; }
.shot-figure img { display: block; width: 100%; height: auto; }

/* Same swap the header logo uses — see .logo-light / .logo-dark above. */
.shot-figure .shot-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .shot-figure .shot-light { display: none; }
  :root:not([data-theme="light"]) .shot-figure .shot-dark  { display: block; }
}
:root[data-theme="dark"]  .shot-figure .shot-light { display: none; }
:root[data-theme="dark"]  .shot-figure .shot-dark  { display: block; }
:root[data-theme="light"] .shot-figure .shot-light { display: block; }
:root[data-theme="light"] .shot-figure .shot-dark  { display: none; }

/* ------------------------------------------------------------------ how */

.steps { list-style: none; margin: 0 auto; padding: 0; max-width: 700px; counter-reset: step; }
.steps li { display: flex; gap: 20px; padding: 22px 0; border-bottom: 1px solid var(--line); }
.steps li:last-child { border-bottom: none; }
.step-num {
  flex: none; width: 36px; height: 36px; border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px;
}
.steps h3 { margin: 0 0 4px; font-size: 16px; }
.steps p { margin: 0; color: var(--muted); font-size: 14.5px; }

/* -------------------------------------------------------------- privacy */

.privacy { background: var(--panel); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.privacy-inner { display: grid; grid-template-columns: 1.3fr 1fr; gap: 56px; align-items: center; }
.privacy .section-title, .privacy .section-lede { text-align: left; margin-left: 0; }
.check-list { list-style: none; margin: 0 0 28px; padding: 0; display: grid; gap: 12px; }
.check-list li { position: relative; padding-left: 30px; font-size: 15px; }
.check-list li::before {
  content: ""; position: absolute; left: 0; top: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 10px 2px, 2px 10px; background-repeat: no-repeat;
  background-position: center;
}
.check-list li::after {
  content: "\2713"; position: absolute; left: 3.5px; top: 1px; font-size: 12px; font-weight: 800; color: var(--accent);
}
.privacy-visual { display: flex; justify-content: center; }
.shield-art { width: 200px; height: 200px; fill: color-mix(in srgb, var(--accent) 10%, transparent); stroke: var(--accent); stroke-width: 3; }
.shield-check { fill: none; stroke-width: 6; stroke-linecap: round; stroke-linejoin: round; }

/* -------------------------------------------------------------- install */

.install-inner { text-align: center; max-width: 640px; }
.install-steps { text-align: left; max-width: 460px; margin: 0 auto 34px; padding-left: 22px; color: var(--muted); font-size: 15px; }
.install-steps li { margin: 10px 0; }
.install-steps strong { color: var(--text); }

.install-inner .hero-actions { margin-bottom: 28px; }
.install-alt { max-width: 520px; margin: 0 auto; text-align: left; }
.install-alt summary { cursor: pointer; color: var(--muted); font-size: 14.5px; }
.install-alt summary:hover { color: var(--accent); }
.install-alt .install-steps { margin-top: 14px; margin-bottom: 8px; }

/* --------------------------------------------------------------- reCAPTCHA */

/* Google's floating badge is hidden, which its terms allow as long as the
   notice it stands for appears instead — .recaptcha-note under the form does
   that, in words rather than in a logo that says nothing about what is being
   sent where. Hiding it here also means the badge staying unstyled (if a
   browser ever ignores the CSP nonce on Google's injected <style>) is
   invisible rather than ugly. */
.grecaptcha-badge { visibility: hidden; }

.recaptcha-note {
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
}

/* ------------------------------------------------------------------ footer */

.site-footer { border-top: 1px solid var(--line); padding: 64px 0 0; background: var(--panel); }
.footer-inner { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 32px; padding-bottom: 40px; }
.footer-brand p { color: var(--muted); font-size: 14px; max-width: 240px; }
.footer-col h3 { font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 14px; }
.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.footer-col a { color: var(--text); font-size: 14.5px; }
.footer-note { color: var(--muted); font-size: 13px; }
.footer-legal {
  border-top: 1px solid var(--line); padding: 20px 24px; display: flex; justify-content: space-between; gap: 16px;
  color: var(--muted); font-size: 13px; flex-wrap: wrap;
}
.footer-legal p { margin: 0; }
.footer-version { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------- reveal-in
 *
 * Content is fully visible by default — this is progressive enhancement,
 * not a requirement. Only main.js opts an element INTO the hidden state
 * (via .reveal-armed, added only to elements not already on screen), so a
 * page with JavaScript blocked or slow to load never has invisible content.
 */
.reveal-armed { opacity: 0; transform: translateY(14px); transition: opacity .5s ease, transform .5s ease; }
.reveal-armed.is-visible { opacity: 1; transform: none; }

/* ------------------------------------------------------------- responsive */

@media (max-width: 880px) {
  .feature-grid { grid-template-columns: 1fr 1fr; }
  /* Two columns and an odd number of cards leaves a lone one in the last row;
     see the three-column version of this rule beside .feature-grid above. */
  .feature-card:last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* Stacked, the copy always reads before the picture it describes. */
  .shot-row { grid-template-columns: 1fr; gap: 26px; }
  .shot-block .shot-copy { text-align: left; }
  .privacy-inner { grid-template-columns: 1fr; gap: 36px; }
  .privacy .section-title, .privacy .section-lede { text-align: center; }
  .privacy-copy { order: 2; }
  .privacy-visual { order: 1; }
  .footer-inner { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 700px) {
  .site-nav {
    position: absolute; top: 64px; left: 0; right: 0;
    background: var(--bg); border-bottom: 1px solid var(--line);
    flex-direction: column; gap: 0; padding: 8px 24px 16px;
    transform: translateY(-8px); opacity: 0; pointer-events: none; transition: transform .18s, opacity .18s;
  }
  .site-nav.is-open { transform: translateY(0); opacity: 1; pointer-events: auto; }
  .site-nav a { padding: 12px 0; border-bottom: 1px solid var(--line); }
  .site-nav a:last-child { border-bottom: none; }
  .nav-toggle { display: flex; }
  .nav-toggle.is-open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  .nav-toggle.is-open span:nth-child(2) { opacity: 0; }
  .nav-toggle.is-open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  .feature-grid { grid-template-columns: 1fr; }
  /* One column: every card is already full width, so the orphan rules above
     have nothing left to correct and would only add a redundant span. */
  .feature-card:last-child:nth-child(odd) { grid-column: auto; }
  /* Three cards side by side stop being tappable targets long before the text
     stops fitting. Full-width rows instead, in the delivered order. */
  .download-row { grid-template-columns: 1fr; max-width: 420px; }
  .footer-inner { grid-template-columns: 1fr; gap: 28px; }
  section { padding: 60px 0; }
  .hero { padding: 56px 0 64px; }
}

/* ===========================================================================
   Interior pages — /privacy and /contact
   ---------------------------------------------------------------------------
   The landing page is one long scroll of full-width sections; these two are
   ordinary documents. Same tokens, narrower measure, no third-party anything.
   =========================================================================== */

.page-hero {
  padding: 64px 0 40px;
  background: linear-gradient(180deg, var(--panel) 0%, var(--bg) 100%);
  border-bottom: 1px solid var(--line);
}
.page-hero h1 { font-size: clamp(28px, 4vw, 40px); line-height: 1.15; margin: 0 0 14px; letter-spacing: -.02em; }
.page-lede { font-size: 17px; color: var(--muted); max-width: 60ch; margin: 0; }

/* Long-form text wants a measure a reader can actually track along. */
.content-narrow { max-width: 78ch; }

.policy-section,
.contact-section { padding: 44px 0 72px; }

/* ---- the policy body ---------------------------------------------------- */
/* Descendant selectors on purpose: most of this markup is injected verbatim
   from the extension's own privacy.html, which carries no site classes. */
.policy { font-size: 15.5px; line-height: 1.7; }
.policy h2 {
  font-size: 20px; margin: 38px 0 12px; padding-bottom: 8px;
  border-bottom: 1px solid var(--line); letter-spacing: -.01em;
}
.policy h2:first-child { margin-top: 0; }
.policy p, .policy li { color: var(--text); }
.policy ul { padding-left: 22px; margin: 12px 0; }
.policy li { margin: 6px 0; }
.policy a { text-decoration: underline; }

.policy table { border-collapse: collapse; width: 100%; margin: 16px 0; font-size: 14.5px; }
.policy th, .policy td {
  border: 1px solid var(--line); padding: 10px 12px; text-align: left; vertical-align: top;
}
.policy th { background: var(--panel); font-weight: 600; }

/* Wide tables scroll inside their own box rather than pushing the page sideways. */
.policy-table-wrap { overflow-x: auto; }

.callout {
  background: var(--panel); border: 1px solid var(--line);
  border-left: 4px solid var(--accent); border-radius: 10px;
  padding: 14px 18px; margin: 20px 0;
}
.callout-warn, .callout.warn { border-left-color: var(--err); }

.github-note {
  display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center;
  justify-content: space-between; background: var(--panel-2);
  border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 16px; margin-bottom: 28px; font-size: 14px; color: var(--muted);
}

/* ---- the contact form --------------------------------------------------- */
.form-grid { display: flex; flex-direction: column; gap: 18px; margin: 0 0 26px; }
.form-row { display: flex; flex-direction: column; gap: 6px; }
.form-row label { font-size: 14px; font-weight: 600; }

.form-row.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 640px) { .form-row.two-col { grid-template-columns: 1fr; } }

.form-grid input[type="text"],
.form-grid input[type="email"],
.form-grid select,
.form-grid textarea {
  width: 100%; font: inherit; color: var(--text);
  background: var(--bg); border: 1px solid var(--line);
  border-radius: 10px; padding: 11px 13px;
}
.form-grid textarea { min-height: 190px; resize: vertical; }
.form-grid :is(input, select, textarea):focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
.form-grid :is(input, select, textarea)[aria-invalid="true"] { border-color: var(--err); }

.field-error { color: var(--err); font-size: 13.5px; margin: 0; }

.notice {
  border: 1px solid var(--line); border-left: 4px solid var(--accent);
  background: var(--panel); border-radius: 10px;
  padding: 14px 18px; margin: 0 0 26px;
}
.notice.success { border-left-color: #16a34a; }
.notice.error { border-left-color: var(--err); }
.notice-detail { display: block; margin-top: 8px; font-size: 13.5px; color: var(--muted); }

.contact-alt { color: var(--muted); font-size: 14.5px; }

/* The spam trap. Real visitors never see it, so they never fill it in; a bot
   reading the markup does. It must stay hidden from assistive technology as
   well, or a screen-reader user fills it in and gets silently discarded --
   which is why this rule is load-bearing, not decoration. */
.honeypot {
  position: absolute !important;
  left: -9999px !important;
  width: 1px !important; height: 1px !important;
  opacity: 0 !important;
  pointer-events: none !important;
}
