:root {
  --theme-control-size: 44px;
  --theme-focus: rgba(0, 104, 232, .5);
}

/* The shared design layer. theme.css is the one stylesheet every surface links - console and
   public alike - so the tokens cross-surface components read are declared here, once.

   They used to live in tailwind-input.css, which compiles to tailwind.css, which the public
   bundle strips. Anything shared that read them was therefore unstyled on a public page, and
   silently: an undefined custom property makes its declaration invalid at computed-value time,
   which is `unset`, which is not an error. That is how /contact shipped a form whose fields had
   no fill, no ring, no padding and no height.

   Tailwind still needs the colours inside its own @theme block to generate console utilities, so
   that copy stays. check-design-directive-contract.mjs asserts the two agree exactly, which turns
   drift into a failed build instead of a surface that quietly loses its styling. */
:root {
  --color-canvas: #ffffff;
  --color-surface: #ffffff;
  --color-surface-sunken: #e9eef6;
  --color-ink: #0b0d12;
  --color-muted: #4f5b6b;
  --color-line: #dbe2ec;
  --color-brand: #0066dd;
  --color-brand-signal: #1473ff;
  --color-brand-deep: #004db8;
  --color-brand-electric: #3b82f6;
  --color-brand-soft: #e5f1ff;
  --color-info: #0066dd;
  --color-info-soft: #e5f1ff;
  --color-success: #3f6212;
  --color-success-soft: #ecfccb;
  --color-warning: #574600;
  --color-warning-soft: #fef9c3;
  --color-relay: #3f6212;
  --color-relay-soft: #ecfccb;
  --color-attention: #574600;
  --color-attention-soft: #fef9c3;
  --color-danger: #c0262d;
  --color-danger-soft: #fff1f2;
  --color-slate-50: #f7f8fa;
  --color-slate-100: #eef1f4;
  --color-slate-200: #d7dde5;
  --color-slate-300: #b8c1cc;
  --color-slate-400: #7d8998;
  --color-slate-500: #4f5b6b;
  --color-blue-200: #bfdbfe;
  --color-blue-300: #93c5fd;
  --color-blue-900: #1e3a8a;
  --color-emerald-50: #f7fee7;
  --color-emerald-100: #ecfccb;
  --color-emerald-200: #d9f99d;
  --color-emerald-800: #3f6212;
  --color-emerald-900: #365314;
  --color-amber-200: #fde047;
  --color-amber-800: #713f12;
  --color-amber-900: #574600;
  --color-red-100: #ffe4e6;
  --color-red-200: #fecdd3;

  --control-height: 44px;
  --control-height-compact: 36px;
  --control-radius: 8px;
  --control-padding-x: 12px;
  --control-font-size: 14px;
  --control-font-weight: 400;
  --control-label-size: 13px;
  --control-help-size: 12px;
  --control-ring: inset 0 0 0 1px var(--color-line);
  --control-ring-hover: inset 0 0 0 1px var(--color-slate-300);
  --control-ring-focus: inset 0 0 0 1px var(--color-brand), 0 0 0 3px var(--control-focus-halo);
  --control-ring-invalid: inset 0 0 0 1px var(--color-danger), 0 0 0 3px var(--control-invalid-halo);
  --control-placeholder: #6b7686;
  --control-leading-icon: 18px;
  --control-leading-indent: calc(var(--control-padding-x) + var(--control-leading-icon) + 8px);
  --control-focus-halo: rgba(0, 111, 238, .12);
  --control-invalid-halo: rgba(192, 38, 45, .1);
  --menu-radius: 12px;
  --menu-padding: 6px;
  --menu-gap: 2px;
  --option-height: 44px;
  --option-radius: 8px;
  --option-padding-x: 10px;
}

@media (pointer: coarse) {
  :root {
    --control-height: 48px;
    --control-height-compact: 40px;
    --control-padding-x: 14px;
    --control-font-size: 16px;
    --control-label-size: 14px;
    --control-help-size: 13px;
    --option-height: 48px;
    --option-padding-x: 12px;
  }
}

html[data-theme="light"] {
  color-scheme: light;
}

