@charset "UTF-8";
/* ==========================================================================
   dnsdumpster.com — site stylesheet

   Replaces ~110 KB of WordPress core block CSS, twentytwentyfour, WooCommerce
   and Autoptimize bundles. Hand-written, no build step.

   Sections 7-11 are LOAD-BEARING: the backend API returns HTML fragments that
   reference those exact class names (index.html, ip_info.html, banners.html,
   charts.html, my_account.html, error.html). They are reproduced from the live
   site's global-styles block — see reference/extracted-design-system.css and
   SPEC.md §4. Do not rename them without editing the backend templates.
   ========================================================================== */

/* ==========================================================================
   1. Tokens
   ========================================================================== */

:root {
  /* Surfaces & text */
  --bg:           #0f111a;
  --fg:           #f9f9f9;
  --fg-2:         #b7b7b7;
  --fg-3:         #909090;
  --muted:        #9fa6bc;
  --greyblue:     #587cac;
  --panel:        #141824;
  --panel-2:      #1c2231;
  --border:       #373e53;
  --accent-blue:  #85a9ff;

  /* Record-type accents, shared with the Cytoscape graph in site.js */
  --ns-bg: #252834;  --ns-fg: #cbd0dd;  --ns-border: #7c83a2;
  --mx-bg: #1a2431;  --mx-fg: #60c6ff;
  --a-bg:  #1d2527;  --a-fg:  #90d67f;  --a-border:  #b0d6a5;

  /* Messaging */
  --alert-bg:     #1a1b26;
  --alert-border: #8d8d93;
  --alert-fg:     #babbbe;
  --danger-fg:    #f48270;
  --danger-bg:    #241f25;

  /* Type */
  --font-mono: Inconsolata, ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Legacy presets the first token extraction missed: the live site still
     uses has-normal-font-size on the membership plan lists. */
  --fs-normal:   16px;
  --fs-huge:     42px;
  --fs-small:    .9rem;
  --fs-medium:   1.05rem;
  --fs-large:    clamp(1.39rem, 1.39rem + ((1vw - 0.2rem) * 0.767), 1.85rem);
  --fs-x-large:  clamp(1.85rem, 1.85rem + ((1vw - 0.2rem) * 1.083), 2.5rem);
  --fs-xx-large: clamp(2.5rem, 2.5rem + ((1vw - 0.2rem) * 1.283), 3.27rem);
  --fs-title:    clamp(0.875rem, 0.875rem + ((1vw - 0.2rem) * 0.542), 1.2rem);

  /* Spacing scale, carried over from the block theme */
  --space-10: 1rem;
  --space-20: min(1.5rem, 2vw);
  --space-30: min(2.5rem, 3vw);
  --space-40: min(4rem, 5vw);
  --space-50: min(6.5rem, 8vw);
  --space-60: min(10.5rem, 13vw);

  --block-gap:    1.2rem;
  --content-size: 1260px;
  --wide-size:    1280px;
  --root-pad-x:   var(--space-50);
}

@font-face {
  font-family: Inconsolata;
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/inconsolata.woff2") format("woff2");
}

/* ==========================================================================
   2. Reset
   ========================================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background-color: var(--bg);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--fs-medium);
  font-weight: 400;
  line-height: 1.55;
  /* No -webkit-font-smoothing override: live does not set one on body (its
     only `antialiased` rules are on WooCommerce icon glyphs), so the browser
     default subpixel rendering is what makes live's text and the code-block
     colours read crisp. Forcing `antialiased` here thinned every glyph and
     muted the whole site — invisible to compare_pages.py, which reads
     computed styles, not rendered pixels (found 2026-08-03). */
}

img, svg, video { max-width: 100%; height: auto; }
svg { vertical-align: middle; }

/* ==========================================================================
   3. Base typography
   ========================================================================== */

/* The live site gives every heading a solid page-coloured background. On the
   flat dark page that is invisible, so it looks like a no-op — but it is what
   puts the readable dark bar behind the heading laid over the cover image.
   Dropping it left white text directly on the artwork. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-mono);
  font-weight: 400;
  margin: 0;
  line-height: 1.2;
  background-color: var(--bg);
  color: var(--fg);
}
h1 { font-size: var(--fs-x-large); line-height: 1.05; letter-spacing: 0; }
h2 { font-size: var(--fs-large); letter-spacing: 0; }
h3 { font-size: var(--fs-large); }
h4 { font-size: clamp(1.1rem, 1.1rem + ((1vw - 0.2rem) * 0.767), 1.5rem); }
h5 { font-size: var(--fs-medium); }
h6 { font-size: var(--fs-small); }

p { font-family: var(--font-mono); margin: 0; }

/* Live excludes buttons here via .wp-element-button; port_content.py maps
   that class to .btn-bl, so the exclusion has to name ours — otherwise this
   rule (0,1,1) outranks the button styles and repaints and underlines them. */
