/* ============================================================
   N7HQ Labs — shared stylesheet
   RF / HF amplifier repair and RF services
   ============================================================ */

/* The [hidden] attribute must win. .btn sets `display:inline-block` (an author rule), which
   beats the UA `[hidden]{display:none}` - so the portal "Sign In" button, shipped `hidden`
   until N7HQ_PORTAL_URL is set, was actually rendered as a dead link to `#`. Force it. */
[hidden] {
  display: none !important;
}

/* Skip link: off-screen until it receives keyboard focus, then it slides into the top-left.
   Lets keyboard/AT users jump past the repeated header nav straight to <main id="main">. */
.skip-link {
  position: absolute;
  left: 8px;
  top: -60px;
  z-index: 1000;
  background: #ff9e2c;
  color: #0c1118;
  padding: 10px 16px;
  border-radius: 8px;
  font-weight: 700;
  text-decoration: none;
  transition: top .15s ease;
}

.skip-link:focus {
  top: 8px;
}

:root {
  --bg: #0c1118;
  --bg-2: #121a24;
  --panel: #16202d;
  --panel-2: #1b2738;
  --line: #243245;
  --text: #e6edf5;
  --muted: #9fb0c3;
  --accent: #ff9e2c;
  /* signal amber */
  --accent-2: #38c5ff;
  /* trace cyan  */
  --good: #41d18a;
  --radius: 12px;
  --maxw: 1120px;
  --shadow: 0 10px 30px rgba(0, 0, 0, .35);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "SFMono-Regular", ui-monospace, "Cascadia Code", Consolas, "Liberation Mono", monospace;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font);
  color: var(--text);
  background:
    radial-gradient(900px 500px at 80% -10%, rgba(56, 197, 255, .08), transparent 60%),
    radial-gradient(700px 500px at -10% 10%, rgba(255, 158, 44, .07), transparent 55%),
    var(--bg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent-2);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 22px;
}

/* ---------- Header / nav ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(12, 17, 24, .82);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 68px;
}

/* THE HOME BUTTON. The logo has always been an <a> to index.html in the top-left of every
   page, but it carried no hover state and no focus ring - so it read as decoration and
   visitors had no reason to think it would take them home. What was missing was the
   affordance, not the link. */
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  font-weight: 700;
  letter-spacing: .3px;
  padding: 6px 10px;
  border: 1px solid transparent;
  border-radius: 10px;
  transition: background-color .15s ease, border-color .15s ease;
}

/* The padding above grows the click target; this cancels it again so the header sits
   exactly where it did before - an affordance should not cost a layout shift. The offsets
   are 7/11, not 6/10: the 1px transparent border counts too, and leaving it out moved the
   logo a pixel right (measured, not assumed). The footer lockup only needs the left half
   cancelled to stay flush with its column (and it sets its own margin-bottom inline, which
   this must not disturb). */
.nav .brand {
  margin: -7px -11px;
}

.site-footer .brand {
  margin-left: -11px;
}

.brand:hover {
  background: var(--panel);
  border-color: var(--accent);
}

/* There was no keyboard focus indicator here at all. :focus-visible rather than :focus so
   a mouse click doesn't leave the ring stuck on after navigating. */
.brand:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Respect a reduced-motion preference: the colour change still happens, just instantly. */
@media (prefers-reduced-motion: reduce) {
  .brand {
    transition: none;
  }
}

.brand .logo {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
}

.brand .name {
  font-size: 1.18rem;
  color: var(--text);
}

.brand .name b {
  color: var(--accent);
}

.brand .tag {
  display: block;
  font-size: .7rem;
  font-weight: 500;
  color: var(--muted);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin-top: -3px;
  white-space: nowrap;
}

.nav-links {
  display: flex;
  gap: 6px;
  align-items: center;
}

.nav-links a {
  color: var(--muted);
  padding: 8px 14px;
  border-radius: 8px;
  font-weight: 500;
}

.nav-links a:hover {
  color: var(--text);
  background: var(--panel);
  text-decoration: none;
}

.nav-links a.active {
  color: var(--text);
}

/* The generic .nav-links a color (muted gray) outranks .btn's dark text by specificity,
   which rendered the header "Request Service" button gray-on-amber at ~2:1 contrast.
   Pin the button's own colors in nav context, including hover (the nav hover rule would
   otherwise repaint it as a plain link). */