html[data-theme="dark"] {
  color-scheme: dark;
  --bg: #050607;
  --bg-soft: #101216;
  --bg-sky: #0c1d31;
  --surface: rgba(17, 19, 24, .96);
  --surface-strong: #191c22;
  --surface-blue: #102a43;
  --text: #f7f9fc;
  --muted: #b2bac6;
  --subtle: #8c97a6;
  --line: #343942;
  --line-strong: #525a67;
  --blue: #55a7ff;
  --blue-strong: #bfdbfe;
  --blue-soft: #102a43;
  --cyan: #38bdf8;
  --green: #c7f85b;
  --amber: #ffd426;
  --red: #ff8590;
  --white: #ffffff;
  --shadow-soft: 0 20px 58px rgba(0, 0, 0, .34);
  --shadow-card: 0 12px 32px rgba(0, 0, 0, .28);
  --pixel-shadow-sm: 0 1px 3px rgba(2, 8, 20, .28);
  --pixel-shadow-md: 0 8px 24px rgba(2, 8, 20, .28);
  --pixel-shadow-lg: 0 14px 38px rgba(0, 0, 0, .3);
  --pixel-shadow-blue: 0 8px 22px rgba(31, 111, 235, .2);
  --pixel-shadow-hover: 0 12px 30px rgba(31, 111, 235, .24);

  /* Dark elevation ink. A cast alone is nearly invisible on a near-black canvas,
     so the rim catches light along the top edge the way a lifted surface would. */
  --elevation-contact: rgba(0, 0, 0, .55);
  --elevation-ambient: rgba(0, 0, 0, .45);
  --elevation-ambient-strong: rgba(0, 0, 0, .58);
  --elevation-cast: rgba(0, 0, 0, .72);
  --elevation-hairline: transparent;
  --elevation-rim: rgba(255, 255, 255, .05);

  /* Control halos need more presence on a dark canvas to read at all. */
  --control-placeholder: #8b95a5;
  --control-focus-halo: rgba(85, 167, 255, .22);
  --control-invalid-halo: rgba(255, 154, 163, .18);

  --editorial-line: #343942;
  --editorial-paper: #15171b;
  /* The same two tokens the light theme sets, so the masthead's paint changes and its geometry
     does not. The wash carries slightly more presence here for the same reason every control halo
     does: it has to read against a dark ground. */
  --editorial-hero-paper: #15171b;
  --editorial-hero-wash: rgba(120, 152, 196, .16);
  --editorial-canvas: #050607;
  --editorial-amber: #fef08a;
  --editorial-shadow: 0 20px 54px rgba(2, 8, 20, .38);

  --color-canvas: #050607;
  --color-surface: #16191f;
  --color-surface-sunken: #101318;
  --color-ink: #f7f9fc;
  --color-muted: #b2bac6;
  --color-line: #343942;
  --color-brand: #55a7ff;
  --color-brand-signal: #86c5ff;
  --color-brand-deep: #bfdbfe;
  --color-brand-electric: #9ed1ff;
  --color-brand-soft: #102a43;
  /* The ramp inverts: on a near-black canvas the darkest blue is the one that disappears, so
     intensity climbs towards light. Unreported land sits just above the canvas, not below it. */
  --map-intensity-0: #262c36;
  --map-intensity-1: #1e4a80;
  --map-intensity-2: #2f70bd;
  --map-intensity-3: #4b96e8;
  --map-intensity-4: #86c5ff;
  --map-outline: rgba(5, 6, 7, .55);
  /* Same three meters, lifted off a near-black canvas: the light theme's values are chosen to
     stay dark enough to read as fills on white, which is exactly what disappears here. */
  --chart-accepted: #55a7ff;
  --chart-google: #c7f85b;
  --chart-retries: #ffc14d;
  /* Its own token, decoupled from the shared sunken-surface one: the ocean stays the darkest
     thing on the map even if that shared token's value ever moves for unrelated surfaces. */
  --map-ocean: #101318;
  --color-info: #86c5ff;
  --color-info-soft: #102a43;
  --color-success: #c7f85b;
  --color-success-soft: #26350d;
  --color-warning: #ffe45c;
  --color-warning-soft: #3d3808;
  --color-relay: #c7f85b;
  --color-relay-soft: #26350d;
  --color-attention: #ffe45c;
  --color-attention-soft: #3d3808;
  --color-danger: #ff9aa3;
  --color-danger-soft: #431a20;
  --color-white: #16191f;
  --color-slate-50: #101318;
  --color-slate-100: #202329;
  --color-slate-200: #343942;
  --color-slate-300: #525a67;
  --color-slate-400: #8c97a6;
  --color-slate-500: #b2bac6;
  --color-blue-200: #264e7a;
  --color-blue-300: #3c6eaa;
  --color-blue-900: #bedcff;
  --color-emerald-50: #26350d;
  --color-emerald-100: #304113;
  --color-emerald-200: #4d641c;
  --color-emerald-800: #d9f99d;
  --color-emerald-900: #ecfccb;
  --color-amber-200: #66550a;
  --color-amber-800: #fde047;
  --color-amber-900: #fef9c3;
  --color-red-100: #4b2228;
  --color-red-200: #74343d;
  --color-indigo-200: #485887;

  /* Drawn atmosphere and public glass. Dark repaints the same three layers the light
     theme draws — no second asset, no geometry change, one frame to swap. */
  --atmos-canvas: #0a0e16;
  --atmos-pool-blue: rgba(66, 133, 244, 0.14);
  --atmos-pool-green: rgba(16, 185, 129, 0.09);
  --atmos-dot: #232b38;
  --atmos-dot-opacity: 0.7;
  --atmos-conduit: rgba(96, 165, 250, 0.14);
  --atmos-conduit-strong: rgba(16, 185, 129, 0.18);
  --atmos-particle: rgba(96, 165, 250, 0.42);
  --atmos-veil: rgba(5, 12, 26, .46);
  --atmos-veil-strong: rgba(6, 14, 30, .62);

  --header-glass: rgba(10, 18, 32, .58);
  --header-solid: rgba(10, 18, 32, .95);
  --footer-veil-0: rgba(5, 12, 26, 0);
  --footer-veil-1: rgba(5, 12, 26, .58);
  --footer-veil-2: rgba(5, 12, 26, .96);
  --glass-fill: rgba(22, 25, 31, .68);
  --glass-fill-strong: rgba(22, 25, 31, .86);
  --glass-fill-quiet: rgba(16, 19, 24, .48);
  --glass-stroke: rgba(146, 173, 214, .16);
  --glass-edge: rgba(0, 0, 0, .48);
  --glass-highlight: rgba(190, 216, 255, .12);
  --glass-saturate: 1.2;

  --demo-glass-fill: rgba(22, 25, 31, 0.82);
  --demo-glass-border: rgba(255, 255, 255, 0.06);
  --demo-glass-outline: rgba(146, 173, 214, 0.16);
  --demo-glass-shadow:
      0 24px 48px -12px rgba(0, 0, 0, 0.55),
      0 4px 12px -2px rgba(0, 0, 0, 0.35);

  --glow-route: rgba(84, 166, 255, .72);
  --glow-route-soft: rgba(84, 166, 255, .26);
  /* Matched to light by measured weight, not by number. Both rings land at a contrast ratio
     of ~1.78 against their own card face, which takes .40 in light and .32 here — a bright
     blue on near-black gains contrast far faster than a mid blue on near-white, so the dark
     ring needs *less* alpha, not more. Picking the numbers by eye is what produced the
     mismatch this replaced. The lift is cast in black rather than blue because a blue shadow
     on a dark ground reads as fog. */
  --card-hover-ring: rgba(84, 166, 255, .32);
  --card-hover-lift: 0 14px 30px -14px rgba(0, 0, 0, .72);
  --relay-entry-mark: #bfe0ff;
  --relay-wire-strong: rgba(150, 190, 242, .58);
  --relay-port-fill: #0d1728;
  --relay-packet-halo: rgba(124, 196, 255, .38);
  --relay-packet-core: #ffffff;
  --relay-signal: #7cc4ff;
  --relay-land: #34d399;
  --relay-land-soft: rgba(52, 211, 153, .22);
  --relay-entry-flash: rgba(96, 176, 255, .46);
  --relay-packet: #9ed4ff;
  --relay-wire: rgba(150, 190, 242, .40);
  --relay-wire-idle: rgba(150, 190, 242, .24);
  --card-badge-fill: rgba(150, 185, 235, .10);
  --card-badge-fill-active: rgba(84, 166, 255, .22);
  --glow-relay: rgba(199, 248, 91, .40);

}