a:not(.btn-bl) {
  color: var(--fg);
  font-family: var(--font-mono);
  text-decoration: underline;
}
a:hover { color: #fff; }

:where(*:focus-visible) { outline: 2px solid var(--accent-blue); outline-offset: 2px; }

hr.sep {
  border: 0;
  border-bottom: 1px solid currentColor;
  color: var(--fg);
  /* margin-inline only: a blanket `margin: 0` here outranks .flow/.col > *
     (0,1,1 beats 0,1,0) and silently stripped the block gap from every
     separator on the site. */
  margin-inline: 0;
  width: 100%;
  opacity: .35;
}

ul, ol { padding-left: var(--space-10); }

/* Live sets no font-family on <pre>, so code blocks fall back to the UA
   monospace face, and .wp-block-code contributes only vertical padding. The
   background/border I had here were invented. */
pre { overflow-x: auto; padding: 0; font-size: 0.9em; }

/* ==========================================================================
   4. Layout primitives
   Replaces .has-global-padding / .is-layout-constrained / .is-layout-flex.
   ========================================================================== */

.wrap { padding-inline: var(--root-pad-x); }

.constrained > * {
  max-width: var(--content-size);
  margin-inline: auto;
}
.constrained > .wide { max-width: var(--wide-size); }
.constrained > .full  { max-width: none; }
/* Beats `.constrained > *` so the form keeps its own cap, as live's does. */
.constrained > form   { max-width: 800px; margin-inline: auto; }
/* Live sizes the form and its fields content-box, so the 800px cap is the
   content width and the padding sits outside it (830px / 822px rendered). */
.constrained > form,
.constrained > form input:not([type=submit]),
.constrained > form textarea { box-sizing: content-box; }
/* The submit stays border-box and fills the row up to its inline 300px cap,
   rather than shrinking to its label. */
.constrained > form input[type=submit] { width: 100%; }

/* WordPress makes every group and column its own flow root, so each child
   after the first picks up the block gap. port_content.py flattens those
   wrapper divs, so the same rule has to apply inside our own containers —
   without it nested content packs tighter than the live site. */
.flow > *, .panel > *, .col > * { margin-block: var(--block-gap) 0; }
.flow > :first-child, .panel > :first-child,
.col > :first-child  { margin-block-start: 0; }
.flow > :last-child, .panel > :last-child,
.col > :last-child   { margin-block-end: 0; }

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--block-gap);
}

/* Live's <main> has no padding: the leading gap is the first child's own top
   margin, which varies per page (19.2px normally, 64px on the home tagline).
   flow-root keeps that margin inside main instead of collapsing through it,
   which is what live's entry-content wrapper does. */
main { padding-block: 0; display: flow-root; }

/* main is the page's flow root, so unlike a nested group its first child
   keeps a top margin rather than sitting flush. */
main.flow > :first-child { margin-block-start: var(--block-gap); }

/* ==========================================================================
   5. Header
   ========================================================================== */

.site-header { padding: var(--space-30); }

.header-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--block-gap);
  margin-inline: auto;
  max-width: var(--wide-size);
}

.site-title {
  font-family: var(--font-mono);
  font-size: var(--fs-title);
  font-weight: 600;
  font-style: normal;
  margin: 0;
}
.site-title a { color: var(--fg); text-decoration: none; }
.site-title a:hover { text-decoration: none; }

.header-right {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--block-gap);
}

.site-nav { font-weight: 500; }
.site-nav ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--block-gap);
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-nav a { text-decoration: none; }
.site-nav a:hover, .site-nav a:focus { text-decoration: underline; }

/* Live renders Login as a padded box (67x34), which is what sets the whole
   header's 114px height — without the padding ours came out 4px shorter and
   the nav group 17px narrower. */
.header-account {
  display: flex;
  align-items: center;
  line-height: 1;
  padding: 8.4px;
  text-decoration: none;
}
.header-account:hover { text-decoration: underline; }

/* Logged-in chrome: Log out sits beside My Account on desktop, but a second
   header link wraps the 414px header onto two rows — below the nav break it
   lives in the collapsed menu instead (.nav-logout in _header.html). */
.nav-logout { display: none; }
@media (max-width: 781px) {
  .header-logout { display: none; }
  .nav-logout { display: list-item; }
}

/* padding-top nudges the 16px glyph down to sit on the nav text baseline;
   live uses 6px but renders a hair low, so 3px here aligns it to the bottom
   of the menu-item lettering. Header is outside compare_pages.py's scope. */
.header-contact { display: inline-flex; color: var(--fg); padding-top: 3px; }

.nav-toggle, .nav-close {
  display: none;
  background: none;
  border: 0;
  padding: 0;
  color: currentColor;
  cursor: pointer;
}

@media (max-width: 781px) {
  .nav-toggle { display: inline-flex; order: -1; }

  .site-nav {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 100000;
    background-color: var(--bg);
    padding: clamp(1rem, var(--root-pad-x), 20rem);
    overflow: auto;
  }
  .site-nav.is-open { display: block; }
  .site-nav.is-open .nav-close {
    display: inline-flex;
    position: absolute;
    top: 1rem;
    right: 1rem;
  }
  .site-nav ul {
    flex-direction: column;
    align-items: flex-start;
    padding-top: calc(2rem + 24px);
  }
  body.nav-open { overflow: hidden; }
}