.nav-links a.btn {
  color: #11161d;
}

.nav-links a.btn:hover {
  color: #11161d;
  background: var(--accent);
}

/* ...but the pin above must not reach the ghost variant. `.nav-links a.btn` (0-2-1) outranks
   `.btn.ghost` (0-2-0), so the transparent "Sign In" button was painted near-black on the dark
   header (~1.1:1 - effectively invisible). Restore its own light text, and keep hover a ghost
   hover (tinted panel + amber edge) instead of the solid-amber fill of the primary CTA. */
.nav-links a.btn.ghost {
  color: var(--text);
}

.nav-links a.btn.ghost:hover {
  color: #fff;
  background: var(--panel);
  border-color: var(--accent);
}

.btn {
  display: inline-block;
  cursor: pointer;
  background: var(--accent);
  color: #11161d;
  font-weight: 700;
  padding: 11px 20px;
  border-radius: 9px;
  border: none;
  transition: transform .08s ease, filter .15s ease;
}

.btn:hover {
  filter: brightness(1.06);
  text-decoration: none;
  transform: translateY(-1px);
}

.btn.ghost {
  background: transparent;
  color: var(--text);
  /* Brighter than --line so the button edge clears 3:1 against the page (WCAG 1.4.11) -
     a ghost button framed in --line read as plain body text. */
  border: 1px solid #6982a8;
}

.btn.ghost:hover {
  border-color: var(--accent);
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid #6982a8;
  color: var(--text);
  border-radius: 8px;
  padding: 8px 11px;
  font-size: 1.1rem;
  cursor: pointer;
}

/* ---------- Hero ---------- */
.hero {
  padding: 84px 0 64px;
  position: relative;
  overflow: hidden;
}

/* ---------- The ghosted Smith chart behind the hero ----------
   Texture, not decoration: it is the instrument the shop actually reads, drawn correctly
   (see the geometry note in index.html) and turned down until it is barely there.

   It sits to the RIGHT because the hero's copy is capped at 640px, so that half of the
   panel is empty on a desktop - and it runs off the edge rather than being centred in the
   gap, which is what keeps it reading as texture instead of as a picture placed there. */
.hero-smith {
  position: absolute;
  top: 50%;
  right: -11%;
  transform: translateY(-50%);
  width: min(720px, 58%);
  height: auto;
  pointer-events: none;   /* it is behind the CTAs; it must never eat a click */
  z-index: 0;
}

/* non-scaling-stroke so the lines stay hairlines at any size. Without it the strokes
   scale with the chart and thicken into something you notice, which is the one thing a
   background must not do. Two weights only: the grid, and the two lines that say "this is
   a chart" - the real axis and the unit circle. */
.hero-plot .sc-grid {
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: .085;
}

.hero-plot .sc-axis {
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: .16;
}

/* The measurement scales. The tick band is what makes a Smith chart recognisable at a
   glance, so the majors carry a touch more weight than the minors and the two rings that
   hold them sit between the grid and the axis. */
.hero-plot .sc-ring {
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: .11;
}

.hero-plot .sc-tick {
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: .09;
}

.hero-plot .sc-tick-major {
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: .17;
}

/* THE PLOT. The only colour on the graphic, because it is the only thing here that is a
   measurement rather than the paper it is printed on - so it carries the brand amber and
   sits well above the chart's opacity while still staying texture. */
.hero-plot .sc-plot {
  stroke: var(--accent);
  stroke-width: 2.25;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .5;
}

.hero-plot .sc-dots {
  opacity: .45;
}

/* The VSWR 2:1 limit, |Γ| = 1/3 - the circle a match is judged against. Dashed and faint:
   it is a reference, not a reading. */