.theme-toggle {
  width: var(--theme-control-size);
  min-width: var(--theme-control-size);
  height: var(--theme-control-size);
  min-height: var(--theme-control-size);
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text, var(--editorial-ink, var(--color-ink)));
  background: transparent;
  box-shadow: none;
  cursor: pointer;
  transition: background-color 160ms ease, color 160ms ease;
}

.theme-toggle:hover {
  color: var(--blue, var(--editorial-brand, var(--color-brand)));
  background: var(--bg-soft, var(--color-slate-100, rgba(8, 103, 232, .08)));
}

.theme-toggle:focus-visible {
  outline: 3px solid var(--theme-focus);
  outline-offset: 3px;
}

.theme-toggle svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.theme-toggle :is(.theme-toggle__sun, .theme-toggle__moon) {
  transform-origin: center;
  transition: opacity 160ms ease, transform 160ms ease;
}

.theme-toggle__moon,
.theme-toggle[data-theme-state="dark"] .theme-toggle__sun {
  opacity: 0;
  transform: scale(.65) rotate(-20deg);
}

[data-theme-state="dark"] .theme-toggle__moon {
  opacity: 1;
  transform: scale(1) rotate(0);
}

/* Which icon to draw is decided by the document, not by the button.

   `data-theme-state` is set on the button by assets/theme-control.js, which runs after first
   paint - so in dark mode the button painted the sun and then animated to the moon through the
   160ms transition above. On a multi-page site that is a blink on every navigation, and no
   layout-shift measurement can see it because nothing moves.

   `data-theme` is on <html>, written synchronously by assets/theme-init.js before any style is
   applied, so these two rules pick the right icon in the first frame with no script involved.
   The button-level selectors stay for any surface that still sets only those. */