/* ==========================================================================
   6. Footer
   ========================================================================== */

/* Live's footer sits one block gap below the content. */
.site-footer { padding-inline: var(--root-pad-x); margin-top: var(--block-gap); }

/* The UA gives hr a 0.5em block margin; live's footer rule sits flush under
   the 104px padding. */
.footer-inner > hr.sep { margin-block: 0; }

.footer-inner {
  margin-inline: auto;
  max-width: var(--wide-size);
  padding-block: var(--space-50);
}

.footer-cols {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--block-gap);
  padding-top: var(--space-10);
  margin-top: var(--block-gap);
}
.footer-col--about      { flex-basis: 36.54%; padding-inline: 0; }
.footer-col--spacer     { flex-basis: 19.23%; }
.footer-col--links      { flex-basis: 19.23%; }
.footer-col--newsletter { flex-basis: 25%; }

.footer-col p { font-size: var(--fs-small); color: var(--fg-2); }
.footer-col p a { color: var(--fg); }
.footer-col--links p { font-size: inherit; }
.footer-col .pt-12 { padding-top: 12px; }

.footer-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-10);
}
.footer-block + .footer-block { margin-top: var(--block-gap); }

.footer-col h3 {
  font-family: var(--font-mono);
  font-size: var(--fs-medium);
}

.footer-gap { height: 24px; margin-top: var(--block-gap); }

.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 6px;
  margin-top: var(--block-gap);
}
.footer-bottom p { font-size: var(--fs-small); }

/* Mailchimp signup */
#mc_embed_signup { margin-top: var(--block-gap); }
#mc_embed_signup form { max-width: none; margin: 0; padding: 15px 0; background: none; }
#mc_embed_signup .mc-hidden { position: absolute; left: -5000px; }
#mc_embed_signup .clear { margin-top: 5px; }
#mc_embed_signup .button { max-width: 160px; font-size: .8em; }
#mc_embed_signup .mc-note { font-size: .8em; }

@media (max-width: 781px) {
  .footer-cols { flex-wrap: wrap; }
  .footer-col--about, .footer-col--links, .footer-col--newsletter { flex-basis: 100%; }
  .footer-col--spacer { display: none; }
}

/* ==========================================================================
   7. Forms  — LOAD-BEARING (backend fragments + all site forms)
   ========================================================================== */

form {
  max-width: 600px;
  margin: 0 auto;
  padding: 15px;
  background-color: var(--bg);
  color: var(--muted);
}

.form-group { margin-bottom: 15px; }

form p { margin-block: 1em; }
form label { display: block; margin-bottom: 5px; font-weight: 500; }

form input, form select, form textarea {
  width: 100%;
  padding: 10px;
  margin-bottom: 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background-color: var(--panel);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .075);
  transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
  color: var(--muted);
}
form input:focus, form select:focus, form textarea:focus {
  border-color: #80bdff;
  outline: 0;
  box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
}