.hero-plot .sc-vswr {
  stroke: var(--accent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 6 9;
  opacity: .18;
}

/* The copy has to sit above it - .hero is the positioning context, so without this the
   chart would paint over the eyebrow and the CTAs. */
.hero .wrap {
  position: relative;
  z-index: 1;
}

/* Below the breakpoint the copy fills the width, so the chart would be behind the text
   rather than beside it. Texture that reduces legibility has stopped being texture. */
@media (max-width: 900px) {
  .hero-smith,
  .hero-tech { display: none; }
}

/* ---------- The same treatment on every section's first panel ----------
   Owner, 2026-08-05: continue the technical-graphic treatment onto the first page of each
   section in the menu. Each page gets the instrument that belongs to it - the filter set
   on Services, its return loss on Parts, Otto's 1-60 MHz spectrum on Capabilities, a Yagi
   pattern on About, a dipole's elevation on Contact - all drawn from the real formulas
   (see the note above each SVG) and all wearing the .hero-plot language, so nothing can
   drift out of step with the Smith chart on Home.

   .page-hero needed position/overflow it never had: the graphic is absolutely positioned
   inside it and runs off the right edge, which only reads as "continuing past the frame"
   if the section clips it. */
.page-hero {
  position: relative;
  overflow: hidden;
}

.hero-tech {
  position: absolute;
  top: 50%;
  right: -4%;
  transform: translateY(-50%);
  /* SIZED BY HEIGHT, not width. A .page-hero is roughly 250px tall against the home
     hero's 400+, and a sweep sized to the width overflowed it top and bottom - which
     clips the PEAKS off a filter response and just looks broken, where bleeding off the
     right edge reads as "continues past the frame". Height first, width follows. */
  /* BOUNDED BOTH WAYS. Sized by width alone it overflowed a short .page-hero and clipped
     the peaks off a filter response; sized by height alone it grew wide enough on a
     taller hero (capabilities, whose h1 wraps to two lines) to run under the body copy.
     A width that leaves the 640px copy column alone, and a max-height that shrinks it
     proportionally when the panel is short. */
  width: min(620px, 44%);
  height: auto;
  max-height: 86%;
  pointer-events: none;
  z-index: 0;
}

/* Six traces at the single-trace opacity is six times the ink. These sit behind body copy
   in a shorter panel than the home hero, so the amber comes down to where it reads as
   texture again rather than as the subject of the page. */
.hero-tech .sc-plot {
  opacity: .3;
  stroke-width: 1.75;
}

/* The second trace on a two-trace sweep - return loss beside insertion loss. Cyan, the
   other brand signal, so the two curves are told apart at a glance without a legend
   nobody would read at 30% opacity. */
.hero-tech .sc-plot-2 {
  stroke: var(--accent-2);
  opacity: .26;
}

.hero-tech.is-polar .sc-plot {
  opacity: .42;
}

/* The polar plots are square, so they want less width than a wide sweep or they tower
   over a short page-hero. */
.hero-tech.is-polar {
  width: min(360px, 26%);
  max-height: 132%;
  right: 3%;
}

.page-hero .wrap {
  position: relative;
  z-index: 1;
}

.hero .eyebrow {
  font-family: var(--mono);
  color: var(--accent-2);
  letter-spacing: 2px;
  text-transform: uppercase;
  font-size: .8rem;
  margin-bottom: 14px;
}

.hero h1 {
  font-size: clamp(2.1rem, 5vw, 3.4rem);
  line-height: 1.08;
  margin: 0 0 18px;
  letter-spacing: -.5px;
}

.hero h1 .hl {
  color: var(--accent);
}

.hero p.lead {
  font-size: 1.2rem;
  color: var(--muted);
  max-width: 640px;
  margin: 0 0 30px;
}

.hero-cta {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}

.hero-meta {
  display: flex;
  gap: 28px;
  flex-wrap: wrap;
  margin-top: 40px;
  color: var(--muted);
  font-size: .92rem;
}

.hero-meta b {
  color: var(--text);
  display: block;
  font-size: 1.5rem;
  font-family: var(--mono);
}

/* ---------- Sections ---------- */
section {
  padding: 64px 0;
}

.section-head {
  max-width: 680px;
  margin-bottom: 40px;
}

.section-head .kicker {
  font-family: var(--mono);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 2px;
  font-size: .78rem;
}

.section-head h2 {
  font-size: clamp(1.7rem, 3.5vw, 2.4rem);
  margin: 10px 0 12px;
  letter-spacing: -.3px;
}

.section-head p {
  color: var(--muted);
  font-size: 1.08rem;
  margin: 0;
}

.alt {
  background: linear-gradient(180deg, var(--bg-2), transparent);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/* ---------- Cards / grid ---------- */
.grid {
  display: grid;
  gap: 20px;
}

/* minmax(0, 1fr) not 1fr: a plain 1fr track floors at its content's min-width, so a long
   unbroken word or a wide child pushes the track - and the whole row - past the viewport.
   minmax(0, ...) lets the track shrink and the content wrap instead. */
.grid.cols-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.grid.cols-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.grid.cols-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.card {
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 26px 24px;
  transition: border-color .15s ease, transform .15s ease;
}

.card:hover {
  border-color: rgba(255, 158, 44, .5);
  transform: translateY(-3px);
}

.card .ico {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  margin-bottom: 16px;
  display: grid;
  place-items: center;
  background: rgba(255, 158, 44, .12);
  border: 1px solid rgba(255, 158, 44, .3);
}

/* Normalized product thumbnail: uniform frame regardless of source image aspect */
.card .shot {
  display: block;
  width: 100%;
  height: 150px;
  object-fit: contain;
  object-position: center;
  background: #f5f7fa;
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 10px;
  margin-bottom: 16px;
}

.card h3 {
  margin: 0 0 8px;
  font-size: 1.18rem;
}

.card p {
  margin: 0;
  color: var(--muted);
  font-size: .97rem;
}

.card .price {
  font-family: var(--mono);
  color: var(--accent-2);
  font-size: .86rem;
  margin-top: 14px;
}

/* Otto software screenshots (dark UI, natural aspect, click for full size) */
.card .benchshot {
  display: block;
  width: 100%;
  height: auto;
  background: #0a0f16;
  border: 1px solid var(--line);
  border-radius: 9px;
  margin-bottom: 14px;
  transition: border-color .15s ease;
}

a:hover > .benchshot {
  border-color: var(--accent-2);
}

/* small external "manufacturer product page" link on service cards */
.card .prodlink {
  display: inline-block;
  margin-top: 10px;
  font-size: .85rem;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px dotted var(--line);
}

.card .prodlink:hover {
  color: var(--accent-2);
  border-bottom-color: var(--accent-2);
}

/* feature list */
.features {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 14px;
}

.features li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  color: var(--muted);
}

.features li svg {
  flex: 0 0 22px;
  margin-top: 2px;
}

.features li b {
  color: var(--text);
}

/* ---------- Process steps ---------- */
.steps {
  counter-reset: step;
  display: grid;
  gap: 18px;
  grid-template-columns: repeat(4, 1fr);
}

.step {
  position: relative;
  padding: 24px 20px 22px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.step::before {
  counter-increment: step;
  content: counter(step, decimal-leading-zero);
  font-family: var(--mono);
  color: var(--accent);
  font-size: .9rem;
  font-weight: 700;
}

.step h4 {
  margin: 8px 0 6px;
  font-size: 1.05rem;
}

.step p {
  margin: 0;
  color: var(--muted);
  font-size: .92rem;
}

/* ---------- Split / about ---------- */
.split {
  display: grid;
  grid-template-columns: 1.1fr .9fr;
  gap: 44px;
  align-items: center;
}

/* A grid item's default minimum is its min-content, so a panel-box form (with 30px padding
   + form fields) floors the 1fr track well above the viewport, overflowing on a phone. Let
   the columns shrink instead. */
.split > * {
  min-width: 0;
}

.split.rev {
  grid-template-columns: .9fr 1.1fr;
}

.panel-box {
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 30px;
}

/* A label/value row. `flex-wrap: wrap` is load-bearing, not decoration: a nowrap flex line
   has NO minimum it can fall back to, so a label + value pair that doesn't fit simply runs
   out of the panel and drags the whole document with it. That is what was shipping - the
   pricing panel's "Bench eval (flat) / $95, credited toward repair" row (index.html and
   services.html) measured right:416 inside a 254px panel, and documentElement.scrollWidth
   came back 416 against a 375 clientWidth: the page scrolled sideways on a phone. Wrapping
   the row costs nothing at any width where it already fit (desktop is byte-identical) and
   gives every future spec row somewhere to go. */
.specrow {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 11px 0;
  border-bottom: 1px dashed var(--line);
  font-size: .95rem;
}

.specrow:last-child {
  border-bottom: none;
}

.specrow span {
  color: var(--muted);
}

.specrow b {
  color: var(--text);
  font-family: var(--mono);
}

/* ---------- CTA band ---------- */
.cta-band {
  text-align: center;
}

.cta-band .panel-box {
  padding: 48px 30px;
}

.cta-band h2 {
  font-size: clamp(1.6rem, 3.5vw, 2.3rem);
  margin: 0 0 12px;
}

.cta-band p {
  color: var(--muted);
  max-width: 560px;
  margin: 0 auto 26px;
  font-size: 1.05rem;
}

/* ---------- Forms ---------- */
.form-grid {
  display: grid;
  gap: 18px;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 7px;
  /* Let the field (a form-grid cell) shrink below its input's intrinsic width instead of
     flooring the grid track and overflowing on a phone. */
  min-width: 0;
}

.field.full {
  grid-column: 1 / -1;
}

.field label {
  font-size: .9rem;
  color: var(--text);
  font-weight: 600;
}

.field .req,
.form-legend .req {
  color: var(--accent);
}

.form-legend {
  margin: 0 0 14px;
  font-size: .85rem;
  color: var(--muted);
}

.field input,
.field select,
.field textarea {
  background: var(--bg-2);
  /* A brighter border than --line: a form field needs a visible boundary (WCAG 1.4.11
     non-text contrast, 3:1). --line (#243245) against the field fill measured ~1.35:1, so
     the input edges were nearly invisible on the dark panels. #6982a8 measures >=3.8:1
     against the field fill, both panel shades, and the page background. */
  border: 1px solid #6982a8;
  color: var(--text);
  border-radius: 9px;
  padding: 12px 13px;
  font: inherit;
  font-size: .97rem;
  transition: border-color .15s ease;
  /* Full width of the field, and allowed to shrink - an input's default intrinsic width
     would otherwise push the field (and the page) wider than a phone viewport. */
  width: 100%;
  min-width: 0;
}

/* Inline consent checkbox (e.g. the SMS opt-in on the service form): the generic .field
   input rules above (width:100%, text-box padding, panel background) are meant for text
   inputs and wreck a checkbox - it goes full-width and shoves its label text out of the
   panel. Reset it to a small fixed box sitting beside its text. */
.field.consent label {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  font-weight: 400;
  cursor: pointer;
}

.field.consent input[type="checkbox"] {
  width: 16px;
  height: 16px;
  min-width: 16px;
  padding: 0;
  margin: 3px 0 0;
  flex: none;
  accent-color: var(--accent-2);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  /* A transparent outline is invisible normally but becomes a real focus ring under Windows
     High Contrast / forced-colors, where the box-shadow below is dropped. */
  outline: 2px solid transparent;
  outline-offset: 2px;
  border-color: var(--accent-2);
  box-shadow: 0 0 0 3px rgba(56, 197, 255, .12);
}

/* Themed keyboard-focus ring for links, buttons and disclosures (which otherwise fell back
   to the UA default). :focus-visible means pointer clicks don't get a ring, only keyboard. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
.btn:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}

/* Respect a reduced-motion preference: no smooth-scroll, no hover lift, no transitions. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .btn,
  .card {
    transition: none;
  }
  .btn:hover,
  .card:hover {
    transform: none;
  }
}

.field textarea {
  resize: vertical;
  min-height: 130px;
}

/* ---------- Live bench load (three unit counts) ----------
   Three facts, given room to read as facts, under the pricing panel. */
.bench-load {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

.bench-load-grid {
  display: grid;
  /* auto-fit, NOT repeat(4, 1fr), and NOT a viewport media query: this block lives inside
     the pricing card, which is ~400px wide at ANY viewport - so a width-based breakpoint
     never fires where it is needed. Four hard columns measured 93px each and wrapped
     "here, awaiting estimate" onto three lines on a 1523px desktop. minmax lets the
     CONTAINER decide: 2x2 in the card, four across anywhere roomier. */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin-top: 14px;
}

.bench-stat {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 10px;
  text-align: center;
  min-width: 0;
}

.bench-stat b {
  display: block;
  font-family: var(--mono);
  font-size: 1.7rem;
  line-height: 1.1;
  color: var(--accent);
}

.bench-stat span {
  display: block;
  margin-top: 5px;
  font-size: .78rem;
  color: var(--muted);
  line-height: 1.35;
}

@media (max-width: 480px) {
  /* Even two columns of a two-word label wrap to one word per line on a phone; stack. */
  .bench-load-grid {
    grid-template-columns: 1fr;
  }
  .bench-stat {
    display: flex;
    align-items: baseline;
    gap: 10px;
    text-align: left;
  }
  .bench-stat b {
    font-size: 1.35rem;
  }
  .bench-stat span {
    margin-top: 0;
  }
}

/* ---------- Spam check (FCC Technician pool, multiple choice) ----------
   Reads as a small exam question rather than a chore: the prompt in body text, the four
   options as pickable rows with a lettered chip. Rows are full-width targets so a phone
   user hits the label, not just the 16px radio. */
.field.captcha {
  /* A four-option exam question is a paragraph-width thing: span the whole form. Without
     this the fieldset sat in ONE track of the two-column grid - a 180px sliver with the
     answers wrapping three words deep (owner's screenshot, 2026-07-31). */
  grid-column: 1 / -1;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-2);
  padding: 14px 16px 12px;
  margin-top: 16px;
  min-width: 0;               /* fieldset defaults to min-content; without this it refuses
                                 to shrink and forces the form to scroll sideways */
}

.captcha-legend {
  padding: 0 6px;
  font-size: .78rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

.captcha-q {
  margin: 2px 0 12px;
  color: var(--text);
  font-weight: 600;
  line-height: 1.45;
}

.captcha-choices {
  display: grid;
  gap: 6px;
}

.captcha-choice {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel);
  cursor: pointer;
  /* .field label sets 600; an option is body text, only the letter chip is emphasised. */
  font-weight: 400;
  transition: border-color .12s ease, background .12s ease;
}

.captcha-choice:hover {
  border-color: var(--accent-2);
}

/* Same reset the SMS consent checkbox needs, and for the same reason: the generic
   `.field input` rule above is written for text boxes (width:100%, text padding, panel
   fill) and wrecks a radio - it went 340px wide and shoved the letter and answer text
   clean outside the fieldset. The row is the control; the dot only anchors state. */
.captcha-choice input[type="radio"] {
  width: 16px;
  height: 16px;
  min-width: 16px;
  padding: 0;
  margin: 3px 0 0;
  flex: none;
  accent-color: var(--accent);
}

.captcha-choice:has(input:checked) {
  border-color: var(--accent);
  background: var(--panel-2);
}

.captcha-choice:has(input:focus-visible) {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}

.captcha-letter {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--accent);
  flex: 0 0 auto;
}

.captcha-choice-text {
  color: var(--text);
  line-height: 1.4;
}

.captcha-text {
  max-width: 220px;
}

.captcha-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 11px 0 0;
}