:root[data-theme="dark"] .theme-toggle .theme-toggle__sun {
  opacity: 0;
  transform: scale(.65) rotate(-20deg);
}

:root[data-theme="dark"] .theme-toggle .theme-toggle__moon {
  opacity: 1;
  transform: scale(1) rotate(0);
}

.theme-toggle--floating {
  position: fixed;
  z-index: 90;
  top: 14px;
  right: 14px;
}

html[data-theme="dark"] .theme-toggle {
  --theme-focus: rgba(104, 170, 255, .6);
}
html[data-theme="dark"] img[src*="/assets/webtzm-logo.svg"] {
  content: url("/assets/webtzm-logo-dark.png");
}

/* In dark mode `--blue` is the light accent that reads on a dark ground, so white on it is 2.51:1
   - the pairing is upside down, not merely dim. Dark ink on the same blue measures 7.3:1 and keeps
   the brand colour. This was already the rule for `.btn-primary` and the editorial header's call to
   action; `.app-shell__cta` and the documentation buttons carry the same background and were never
   added to it, which the rendered sweep found on `/` and `/documentation` at four widths. */
html[data-theme="dark"] :is(.btn-primary, .editorial-header__cta, .app-shell__cta) {
  color: #071525;
}

html[data-theme="dark"] .documentation-page {
  --documentation-on-primary: #071525;
}

html[data-theme="dark"] :is(.btn-primary, .editorial-header__cta):hover {
  color: #071525;
}

/* Landing: a deliberate evening cloudscape, never an inverted image. */
html[data-theme="dark"] .landing-page {
  --landing-ink: #edf4ff;
  --landing-muted: #adbbce;
  --landing-subtle: #8ea0b8;
  --landing-line: #343942;
  --landing-blue: #55a7ff;
  --landing-blue-strong: #91c1ff;
  --landing-blue-soft: #102a43;
  --landing-green: #c7f85b;
  --landing-paper: rgba(17, 19, 24, .96);
  --landing-paper-soft: rgba(9, 11, 14, .96);
  background: #050607;
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  html[data-theme="dark"] .landing-page :is(.section-grid, .demo-panel,
    .sheet-panel, .workflow-detail, .architecture-strip, .atlas-stage,
    .integration-card, .price-card, .output-card, .route-card,
    .route-cloud, .flow-node) {
    background:
      linear-gradient(180deg, var(--glass-highlight), transparent 46%),
      var(--glass-fill-quiet);
  }
}