/* Note: the live site declares this rule twice; the second (blue) wins. */
form button, form input[type=submit] {
  display: inline-block;
  font-weight: 400;
  color: var(--accent-blue);
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  background-color: var(--panel-2);
  border: 1px solid var(--accent-blue);
  padding: .375rem .75rem;
  font-size: 1rem;
  border-radius: .25rem;
  width: auto;
  transition: color .15s ease-in-out, background-color .15s ease-in-out,
              border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
form button:hover, form input[type=submit]:hover {
  background-color: var(--panel);
  border-color: #a0b5ff;
}
form button:focus, form input[type=submit]:focus { outline: none; }

.form-check { position: relative; display: block; margin-bottom: 10px; }
.form-check input[type=checkbox], .form-check input[type=radio] {
  position: absolute;
  width: auto;
  margin-top: .3rem;
  margin-left: -1.5rem;
}
.form-check-label { margin-left: 1.75rem; font-weight: 400; }
.form-input { color: var(--muted); }
.form-text {
  display: block;
  margin-top: .25rem;
  margin-bottom: .25rem;
  color: #fff;
  font-size: .875rem;
}

/* Honeypot on the contact form */
.scf-hidden-field {
  position: absolute !important;
  left: -9999px !important;
  top: -9999px !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
}

/* ==========================================================================
   8. Buttons  — LOAD-BEARING (index.html, my_account.html)
   ========================================================================== */

.btn-primary {
  background-color: var(--a-bg);
  color: var(--a-fg);
  border: 1px solid rgb(37 176 3 / 24%);
  padding: 10px 20px;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color .3s ease;
}
.btn-primary:hover {
  background-color: rgba(37, 176, 3, .2);
  border-color: rgb(167 223 153 / 23%);
}
.btn-primary:active {
  background-color: rgba(37, 176, 3, .2);
  box-shadow: 0 2px 5px rgba(0, 0, 0, .2);
}

.btn-sm { font-size: 12px; padding: 4px 12px; }

.btn-bl {
  display: inline-block;
  font-weight: 400;
  color: var(--accent-blue);
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  background-color: var(--panel-2);
  /* Live's buttons have no border and a 0.9rem face; the border was adding
     2px of height to every one of them. */
  border: 0;
  padding: .6rem 1rem;
  font-size: .9rem;
  border-radius: 5.28px;
  text-decoration: none;
  transition: color .15s ease-in-out, background-color .15s ease-in-out,
              border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
/* Per-instance button colours, from live's has-*-background-color classes. */
.btn-light { background-color: var(--fg); color: var(--bg); }
.btn-blue  { background-color: #587cac; color: var(--fg); }
.btn-black { background-color: #000; color: #fff; border-radius: 0; }
.btn-light:hover, .btn-blue:hover, .btn-black:hover { color: #fff; }
.btn-light:hover { background-color: #d5d5d5; color: var(--bg); }

/* A figure is inline-block so it shrink-wraps its image; without this it also
   sits on the text baseline and adds ~7px of descender space below. */
.fig { vertical-align: top; }

.btn-bl:hover { background-color: var(--panel); border-color: var(--accent-blue); color: #fff; }
.btn-bl:focus { outline: none; }

/* No .w-100 rule: the live theme never defined one, so the button is
   auto-width. Defining it stretched the button across the form. */

/* ==========================================================================
   9. Tables  — LOAD-BEARING (index.html, banners.html, ip_info.html)

   The live site declares `table{color:#9f}`, which is an invalid colour and is
   discarded by every browser, so tables inherit body colour. Inherit here too
   — reproducing the typo would change nothing but mislead the next reader.
   ========================================================================== */

table {
  font-family: var(--font-mono);
  border-collapse: collapse;
  width: 100%;
}
td {
  font-size: .9em;
  border-bottom: 1px solid rgba(55, 62, 83, .78);
  vertical-align: top;
  padding: 4px;
}
th {
  text-align: left;
  border-bottom: 1px solid rgba(55, 62, 83, .88);
  font-family: inherit;
}
.table-sm { font-size: .9em; line-height: 1.2; }

.sm-text   { font-size: .9em; }
.xs-text   { font-size: .7em; color: rgb(138 148 173 / 85%); font-family: inherit; }
.light-text { color: var(--greyblue); }

.badge-tag {
  background-color: #31374a;
  color: var(--ns-fg);
  font-weight: 700;
  font-size: .64rem;
  padding: .25rem .5rem;
  text-transform: uppercase;
}

/* ==========================================================================
   10. Result components  — LOAD-BEARING
       Alerts, modal, kebab menu, charts, graph containers.
   ========================================================================== */

.main-alert {
  background-color: var(--alert-bg);
  border: 1px solid var(--alert-border);
  color: var(--alert-fg);
  border-radius: 4px;
  padding: 10px;
  margin-top: 20px;
  margin-bottom: 40px;
}

.alert-danger {
  color: var(--danger-fg);
  background-color: var(--danger-bg);
  border: 1px solid var(--danger-fg);
  padding: 10px;
  border-radius: 4px;
}

.modal {
  display: none;
  position: fixed;
  z-index: 1;
  left: 50%;
  top: 10%;
  transform: translateX(-50%);
  width: 800px;
  max-width: calc(100vw - 2rem);
  height: 600px;
  max-height: 80vh;
  overflow: hidden;
  background-color: var(--panel);
  border: 1px solid #949db5;
  border-radius: 10px;
}
.modal-content {
  background-color: var(--panel);
  padding: 20px;
  height: 100%;
  box-sizing: border-box;
  overflow-y: auto;
}
.close {
  color: #949db5;
  float: right;
  font-size: 28px;
  font-weight: 700;
  margin-right: 10px;
}
.close:hover, .close:focus { color: #fff; text-decoration: none; cursor: pointer; }

.kebab-menu { position: relative; display: inline-block; }
.kebab-button {
  background: 0 0;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #fff;
}
.dropdown-content {
  display: none;
  position: absolute;
  right: 0;
  background-color: #f9f9f9;
  min-width: 160px;
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, .2);
  z-index: 1;
}
.dropdown-content a { color: #000; padding: 12px 16px; text-decoration: none; display: block; }
.dropdown-content a:hover { background-color: #f1f1f1; }
.show { display: block; }

#map { background-color: var(--bg); }
.jvm-tooltip { background-color: var(--mx-bg) !important; color: #eff2f6 !important; }

#cy {
  width: 100%;
  height: 600px;
  background-color: var(--bg);
  margin-top: 20px;
  border-radius: 8px;
  overflow: hidden;
}

/* ==========================================================================
   11. Content helpers
   ========================================================================== */

.three-column-container { display: flex; flex-wrap: wrap; gap: var(--block-gap); }
.three-column-container .column {
  flex: 1;
  padding: 10px;
  min-width: 300px;
  max-width: calc(33.333% - 20px);
  box-sizing: border-box;
}
@media (max-width: 900px) { .three-column-container .column { max-width: calc(50% - 20px); } }
@media (max-width: 600px) { .three-column-container .column { max-width: 100%; } }

/* HTMX request indicator: hidden until htmx marks the element as requesting. */
.htmx-indicator { opacity: 0; transition: opacity 200ms ease-in; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { opacity: 1; }

/* ==========================================================================
   12. Home page
   ========================================================================== */

.tagline {
  text-align: center;
  margin-block: var(--space-40);
  padding: var(--space-10);
}

/* The block theme constrains the form group to 720px; the form itself is
   600px and centred inside it. */
/* No width on the wrapper: the 720px cap belongs on the form itself. With it
   on the wrapper the form can only fill 720px INCLUDING its 15px padding,
   which is 30px narrower than the live site. */
.form-shell { margin-inline: auto; }
/* On the live site the container's 720px cap beats the generic form rule
   (class selector vs element selector), so the lookup form is 720px, not 600. */
.form-shell form { max-width: 720px; }
/* The live theme leaves plain form controls at the UA default box-sizing, so
   the form measures 720 + 30px padding = 750 and the input 720 + 22. Scoped
   here rather than globally — border-box stays the default everywhere else. */
.form-shell form,
.form-shell form input,
.form-shell form button { box-sizing: content-box; }

/* ==========================================================================
   13. Content page primitives

   These replace the WordPress block classes that scripts/port_content.py maps
   away: wp-block-columns -> .cols, wp-block-column -> .col, wp-block-buttons
   -> .btn-row, wp-block-image -> .fig, and the has-*-font-size / colour
   utilities -> .fs-* / .fg-*.
   ========================================================================== */

/* Every wp-block-columns instance on the live site is flex-wrap:nowrap; the
   stacking below 782px comes from a media query, not from wrapping. */
.cols {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--block-gap);
  align-items: normal;
}

/* Columns blocks carry per-instance gaps that port_content.py cannot see —
   they live in generated wp-container-* rules, not on the element. */
.cols-gap-lg { gap: var(--space-40) var(--space-30); }
.cols-gap-xl { gap: var(--space-50) var(--space-60); }
.cols-gap-24 { gap: 24px; }
.col { flex: 1 1 0; min-width: 0; }
/* .wp-block-column[style*=flex-basis]{flex-grow:0} — a column with an explicit
   basis must not also grow, or the declared split is not what you get. */
.col[style*="flex-basis"] { flex-grow: 0; }

@media (max-width: 781px) {
  .cols { flex-wrap: wrap; }
  .col { flex-basis: 100% !important; }
}

/* The Attack/Defend/Learn row uses spacing-30/40, not the default block gap;
   with the smaller gap the columns come out 30px too wide each. */
.panel > .cols { gap: var(--space-30) var(--space-40); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--block-gap);
  margin-block: var(--block-gap);
}

.fig { margin: 0; display: inline-block; }
.fig img { display: block; }

.spacer { display: block; }

/* Utilities mapped from the block editor's presets. */
.text-center { text-align: center; }
.fs-small    { font-size: var(--fs-small); }
.fs-medium   { font-size: var(--fs-medium); }
.fs-large    { font-size: var(--fs-large); }
.fs-x-large  { font-size: var(--fs-x-large); }
.fg-2        { color: var(--fg-2); }
.fg-3        { color: var(--fg-3); }

/* The lookup form keeps its original Bootstrap-ish hooks: the backend's
   fragments and the old markup both reference them. */
.form-label   { display: block; margin-bottom: 5px; font-weight: 500; }
.form-control { width: 100%; }
.btn          { cursor: pointer; }
.mb-6         { margin-bottom: 1.5rem; }

.scf-error-messages { margin: 0; padding-left: 1.2em; }

pre { line-height: 1.55; }
pre code { background: none; padding: 0; }
:not(pre) > code {
  background-color: var(--panel);
  border-radius: 3px;
  padding: .1em .35em;
  font-size: .9em;
}

/* Long prose reads badly at full 1260px, so cap the measure on text-only
   pages while leaving the wide result tables unconstrained. */
.prose { max-width: 78ch; }

/* ==========================================================================
   14. Home page sections
   ========================================================================== */

/* The block editor uses <mark> purely as an emphasis colour here — the live
   markup carries a transparent background and white text inline. Encoding it
   once as a rule beats repeating the inline style on every occurrence. */
mark {
  background-color: transparent;
  color: #fff;
}

/* The logos are letterboxed into a 4:3 box on the live site — each carries
   an inline object-fit:contain. Without it the default `fill` stretches them:
   defcon.png is 300x68, so squeezing it into 150x113 distorts it badly. */
.logo-row img { object-fit: contain; }

.logo-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-30);
  justify-content: center;
  align-items: center;
}


/* ==========================================================================
   15. Account pages
   ========================================================================== */

/* Login and Register sit side by side above 782px, as on the WooCommerce
   page they replace, and stack below it via the .col rule in §13. */
.auth-cols { align-items: flex-start; gap: var(--space-30); }
.auth-cols form { max-width: none; margin: 0; padding: 0; }

/* Required-field marker. WooCommerce printed "*Required" after the label; a
   red asterisk with an accessible label reads the same and is less noisy. */
.req { color: var(--danger-fg); }

/* The account panel arrives from the backend as a bare <table> of inline
   styles; it inherits the table rules in §9. Give the first column a stable
   width so the layout does not jump when htmx swaps it in. */
#my-account-info table td:first-child { width: 160px; }
#my-account-info .btn-primary { display: inline-block; text-decoration: none; }

/* Cloudflare Turnstile renders in an iframe of its own; just give it room. */
.cf-turnstile { margin-block: var(--block-gap); }

.mt-1 { margin-top: .5rem; }
.mt-2 { margin-top: var(--space-20); }

/* Buy buttons post to /billing/checkout, so they are forms; strip the form
   chrome from §7 so they sit inline like the links they replace. */
.plan-form { max-width: none; margin: 0; padding: 0; display: inline-block; }
.plan-form button { margin: 0; }

/* ==========================================================================
   16. Home page section widths — measured from the live site
   ========================================================================== */

/* The only element on the page with its own width cap. Without it the intro
   runs the full 1260px content width, which is unreadably long. */
.intro { padding: var(--space-50); }
.intro > * { max-width: 700px; margin-inline: auto; }

/* The live block overrides the h3 default with an inline clamp; encoding it
   here keeps the markup clean. */
.section-heading {
  font-size: clamp(0.929rem, 0.929rem + ((1vw - 0.2rem) * 0.785), 1.4rem);
}

/* WordPress "alignfull" group: a full-width band with generous vertical
   padding and no horizontal inset of its own. */
/* The band sits flush in the flow — its 104px padding IS the spacing, so it
   must not also collect the block gap from .flow > *. Like live's alignfull
   group it bleeds to the viewport edges and re-applies the root padding, so
   its children can reach wide-size on screens wider than ~1490px. */
.band {
  padding-block: var(--space-50);
  margin-block: 0;
  max-width: none;
  margin-inline: calc(-1 * var(--root-pad-x));
  padding-inline: var(--root-pad-x);
}
.band > * { max-width: var(--wide-size); margin-inline: auto; }
/* Wide-screen (>~1490px) centering fixes, found 2026-08-03. All three are
   invisible to compare_pages.py (hr/img carry no text to match on):
   - hr.sep's own margin-inline:0 outranks the centering above, leaving FAQ
     separators on the left margin while the text centres;
   - .fig is inline-block, and auto margins resolve to 0 on inline-level
     boxes, so band figures never centred (live's wp-block-image does);
   - live justifies the defend page's button row centre
     (is-content-justification-center) — the port dropped it. */
.band > .sep { margin-inline: auto; }
.band .fig { display: block; }
.band .fig img { margin-inline: auto; }
.btn-row-center { justify-content: center; }
/* Most bands hold alignwide blocks; a few hold plain constrained ones. */
.band-narrow > * { max-width: var(--content-size); }
.band-sm { padding-block: var(--space-30); }
.band-xs { padding-block: var(--space-20); }

/* Marks an element that begins a new section on live, where it is the first
   child of a group and so takes no block gap. port_content.py flattens those
   wrappers away, so the intent has to be carried on the element itself. */
.flush { margin-block-start: 0; }

/* Live sizes constrained blocks content-box, so padding sits outside the
   max-width cap rather than inside it. */
.tagline { box-sizing: content-box; }

/* WordPress's is-vertically-aligned-center on a column: the column shrinks to
   its content and centres in the row instead of stretching to full height. */
.v-center { align-self: center; }

/* One column set on the home page carries a tighter inner gap than the
   1.2rem default. Overriding the custom property on the columns feeds
   .col > * without touching the flex gap between the columns. */
.cols-tight > .col { --block-gap: 1rem; }
.cols-loose > .col { --block-gap: var(--space-40); }

.panel { padding: var(--space-50); }

/* advanced-access hero: a WordPress alignfull group in the accent-4 colour
   that fills the viewport. alignfull breaks out of the constrained wrap with
   a negative inline margin and restores the padding inside. */
.section-accent {
  max-width: none;
  margin-inline: calc(-1 * var(--root-pad-x));
  /* Live nests two groups: this one carries the bleed, colour and viewport
     height with a small 24px inset; the inner one carries the 104px inset.
     Collapsing them into one only matches while min-height dominates. */
  padding: var(--space-20) var(--root-pad-x);
  background-color: #4d5b48;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  /* Live centres rather than stretches, so the inner group shrinks to its
     content instead of filling the viewport on wide screens. */
  align-items: center;
}

.section-accent__inner { padding: var(--space-50); }
.panel.rounded { border-radius: 16px; }

/* --------------------------------------------------------------------------
   Cover block

   Reproduces WordPress's wp-block-cover: the image is an absolutely
   positioned, cropped background and the copy sits over it. The fixed
   min-height is what stops a 1024x1024 asset from taking over the page.
   -------------------------------------------------------------------------- */

.cover {
  /* Live wraps the cover in an alignwide group capped at 1280px and centred.
     `none` here let it fill the viewport: identical up to ~1490px wide (where
     the wrap padding still binds), then progressively too wide. */
  max-width: var(--wide-size);
  color: #fff;
  position: relative;
  display: flex;
  align-items: flex-start;      /* is-position-top-left */
  justify-content: flex-start;
  min-height: 430px;          /* wp-block-cover default */
  overflow: hidden;
  padding: 1em;               /* wp-block-cover default */
  margin-inline: auto;
  box-sizing: border-box;
}
.cover__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
  border: none;
  object-fit: cover;
  /* Default WP crop. Only the home hero overrides it (inline, as live does)
     — baking 50% 31% in here mis-cropped every other cover block. */
  object-position: 50% 50%;     /* matches the live crop */
}
.cover__inner { position: relative; z-index: 1; width: 100%; }
/* The cover's inner container is a flow layout on the live site, so its
   children are separated by the block gap. Without it the heading bar and the
   footnote bar butt together instead of sitting apart. */
.cover__inner > * { margin-block: var(--block-gap) 0; }
.cover__inner > :first-child { margin-block-start: 0; }

/* Overlay copy. Both blocks carry the page background so they stay legible
   against the artwork; measured from the live render, neither has vertical
   padding — the bar height is pure line-height. */
.cover__inner h3 {
  color: #fff;
  font-size: clamp(14.642px, 0.915rem + ((1vw - 3.2px) * 0.766), 22px);
  line-height: 1.3;
  padding: 0 var(--space-10);
}
.cover__inner .cover-note {
  background-color: var(--bg);
  color: #fff;
  font-size: var(--fs-medium);
  padding: 0 var(--space-10);
}

/* Live caps this column's contents at 440px (wp-container-core-column-is-layout-5687bee6). */
.col-440 > * { max-width: 440px; }

/* Section headings that carry their own inline clamp on the live site.
   port_content.py drops inline font-size, so these are re-stated here. */
.fs-h2-wide {
  font-size: clamp(1.301rem, 1.301rem + ((1vw - 0.2rem) * 1.332), 2.1rem);
  letter-spacing: -0.02em;
}

/* ==========================================================================
   17. Per-page pieces measured against the live render
   ========================================================================== */

/* Inline font-weight the converter drops (STYLE_KEEP carries no font-weight). */
.fw-bold { font-weight: 700; }

/* advanced-access: the two-column block is a rounded panel with its own
   background and a spacing-50 gap, not a bare column pair. */
.panel > .cols.cols-panel,
.cols-panel {
  background-color: #0b0f19;
  border-radius: 32px;
  padding: 1.25em 2.375em;
  gap: var(--space-50);
}

/* A vertical flex group whose align-items:center makes children shrink to
   their content width and centre, rather than stretching full width. */
.stack-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-10);
}

/* The membership hero group: spacing-50 vertical, spacing-40 horizontal. */
/* Live's hero is a vertical flex group with a block-gap row gap, so the gap
   and each child's own margin BOTH apply (flex margins do not collapse). */
.panel-hero {
  padding: var(--space-50) var(--space-40);
  display: flex;
  flex-direction: column;
  gap: var(--block-gap);
}

/* Membership plan cards: a bordered panel per tier. The port dropped these
   because they come from has-*-background-color / has-border-color classes. */
.plan-card {
  /* The live markup sets border-style only, so the width is the initial
     `medium` — 3px in Chrome, not 1px. That is the 4px per card. */
  border-style: solid;
  border-width: medium;
  /* Each tier carries its own accent on live — card background, border and
     the rule under the heading all share it. Basic and Unplugged use the
     defaults below; the rest override via --plan-* on the card. */
  background-color: var(--plan-bg, var(--ns-bg));
  border-color: var(--plan-accent, var(--ns-border));
}

.plan-plus   { --plan-bg: #1a2431; --plan-accent: #60c6ff; }
.plan-max    { --plan-bg: #1d2527; --plan-accent: #b0d6a5; }
.plan-adv    { --plan-bg: #1a2431; --plan-accent: #587cac; }
.plan-unplug { --plan-rule: #b7b7b7; }

.plan-card hr.sep {
  border-bottom-color: var(--plan-rule, var(--plan-accent, var(--fg)));
  opacity: 1;
}

/* Tagline under the membership title: greyblue, bold, its own clamp. */
.plan-tagline {
  color: var(--greyblue);
  font-weight: 700;
  margin-top: var(--space-10);
  font-size: clamp(0.875rem, 0.875rem + ((1vw - 0.2rem) * 0.708), 1.3rem);
}

.fs-normal { font-size: var(--fs-normal); }
/* Plan card copy uses the legacy "normal" preset (16px), not the 1.05rem body
   size — the difference is small but it is deliberate on the live site. */
.plan-card p, .plan-card ul { font-size: var(--fs-normal); }

/* Membership "Advanced" / "Unplugged" headings carry their own clamp. */
/* Basic / Plus / Max tier headings: live sets this clamp inline, plus bold. */
.fs-h2-tier {
  font-size: clamp(24.034px, 1.502rem + ((1vw - 3.2px) * 1.663), 40px);
  font-weight: 700;
}

.fs-h2-plan {
  font-size: clamp(22.041px, 1.378rem + ((1vw - 3.2px) * 1.454), 36px);
}

/* is-style-checkmark-list: a tick marker, 1ch of item padding, and a wide
   inline padding set on the list itself. */
.checkmark-list {
  list-style-type: "\2713";
  line-height: 1.75;
}
.checkmark-list li { padding-inline-start: 1ch; }

/* ==========================================================================
   14. My-account area
   Left tab nav revealing one section at a time, WooCommerce-style: the old
   site's my-account page put Dashboard / Account details / Log out down the
   left with the active tab underlined, and kept the content column compact.
   ========================================================================== */

/* Narrower than --content-size so the account reads as a compact centred
   column rather than a full-width sheet. */
.account { max-width: 960px; margin-inline: auto; }

.account-grid {
  display: flex;
  gap: clamp(24px, 4vw, 64px);
  align-items: flex-start;
}

.account-nav { flex: 0 0 168px; }
.account-nav ul { list-style: none; margin: 0; padding: 0; }
.account-nav li + li { margin-top: var(--space-30); }
.account-nav a { color: inherit; text-decoration: none; }
.account-nav a:hover { text-decoration: underline; }
.account-nav a[aria-current="page"] { text-decoration: underline; }

.account-content { flex: 1 1 auto; min-width: 0; }

/* Compact label column for the details table (the backend's htmx fragment
   brings its own table and simply inherits the narrower column). */
.account-table td:first-child { width: 150px; color: var(--ns-fg); }
.account-table th { text-align: left; font-weight: 500; color: var(--ns-fg); }

/* Breathing room around the details table — the flow gap alone reads tight
   between the greeting, the table and the actions row. */
.account-content .account-table { margin-block: var(--space-20); }

/* Purchase actions row. flex gap spaces the buttons on both axes, so they
   keep clear of each other when they wrap on narrow screens. */
.account-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-10);
  margin-block: var(--space-20);
}

/* The actions reuse the lookup form's green .btn-primary. As an anchor
   (free tier) it needs the link underline stripped; the cart icon sits a
   couple of px down so it rides centred on the label. */
.account-actions .btn-primary { text-decoration: none; }
.btn-icon { vertical-align: -2px; margin-right: 4px; }

/* The backend fragment repeats the purchase buttons as WooCommerce /cart/
   links this site does not serve; ours render above the heading instead. */
#my-account-info a.btn { display: none; }

/* Password fields full-width read as a search box; cap them. */
.account-content .form-control { max-width: 360px; }

/* The global `form` rule centres with `margin: 0 auto` and adds 15px of
   padding — sensible for the standalone contact/login forms, but inside the
   account column it pushed the password form right of the heading and table
   it belongs under. Pin it to the column's left edge instead. */
.account-content form { margin-inline: 0; padding-inline: 0; }

/* The Change password heading sits directly under the details table and
   needs more separation than the standard block gap gives. */
.account-content .form-heading { margin-top: calc(var(--space-20) + 20px); }

@media (max-width: 781px) {
  .account-grid { flex-direction: column; }
  .account-nav { flex-basis: auto; }
  .account-nav ul { display: flex; flex-wrap: wrap; gap: var(--space-10) var(--space-30); }
  .account-nav li + li { margin-top: 0; }
}

/* ==========================================================================
   15. Lookup results fragment (backend markup)
   charts.html arrives as WordPress block markup. WordPress normally emits
   these layout rules as generated per-page CSS in the page head, which the
   rebuild does not carry — so the flex-columns behaviour is restated here.
   No template of our own uses wp-block classes; scope is the fragment only.
   ========================================================================== */

/* Three chart columns in a row on desktop, stacked below 782px (the same
   781/782 break WordPress uses and the header nav already follows). The
   first column arrives wrapped in entry-content/group divs, so the stacking
   rule targets all direct children, not just .wp-block-column. */
.wp-block-columns {
  display: flex;
  flex-wrap: wrap;
  gap: 2em;
}
.wp-block-column { flex-grow: 1; min-width: 0; overflow-wrap: break-word; }
@media (min-width: 782px) { .wp-block-columns { flex-wrap: nowrap; } }
@media (max-width: 781px) { .wp-block-columns > * { flex-basis: 100%; } }

/* The "Showing N records out of a total of M found" notice otherwise sits
   flush between the banner table and the A-records heading. */
#results .alert-danger { margin-block: var(--space-20); }