.captcha-source {
  font-size: .78rem;
  color: var(--muted);
}

/* A real button, styled as a quiet link - it must not compete with Send. */
.captcha-swap {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: .8rem;
  color: var(--accent-2);
  cursor: pointer;
  text-decoration: underline;
}

.captcha-swap:hover {
  color: var(--text);
}

.form-note {
  color: var(--muted);
  font-size: .86rem;
}

.form-status {
  margin-top: 8px;
  font-size: .95rem;
  min-height: 1.2em;
}

.form-status.ok {
  color: var(--good);
}

.form-status.err {
  color: #ff6b6b;
}

/* ---------- Footer ---------- */
.site-footer {
  border-top: 1px solid var(--line);
  background: var(--bg-2);
  margin-top: 24px;
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 32px;
  padding: 50px 0 30px;
}

.footer-grid h5 {
  font-size: .82rem;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--muted);
  margin: 0 0 14px;
}

.footer-grid a {
  color: var(--muted);
  display: block;
  padding: 4px 0;
}

.footer-grid a:hover {
  color: var(--text);
}

.footer-bottom {
  border-top: 1px solid var(--line);
  padding: 20px 0;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  color: var(--muted);
  font-size: .85rem;
}

.footer-bottom .mono {
  font-family: var(--mono);
}