html[data-theme="dark"] .landing-page .price-card.scale {
  --price-accent: #a3e635;
  border-color: rgba(199, 248, 91, .4);
  background: linear-gradient(180deg, #173253 0%, #0b213a 58%, #07192c 100%);
  box-shadow: 0 24px 58px rgba(2, 8, 20, .48);
}

html[data-theme="dark"] :is(.landing-demo-tab.active, .landing-sheet-tab.active,
  .feature-icon) {
  background: var(--landing-blue-soft);
}

html[data-theme="dark"] .cta-band {
  border-color: var(--landing-line);
  background: rgba(12, 21, 37, .82);
}

/* Editorial: reading surfaces remain paper-like and diagrams retain original color.
   Scoped to the editorial bodies: an unscoped `body` rule outranks the `bg-canvas`
   utility and silently repainted the console canvas in dark mode. */
html[data-theme="dark"] body:is(.legal-page, .editorial-page, .documentation-page) {
  background-color: var(--editorial-canvas, var(--bg));
}

html[data-theme="dark"] :is(.article-page, .legal-page) {
  background: var(--editorial-canvas);
}

html[data-theme="dark"] body::before {
  background:
    linear-gradient(rgba(104, 170, 255, .055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(104, 170, 255, .055) 1px, transparent 1px),
    linear-gradient(180deg, #111d31 0%, rgba(14, 23, 40, 0) 100%);
}

html[data-theme="dark"] .editorial-header {
  border-color: rgba(52, 67, 90, .92);
  background: rgba(14, 23, 40, .94);
  box-shadow: 0 8px 24px rgba(2, 8, 20, .28);
}

html[data-theme="dark"] .editorial-brand__divider {
  color: #71829d;
}

html[data-theme="dark"] :is(.articles-hero, .article-card) {
  border-color: var(--editorial-line);
  background: var(--editorial-paper);
}

/* Dark keeps the same wash on the same sheet, inverted - this theme's paper rather than a
   different picture. A night photograph here would reintroduce exactly the mismatch the light
   theme just lost. Colour only: the geometry, gradient stops and layer order are identical across
   themes, which is the rule in frontend/AGENTS.md. */
html[data-theme="dark"] :is(.articles-hero, .article-detail-hero)::before {
  opacity: 1;
  filter: none;
}

/* The backdrop is a pale watercolour. At the light theme's opacity it lifts a dark band to grey
   fog, so dark takes the same picture further back and lets the veil carry the band's colour.
   Paint only - the layer, its position, its scale and its blur radius are the light theme's, per
   the rule that a [data-theme] rule may not move anything. */
html[data-theme="dark"] :is(.articles-hero__wash, .article-detail-hero__wash) img {
  opacity: .26;
  filter: blur(46px) saturate(1.35) brightness(.62);
}

html[data-theme="dark"] .articles-hero::after {
  background:
    radial-gradient(circle at 24% 22%, rgba(47, 108, 170, .1), transparent 38%),
    linear-gradient(90deg, rgba(9, 13, 21, .93) 0%, rgba(9, 13, 21, .74) 46%, rgba(9, 13, 21, .2) 100%),
    linear-gradient(180deg, rgba(4, 12, 25, .08), rgba(4, 11, 23, .3));
}

html[data-theme="dark"] .article-detail-hero::after {
  background:
    linear-gradient(rgba(5,14,29,.08), rgba(5,14,29,.2)),
    linear-gradient(90deg, rgba(14, 26, 44, .98) 0%, rgba(14, 26, 44, .92) 42%, rgba(8, 31, 57, .7) 66%, rgba(4, 19, 38, .24) 100%);
}

html[data-theme="dark"] :is(.articles-hero p, .article-card p, .article-byline,
  .article-body, .article-lede, .article-takeaways li p, .article-faq h3 + p) {
  color: var(--editorial-muted);
}

html[data-theme="dark"] .article-takeaways {
  background: #121f34;
}

html[data-theme="dark"] :is(.article-pullquote, .article-wide-visual,
  .article-system-diagram) {
  border-color: var(--editorial-line);
  background: var(--editorial-paper);
}

html[data-theme="dark"] .article-wide-visual img {
  background: #f8fafc;
}

html[data-theme="dark"] .article-action-card {
  border: 1px solid var(--editorial-line);
  color: var(--editorial-ink);
  background: var(--editorial-paper);
}

html[data-theme="dark"] .article-action-card h2 {
  color: var(--editorial-ink);
}

html[data-theme="dark"] .article-action-card p {
  color: var(--editorial-muted);
}

html[data-theme="dark"] .article-action-card a {
  color: #071525;
  background: #d5e8ff;
}

/* Console: semantic Tailwind variables cover dynamic and future components. */
html[data-theme="dark"] :is(.dashboard-body, .manager-body) {
  --dash-bg: #050607;
  --dash-surface: rgba(21, 23, 27, .97);
  --dash-surface-solid: #15171b;
  --dash-surface-muted: #202329;
  --dash-line: #343942;
  --dash-line-strong: #525a67;
  --dash-text: #f7f9fc;
  --dash-muted: #b2bac6;
  --dash-subtle: #8c97a6;
  --dash-blue: #55a7ff;
  --dash-blue-strong: #9acbff;
  --dash-shadow: 0 12px 30px rgba(0, 0, 0, .3);
  --dash-shadow-soft: 0 8px 24px rgba(0, 0, 0, .24);
  background:
    radial-gradient(circle at top left, rgba(104, 170, 255, .08), transparent 35%),
    radial-gradient(circle at bottom right, rgba(190, 242, 100, .04), transparent 40%),
    var(--color-canvas);
}

html[data-theme="dark"] :is(.dashboard-body, .manager-body) .theme-toggle {
  border-color: transparent;
  color: var(--color-ink);
  background: transparent;
}

html[data-theme="dark"] :is(.dashboard-body, .manager-body) .theme-toggle:hover {
  background: var(--color-slate-100);
}

.dashboard-body .header-cluster .theme-toggle {
  --theme-control-size: 44px;
}

html[data-theme="dark"] :is(.dashboard-body, .manager-body) :is(input, select, textarea) {
  color-scheme: dark;
}

.console-mobile-brand {
  min-width: 44px;
  flex-shrink: 0;
}

@media (max-width: 1120px) {
  html[data-theme="dark"] .landing-menu-toggle,
  html[data-theme="dark"] .landing-primary-nav {
    border-color: var(--landing-line);
    color: var(--landing-ink);
    background: #111f34;
    box-shadow: var(--shadow-surface);
  }

  html[data-theme="dark"] .landing-primary-nav a {
    border-color: #334963;
  }

  html[data-theme="dark"] .landing-primary-nav a::before {
    border-color: #4d83bd;
    background: #172a45;
    box-shadow: var(--shadow-surface);
  }

  html[data-theme="dark"] .landing-primary-nav a:is(.is-active, [aria-current="page"])::before {
    border-color: var(--landing-blue-strong);
    background: var(--landing-blue);
    box-shadow: 0 0 0 3px rgba(104, 170, 255, .16);
  }
}

@media (max-width: 620px) {
  .editorial-header {
    gap: 8px;
  }

  .editorial-header__nav {
    gap: 4px;
  }
}

@media (max-width: 420px) {
  .dashboard-body .console-topbar__title {
    display: none;
  }
}

@media (max-width: 359px) {
  .dashboard-body .alert-button {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-theme-toggle],
  .theme-toggle__sun,
  .theme-toggle__moon {
    transition-duration: .01ms;
  }
}

/* The skip link targets `main`, but a plain fragment jump only scrolls - it does not move keyboard
   focus, so the next Tab returned to the top of the navigation the link had just skipped. Every
   `main#main-content` carries `tabindex="-1"` to be a real focus target; this suppresses the ring
   that would otherwise outline the whole page, because the element is only ever focused
   programmatically and is never reachable by Tab. */
#main-content:focus {
    outline: none;
}