/* ---------- Page hero (interior) ---------- */
.page-hero {
  padding: 64px 0 30px;
  border-bottom: 1px solid var(--line);
}

/* --- In-page jump nav (long catalog pages) ------------------------------------------
   A sticky chip row of section anchors that rides just below the site header, so a
   10,000px page like services.html is navigable without scrolling through everything.
   Horizontally scrollable on phones. */
.jump-nav {
  position: sticky;
  top: 61px;                       /* just under the sticky .site-header */
  z-index: 40;
  background: rgba(12, 17, 24, .92);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.jump-nav .wrap {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-top: 10px;
  padding-bottom: 10px;
  /* Hide the strip's scrollbar. `-ms-overflow-style` covers legacy Edge/IE, the
     `::-webkit-scrollbar` rule below covers Chrome/Safari, and `scrollbar-width`
     is the standard (Firefox, Chrome 121+). The compat linter flags the standard
     property on older browsers - the fallbacks here ARE the fix; behavior without
     any of them is merely a visible scrollbar. */
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.jump-nav .wrap::-webkit-scrollbar {
  display: none;
}

.jump-nav a {
  flex: none;
  font-size: .85rem;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 14px;
  white-space: nowrap;
}

.jump-nav a:hover {
  color: var(--text);
  border-color: var(--accent-2);
  text-decoration: none;
}

/* Anchored sections land below the sticky header + jump nav instead of under them. */
section[id] {
  scroll-margin-top: 118px;
}

/* --- Compact model tables ------------------------------------------------------------
   Catalog families with many similar units (switches, automation) read faster as one
   scannable table than as a wall of near-identical cards. */
.table-scroll {
  overflow-x: auto;
}

.model-table {
  width: 100%;
  border-collapse: collapse;
  font-size: .95rem;
}

.model-table th {
  text-align: left;
  font-family: var(--mono);
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--accent-2);
  padding: 10px 14px;
  border-bottom: 1px solid #6982a8;
}

.model-table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  vertical-align: top;
}

.model-table td:first-child {
  color: var(--text);
  font-weight: 600;
  white-space: nowrap;
}

.model-table tr:hover td {
  background: rgba(56, 197, 255, .04);
}

.model-table a {
  white-space: nowrap;
}

/* Opt-out of .split's vertical centering when one column is much taller (e.g. the
   contact page's tall form next to a short intro) - centering leaves the short column
   floating in a void. */
.split.top {
  align-items: start;
}

.page-hero .eyebrow {
  font-family: var(--mono);
  color: var(--accent-2);
  text-transform: uppercase;
  letter-spacing: 2px;
  font-size: .78rem;
}

.page-hero h1 {
  font-size: clamp(1.9rem, 4vw, 2.8rem);
  margin: 12px 0 12px;
}

.page-hero p {
  color: var(--muted);
  font-size: 1.1rem;
  max-width: 640px;
  margin: 0;
}

.badge {
  display: inline-block;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--accent-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px 12px;
  background: var(--panel);
}

/* ---------- Disclosure (progressive technical detail) ---------- */
.disclosure {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
}

.disclosure+.disclosure {
  margin-top: 12px;
}

.disclosure>summary {
  cursor: pointer;
  list-style: none;
  padding: 18px 22px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-weight: 600;
  color: var(--text);
  transition: color .15s ease;
}

.disclosure>summary::-webkit-details-marker {
  display: none;
}

.disclosure>summary::before {
  content: "+";
  font-family: var(--mono);
  color: var(--accent);
  font-size: 1.3rem;
  line-height: 1;
  width: 16px;
  flex: 0 0 16px;
  text-align: center;
}

.disclosure[open]>summary::before {
  content: "\2013";
}

.disclosure>summary:hover {
  color: var(--accent);
}

.disclosure>summary .sub {
  margin-left: auto;
  color: var(--muted);
  font-weight: 400;
  font-size: .86rem;
  font-family: var(--mono);
}

.disclosure-body {
  padding: 18px 22px 22px;
  margin: 0 22px;
  border-top: 1px dashed var(--line);
  color: var(--muted);
  font-size: .97rem;
}

.disclosure-body> :first-child {
  margin-top: 0;
}

.disclosure-body> :last-child {
  margin-bottom: 0;
}

.disclosure-body b {
  color: var(--text);
}

/* ---------- Responsive ---------- */
@media (max-width: 900px) {

  .grid.cols-3,
  .grid.cols-4 {
    grid-template-columns: repeat(2, 1fr);
  }

  .steps {
    grid-template-columns: repeat(2, 1fr);
  }

  .split,
  .split.rev {
    grid-template-columns: 1fr;
  }

  .footer-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* The nav collapses to the hamburger on its OWN breakpoint, not the layout one below.
   The horizontal nav is a fixed cost: brand 266px + six links and two buttons 682px +
   the .wrap's 44px of padding = 992px of content width before it fits. The collapse used
   to live in the 620px block with the grids, so between those two numbers the row simply
   ran off the page - every one of the nine pages measured scrollWidth 970 against a 753
   clientWidth at a 768px viewport (iPad portrait), 345px of sideways scroll at 640. The
   grids genuinely want 620; the nav wants 1000. They are different questions, so they get
   different queries. Nothing above 1000px changes - desktop keeps the full row. */
@media (max-width: 1000px) {
  .nav-links {
    display: none;
    position: absolute;
    top: 68px;
    left: 0;
    right: 0;
    flex-direction: column;
    background: var(--bg-2);
    border-bottom: 1px solid var(--line);
    padding: 12px 18px;
    gap: 2px;
  }

  .nav-links.open {
    display: flex;
  }

  .nav-links a {
    padding: 10px 4px;
  }

  .nav-toggle {
    display: block;
  }
}

@media (max-width: 620px) {
  /* One column for everything on a phone. Without this the two-up card grids and the
     two-up form grid stay side by side and force horizontal page scroll (contact.html
     measured 644px wide at a 375px viewport). */
  .grid.cols-2,
  .grid.cols-3,
  .grid.cols-4,
  .steps,
  .form-grid {
    grid-template-columns: 1fr;
  }
}

/* ---------- Text wrapping ---------- */
/* Balance heading line breaks (avoids widowed words). Progressive enhancement behind
   @supports so browsers that predate `text-wrap` (Chrome < 114) skip the whole block
   cleanly - headings simply wrap normally there. */
@supports (text-wrap: balance) {
  h1, h2, h3 {
    text-wrap: balance;
  }
}

/* Keep a natural-speech phrase on one line. Use only on short phrases so
   narrow screens don't overflow. */
.nobr {
  white-space: nowrap;
}


/* ---------- The open-book-bench banner (sitewide masthead) ----------
   One picture, one argument: the calibrated sweep runs off the instruments and becomes
   the underline of the customer's total - the measurement becomes the bill. */
.banner-art {
  padding: 8px 0 34px;
}

/* NOTHING MATCHES .banner-art.masthead RIGHT NOW. The graphic was dropped from every page
   on 2026-08-04 ("just drop the graphic for now - we'll revisit it later"); the rules are
   kept, unchanged, so putting it back is one <section> per page. The history below is why
   each value is what it is - read it before changing any of them.

   The masthead placement: the art sits ABOVE the sticky menu (owner, 2026-08-02), so it
   opens the page and scrolls away while the nav stays. Capped to a standard internet
   banner height (owner: "reduce the banner size") - the SVG keeps its aspect, so the cap
   sizes it down and centers it. Phones swap in the compact composition via <picture> and
   let it run the width.
   The cap is deliberately ONE value for every page (owner, 2026-08-03: "the menu bar
   jumps up and down"). Every marketing page carries this same block, so the nav starts at
   the same offset sitewide and holds still when you move between pages - a shorter
   interior banner would put the jump straight back. Do not scope this per page. */
.banner-art.masthead {
  padding: 10px 0 12px;
}

.banner-art.masthead img {
  width: auto;
  max-width: 100%;
  max-height: 210px;
  margin: 0 auto;
}

@media (max-width: 640px) {
  .banner-art.masthead img {
    width: 100%;
    max-height: none;
  }
}

.banner-art img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* ---------------------------------------------------------------- the shop, in one row
   The same live counts as .bench-load, compacted to a single strip for the hero (owner,
   2026-08-09: "a smaller single row version ... under the manufacturer authorized text",
   "I want that transparency in your face").

   IT IS THE SAME DATA AND THE SAME RULE. Both blocks carry `data-bench-load` and ship
   `hidden`; site.js fills whichever are on the page and reveals them only once the API has
   answered. If the shop's own system cannot be reached, the page makes no claim at all
   rather than showing a stale one - which is the entire reason the numbers are worth
   putting at the top.

   Sits ABOVE the headline, so it is read before the sales copy rather than after it. */
.bench-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
  margin: 10px 0 18px;
  padding: 10px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  /* Never wider than the prose it sits under - a strip running the full hero width reads
     as a toolbar rather than as a fact about the shop. */
  max-width: 46rem;
}

.bench-strip .bench-strip-label {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent-2);
  white-space: nowrap;
}

.bench-strip-item {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
  font-size: .84rem;
  color: var(--muted);
}

.bench-strip-item b {
  font-family: var(--mono);
  font-size: 1.15rem;
  color: var(--accent);
  line-height: 1;
}

@media (max-width: 620px) {
  .bench-strip {
    /* A pill that has wrapped to four lines is not a pill. */
    border-radius: 14px;
    gap: 6px 14px;
  }
  .bench-strip-item {
    font-size: .8rem;
  }
}
