/* ==========================================================================
   Charlie Brown Electric
   Licensed residential electrician, Charlotte NC.

   Design note: the brand's black-and-yellow is inherited from an older logo that
   carried a cartoon character. That gives the palette a pull toward "playful",
   which is the last thing a homeowner wants from the person touching their service
   panel. So the whole system is deliberately technical - schematic linework, mono
   for anything that reads as a specification, no rounded corners, no bounce.
   The warmth comes entirely from Charlie's photographs.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Brand. --volt and --carbon are taken from the logo SVGs. */
  --volt: #fec305;
  --volt-dim: #c99a0c;
  --carbon: #060606;
  --steel: #1a1c1f;   /* raised surfaces - cool-shifted so it reads as metal */
  --conduit: #6b7076; /* secondary text, de-energised UI */
  --copper: #b87333;  /* the conductor at rest. Hairline only, never a fill. */
  --white: #ffffff;
  --paper: #f4f4f2;   /* ground for the inverted section */

  --line: color-mix(in srgb, var(--conduit) 34%, transparent);
  --line-strong: color-mix(in srgb, var(--conduit) 60%, transparent);

  --font-display: 'Archivo', 'Arial Narrow', Helvetica, Arial, sans-serif;
  --font-body: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    Helvetica, Arial, sans-serif;
  --font-mono: 'DM Mono', ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono',
    monospace;

  /* Fluid type and space are both defined across the full range this site has to
     survive: 390px phone to a 2560px desktop. Every step interpolates the whole
     way. An earlier version capped out around 1440, which made a 27" monitor show
     the same size text as a laptop surrounded by dead space - the exact failure
     this scale exists to prevent. */
  --step--2: clamp(0.7rem, 0.669rem + 0.129vw, 0.875rem);
  --step--1: clamp(0.82rem, 0.775rem + 0.18vw, 1.06rem);
  --step-0: clamp(1rem, 0.933rem + 0.277vw, 1.375rem);
  --step-1: clamp(1.2rem, 1.079rem + 0.498vw, 1.875rem);
  --step-2: clamp(1.45rem, 1.205rem + 1.005vw, 2.81rem);
  --step-3: clamp(1.85rem, 1.351rem + 2.046vw, 4.625rem);
  --step-4: clamp(2.1rem, 1.399rem + 2.876vw, 6rem);
  --step-5: clamp(2.5rem, 1.332rem + 4.793vw, 9rem);

  /* Vertical rhythm scales with the same span, so sections get proportionally
     more breathing room on a big screen instead of the content drifting apart. */
  --space-3xs: clamp(0.25rem, 0.228rem + 0.092vw, 0.375rem);
  --space-2xs: clamp(0.5rem, 0.455rem + 0.184vw, 0.75rem);
  --space-xs: clamp(0.75rem, 0.683rem + 0.277vw, 1.125rem);
  --space-s: clamp(1rem, 0.91rem + 0.369vw, 1.5rem);
  --space-m: clamp(1.5rem, 1.365rem + 0.553vw, 2.25rem);
  --space-l: clamp(2rem, 1.73rem + 1.106vw, 3.5rem);
  --space-xl: clamp(3rem, 2.46rem + 2.212vw, 6rem);
  --space-2xl: clamp(4rem, 3.1rem + 3.687vw, 9rem);
  --space-3xl: clamp(5.5rem, 4.15rem + 5.53vw, 13rem);

  /* Running text never exceeds a comfortable measure, at any viewport width.
     Because it is in ch it scales with the clamped font size automatically.
     Note ch is the width of "0", which is wider than an average lowercase
     character - 62ch measures out at roughly 72 real characters per line, which
     is where we want it. Setting it to 75ch would land nearer 88. */
  --measure: 62ch;

  /* Page gutter. The conductor rail lives inside this. */
  --gutter: clamp(1.25rem, -0.32rem + 6.452vw, 10rem);
  --rail-x: clamp(0.75rem, -0.194rem + 3.871vw, 6rem);

  --header-h: clamp(3.75rem, 3.256rem + 2.028vw, 6.5rem);

  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored sections must clear the fixed header. */
  scroll-padding-top: calc(var(--header-h) + var(--space-m));
}

body {
  margin: 0;
  background: var(--carbon);
  color: var(--white);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden; /* the conductor rail must never create a scrollbar */
}

img,
picture,
svg { display: block; max-width: 100%; }

img { height: auto; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 800;
  font-variation-settings: 'wdth' 112;
  line-height: 0.98;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  text-wrap: balance;
  margin: 0;
}

p { margin: 0; text-wrap: pretty; }

/* Every block of running prose is capped, wherever it appears. */
p, li { max-width: var(--measure); }

ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; }

::selection { background: var(--volt); color: var(--carbon); }

:focus-visible {
  outline: 3px solid var(--volt);
  outline-offset: 3px;
}

.skip-link {
  position: absolute;
  left: var(--space-s);
  top: -100vh;
  z-index: 100;
  padding: var(--space-xs) var(--space-m);
  background: var(--volt);
  color: var(--carbon);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  text-decoration: none;
}
.skip-link:focus { top: var(--space-s); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */

.section {
  position: relative;
  padding-block: var(--space-3xl);
}

.section__inner {
  padding-inline: var(--gutter);
  /* No max-width. The page is full-bleed; only the prose is constrained. */
}

/* On a wide screen a section intro stacked above its content leaves the right
   half of the viewport empty, because the prose below it is measure-capped. In
   split sections the intro moves alongside instead, and sticks while the list
   scrolls past - which also keeps the primary CTA on screen throughout. */
.section--split .section__inner { display: grid; gap: var(--space-xl); }

@media (min-width: 72rem) {
  .section--split .section__inner {
    grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.7fr);
    gap: var(--space-3xl);
    align-items: start;
  }

  .section--split .section__intro {
    position: sticky;
    top: calc(var(--header-h) + var(--space-xl));
  }
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--volt);
  margin-bottom: var(--space-s);
}

.section__title { font-size: var(--step-3); }

.lede {
  font-size: var(--step-1);
  line-height: 1.5;
  color: color-mix(in srgb, var(--white) 82%, transparent);
  margin-top: var(--space-m);
}

/* ---- Entrance animations ----
   Headings used to strike on like a fluorescent tube. It read as the site
   malfunctioning rather than as a deliberate effect, so it is gone. Everything
   now enters the ordinary way - a short rise and fade, or a slide in from the
   right - triggered once when its section is fed.

   The elements start hidden in CSS only when the reveal script is present
   (html.js). Without JS nothing is ever hidden, so the page still reads. */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
}

.js [data-reveal="right"] {
  transform: translateX(clamp(24px, 4vw, 64px));
}

.js [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition:
    opacity 680ms var(--ease) var(--reveal-delay, 0ms),
    transform 780ms var(--ease) var(--reveal-delay, 0ms);
}

/* A hairline rule that reads as a schematic divider rather than decoration. */
.rule {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 0;
}

/* --------------------------------------------------------------------------
   4. The conductor
   The signature element: one continuous run down the page, energised by scroll.
   Base rail is copper (the metal); the travelling current is volt (the power).
   -------------------------------------------------------------------------- */

.conductor {
  position: fixed;
  top: 0;
  left: var(--rail-x);
  width: 1px;
  height: 100vh;
  z-index: 2;
  pointer-events: none;
  background: color-mix(in srgb, var(--copper) 45%, transparent);
}

/* The energised portion of the run. Sized by height rather than scaleY, because a
   scaled parent would stretch the current pulses inside it out of shape. */
.conductor__live {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: calc(var(--charge, 0) * 100%);
  background: linear-gradient(
    to bottom,
    var(--volt) 0%,
    var(--volt) 88%,
    color-mix(in srgb, var(--volt) 35%, transparent) 100%
  );
  /* NOT overflow:hidden. That clips both axes, and it was cutting every pixel
     of the pulse glow off at the 1px rail - which is why the glow was
     imperceptible. This clips to the charged length vertically while letting
     the glow bleed sideways. */
  clip-path: inset(0 -60px);
}

/* Actual current in the conductor: bright pulses running continuously down the
   energised section. This is what makes the rail read as live rather than filled. */
.conductor__live::after {
  content: '';
  position: absolute;
  left: -1px;
  right: -1px;
  top: 0;
  height: 200%;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0 92px,
    color-mix(in srgb, var(--white) 70%, transparent) 104px,
    var(--white) 110px,
    var(--white) 122px,
    color-mix(in srgb, var(--white) 70%, transparent) 128px,
    transparent 140px 200px
  );
  /* A 1px rail gives the pulses nowhere to be seen, so the glow is doing all
     the work. Three stacked shadows: a tight white core, a volt bloom, and a
     wide soft falloff. */
  filter:
    drop-shadow(0 0 3px var(--white))
    drop-shadow(0 0 8px var(--volt))
    drop-shadow(0 0 20px var(--volt))
    drop-shadow(0 0 34px color-mix(in srgb, var(--volt) 60%, transparent));
  animation: currentFlow 1.9s linear infinite;
}

/* One gradient period, so the loop is seamless. */
@keyframes currentFlow {
  from { transform: translateY(0); }
  to   { transform: translateY(200px); }
}

/* The leading edge of the current, sitting at the tip of the energised run. */
.conductor__head {
  position: absolute;
  left: 50%;
  top: 0;
  width: 7px;
  height: 7px;
  margin-left: -3px;
  margin-top: -3px;
  background: var(--volt);
  transform: translateY(calc(var(--charge, 0) * 100vh));
  will-change: transform;
}

/* Separated from the head so the glow can pulse without touching its transform. */
.conductor__head::before {
  content: '';
  position: absolute;
  inset: -2px;
  background: var(--volt);
  box-shadow: 0 0 12px 2px color-mix(in srgb, var(--volt) 75%, transparent);
  animation: headPulse 1.5s ease-in-out infinite;
}

@keyframes headPulse {
  0%, 100% {
    opacity: 0.8;
    box-shadow: 0 0 10px 2px color-mix(in srgb, var(--volt) 70%, transparent),
                0 0 26px 6px color-mix(in srgb, var(--volt) 40%, transparent);
  }
  50% {
    opacity: 1;
    box-shadow: 0 0 16px 4px var(--volt),
                0 0 40px 12px color-mix(in srgb, var(--volt) 55%, transparent);
  }
}

/* Junction: a stub off the rail into each section, with a node that lights when
   the section is in view. Mirrors how a circuit taps off a run to feed a load. */
.junction {
  position: absolute;
  left: calc(var(--rail-x) - var(--gutter));
  top: var(--space-3xl);
  width: calc(var(--gutter) - var(--rail-x) - var(--space-s));
  height: 1px;
  background: color-mix(in srgb, var(--copper) 45%, transparent);
  pointer-events: none;
  transition: background 500ms var(--ease);
}

.junction::after {
  content: '';
  position: absolute;
  right: -3px;
  top: -3px;
  width: 7px;
  height: 7px;
  background: var(--steel);
  border: 1px solid color-mix(in srgb, var(--copper) 60%, transparent);
  transition: background 450ms var(--ease), border-color 450ms var(--ease),
    box-shadow 450ms var(--ease);
}

/* A junction fires once when its section is first fed. */
.junction::before {
  content: '';
  position: absolute;
  right: -9px;
  top: -9px;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--volt) 0%, transparent 70%);
  opacity: 0;
  pointer-events: none;
}

.is-live .junction { background: var(--volt); }
.is-live .junction::before { animation: spark 900ms var(--ease) forwards; }

@keyframes spark {
  0%   { opacity: 0;    transform: scale(0.2); }
  25%  { opacity: 0.95; transform: scale(1.5); }
  100% { opacity: 0;    transform: scale(2.6); }
}

.is-live .junction::after {
  background: var(--volt);
  border-color: var(--volt);
  box-shadow: 0 0 12px 1px color-mix(in srgb, var(--volt) 55%, transparent);
}

/* Below the point where the gutter is too narrow for a stub, hide the junctions
   and keep only the rail. */
@media (max-width: 46rem) {
  .junction { display: none; }
}

/* --------------------------------------------------------------------------
   5. Buttons and links
   One primary action. Everything else is quieter than it.
   -------------------------------------------------------------------------- */

.btn {
  --btn-pad-y: var(--space-xs);
  --btn-pad-x: var(--space-m);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  padding: var(--btn-pad-y) var(--btn-pad-x);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  /* A wrapped label makes the fixed header taller, which shifts the whole page
     down. Buttons are always one line. */
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  transition: color 220ms var(--ease), border-color 220ms var(--ease),
    background 220ms var(--ease);
}

/* Buttons are wired into the same circuit as everything else on the page.

   A dim conductor outline carries short bright charges that chase around it
   continuously - the control is live before you touch it, which is what makes
   it read as powered rather than decorated. On hover the current REVERSES and
   speeds up about five-fold.

   The reversal is done by cross-fading two sets of charges rather than by
   changing animation-direction on one set, because retiming an animation
   mid-flight makes it visibly jump.

   pathLength=1000 normalises the outline, so one set of dash values works on
   every button whatever its size or aspect ratio - a wide CTA and a small
   header button chase at the same apparent speed.

   The SVG is injected by js/conductor.js so the markup stays clean. It is
   decoration: with no JS the buttons simply have no edge. */

.btn__edge {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Above the button's own background, below its label. */
  z-index: -1;
  pointer-events: none;
  overflow: visible;
  animation: edgeFlicker 2.3s steps(1, end) infinite;
}

.btn__edge rect {
  fill: none;
  vector-effect: non-scaling-stroke;
}

.btn__edge .rail {
  stroke: color-mix(in srgb, var(--copper) 75%, transparent);
  stroke-width: 1.2;
}

.btn__edge .charge,
.btn__edge .surge {
  stroke: var(--volt);
  stroke-width: 2.2;
  stroke-dasharray: 70 930;
  filter: drop-shadow(0 0 5px var(--volt));
  animation: chase 3s linear infinite;
}

.btn__edge .charge--b,
.btn__edge .surge--b {
  stroke: var(--white);
  stroke-width: 1.4;
  stroke-dasharray: 34 966;
  animation-duration: 1.9s;
  animation-delay: -0.9s;
}

.btn__edge .charge--c,
.btn__edge .surge--c {
  stroke-width: 1.6;
  stroke-dasharray: 22 978;
  animation-duration: 4.4s;
  animation-delay: -2.4s;
  opacity: 0.75;
}

/* The hover set: same charges running backwards, five times faster. */
.btn__edge .surge {
  opacity: 0;
  animation-duration: 0.55s;
  animation-direction: reverse;
  transition: opacity 150ms var(--ease);
}
.btn__edge .surge--b { animation-duration: 0.4s; animation-delay: -0.2s; }
.btn__edge .surge--c { animation-duration: 0.7s; animation-delay: -0.35s; opacity: 0; }

.btn:hover .btn__edge .charge,
.btn:focus-visible .btn__edge .charge { opacity: 0; transition: opacity 150ms var(--ease); }

.btn:hover .btn__edge .surge,
.btn:focus-visible .btn__edge .surge { opacity: 1; }
.btn:hover .btn__edge .surge--c,
.btn:focus-visible .btn__edge .surge--c { opacity: 0.8; }

@keyframes chase { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }

/* Irregular, on hard steps. A discharge does not ease. */
@keyframes edgeFlicker {
  0%, 100% { opacity: 1; }
  7%   { opacity: 0.45; }
  9%   { opacity: 1; }
  38%  { opacity: 0.7; }
  40%  { opacity: 1; }
  63%  { opacity: 0.35; }
  66%  { opacity: 1; }
  81%  { opacity: 0.8; }
  83%  { opacity: 1; }
}

/* On a volt face a volt charge is invisible, so the primary runs white current
   over a dark rail instead.

   The rail is deliberately wider than the charges that run along it. It is a
   channel, not a hairline: it gives the white current a dark backdrop to read
   against whether the face is brand yellow at rest or almost white on hover.
   Without it the current washes out the moment the button lights up. */
.btn--primary .btn__edge .rail {
  stroke: color-mix(in srgb, var(--carbon) 72%, transparent);
  stroke-width: 3.4;
}
.btn--primary .btn__edge .charge,
.btn--primary .btn__edge .surge {
  stroke: var(--white);
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--white) 85%, transparent));
}
.btn--primary .btn__edge .charge--c,
.btn--primary .btn__edge .surge--c {
  stroke: var(--carbon);
  filter: none;
}

/* The fill still sweeps on hover - it is the plain hover affordance, and the
   edge is the character.

   It sits a layer BELOW the edge deliberately. Both were at -1, and since
   ::after is always the element's last child it painted over the edge and made
   the whole animation vanish at exactly the moment you hovered. */
.btn::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background: var(--volt);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 320ms var(--ease);
}

.btn:hover::after,
.btn:focus-visible::after { transform: scaleX(1); }

.btn:hover,
.btn:focus-visible {
  box-shadow: 0 0 28px -6px color-mix(in srgb, var(--volt) 85%, transparent);
}

.btn--primary {
  background: var(--volt);
  color: var(--carbon);
  border-color: var(--volt);
  font-weight: 500;
}
/* The primary lights up on hover - it should read as being switched on, not
   dimmed. The deeper amber solved a contrast problem but felt wrong, which it
   was: a button that darkens when you reach for it reads as going off.
   The current stays legible on the bright face because it runs inside a dark
   channel rather than directly on the yellow - see the rail below. */
.btn--primary::after { background: color-mix(in srgb, var(--white) 74%, var(--volt)); }
.btn--primary:hover,
.btn--primary:focus-visible { color: var(--carbon); }

.btn--ghost {
  background: transparent;
  color: var(--white);
  border-color: var(--line-strong);
}

/* The primary reads well because its dark rail sits a pixel inside a yellow
   face - the contrast gives the current a visible track to run on. The ghost
   had a copper rail on a near-black face and the animation simply disappeared
   into it. It gets a light track instead. */
.btn--ghost .btn__edge .rail {
  stroke: color-mix(in srgb, var(--white) 34%, transparent);
  stroke-width: 1.4;
}

/* And it no longer fills yellow on hover. Filling made the face the same colour
   as the current, losing the animation a second time, and it flattened the
   difference between the primary and secondary actions. The ghost stays dark
   and energises instead: border goes live, current reverses, glow comes up. */
.btn--ghost::after { background: color-mix(in srgb, var(--steel) 92%, transparent); }

.btn--ghost:hover,
.btn--ghost:focus-visible {
  color: var(--white);
  border-color: var(--volt);
}

.btn--ghost:hover .btn__edge .rail,
.btn--ghost:focus-visible .btn__edge .rail {
  stroke: color-mix(in srgb, var(--white) 22%, transparent);
}

/* Padding is tuned so the two hero actions sit side by side rather than
   stacking, which pushed the primary CTA below the fold. */
.btn--lg { --btn-pad-y: var(--space-s); --btn-pad-x: var(--space-m); font-size: var(--step-0); }

/* Inline links get the same current, as an underline that fills from the left. */
.link {
  position: relative;
  text-decoration: none;
  background-image: linear-gradient(var(--volt), var(--volt));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size 300ms var(--ease), color 300ms var(--ease);
  padding-bottom: 1px;
  border-bottom: 1px solid var(--line-strong);
}
.link:hover,
.link:focus-visible { background-size: 100% 1px; color: var(--volt); }

/* --------------------------------------------------------------------------
   6. Header
   -------------------------------------------------------------------------- */

.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  height: var(--header-h);
  display: flex;
  align-items: center;
  gap: var(--space-m);
  padding-inline: var(--gutter);
  background: color-mix(in srgb, var(--carbon) 88%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}

.header__logo { display: block; flex-shrink: 0; }
.header__logo img { height: clamp(1.6rem, 1.2rem + 1.4vw, 2.4rem); width: auto; }

/* On the home page the logo lives large in the hero, so the header copy stays
   hidden until the hero is scrolled past. It keeps its space rather than being
   removed, so the nav does not jump when it appears. */
.has-hero-logo .header__logo {
  opacity: 0;
  transform: translateY(-0.4rem);
  pointer-events: none;
  transition: opacity 350ms var(--ease), transform 350ms var(--ease);
}

.has-hero-logo.is-scrolled .header__logo {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.header__nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: clamp(var(--space-s), 2vw, var(--space-l));
}

.header__link {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: color-mix(in srgb, var(--white) 78%, transparent);
  transition: color 200ms var(--ease);
}
.header__link:hover,
.header__link:focus-visible { color: var(--volt); }

/* The phone is always present and never hidden - it is just quieter than the
   quote button. */
.header__phone {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-decoration: none;
  color: var(--white);
  white-space: nowrap;
}
.header__phone:hover { color: var(--volt); }

@media (max-width: 58rem) {
  .header__nav .header__link { display: none; }
}

/* Below this the header carries the logo, the phone and the quote button, and
   they have to share a 390px line without any of them shrinking illegibly. */
@media (max-width: 26rem) {
  .header { gap: var(--space-xs); padding-inline: var(--space-s); }
  .header__nav { gap: var(--space-xs); }
  .header .btn { --btn-pad-x: var(--space-s); font-size: var(--step--2); }
  .header__phone { font-size: var(--step--2); }
}

/* --------------------------------------------------------------------------
   7. Hero
   Asymmetric split. Every photograph in the client's set is portrait, so the
   layout is built around a tall image rather than fighting it with a crop.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  align-items: stretch;
  min-height: max(40rem, 100svh);
  padding-top: var(--header-h);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}

.hero__body {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-l) var(--space-xl) var(--space-l) var(--gutter);
}

/* The logo is the hero's primary mark - large enough to actually register as a
   brand, which is the job the 28px header version could not do.
   Every size below is min()'d against svh as well as vw. Width alone is not
   enough: on a 1920x950 window the width-driven sizes summed to more than the
   viewport height and pushed the buttons off the bottom of the screen. */
.hero__logo {
  display: block;
  margin-bottom: var(--space-m);
}

.hero__logo .cbe-logo {
  height: min(clamp(2.6rem, 6vw, 7.5rem), 12svh);
  width: auto;
}

.cbe-l__w { fill: var(--white); }
.cbe-l__y { fill: var(--volt); }

/* The bolt in the monogram strikes every few seconds. Subtle, and the only place
   the logo itself moves. */
.cbe-l__bolt { animation: boltStrike 4.5s ease-in-out infinite; }

@keyframes boltStrike {
  0%, 86%, 100% { fill: var(--volt); }
  88%           { fill: var(--white); }
  90%           { fill: var(--volt); }
  92%           { fill: var(--white); }
  95%           { fill: var(--volt); }
}

.hero__title {
  font-size: min(var(--step-4), 8.4svh);
  font-variation-settings: 'wdth' 100;
  /* Flush left. `balance` (inherited from the h1 rule) evens the line lengths,
     which reads as a stray indent on the last line of a left-aligned headline. */
  text-wrap: initial;
  margin-top: var(--space-2xs);
}

.hero__claim {
  display: block;
  color: var(--volt);
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: min(var(--step-2), 4.3svh);
  font-variation-settings: 'wdth' 96;
  line-height: 1.02;
  letter-spacing: -0.005em;
  margin-top: var(--space-s);
  max-width: 24ch;
}

.hero__lede {
  max-width: 52ch;
  margin-top: var(--space-m);
  font-size: min(var(--step-0), 2.5svh);
  line-height: 1.55;
  color: color-mix(in srgb, var(--white) 80%, transparent);
}

/* The primary action has to be reachable without scrolling, so the space above
   it is deliberately tighter than the rest of the page's rhythm. */
.hero__actions {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-top: var(--space-l);
}

/* The feed to the CTA - the one load on this circuit that is actually energised.
   Drawn here rather than in the hero SVG because the SVG's coordinates are a
   fixed fraction of the hero height, while the buttons sit wherever the centred
   content stack puts them. On a 2560x1440 screen that drifted the stub about
   120px below the button. Anchored to the element, it is always exactly right. */
.hero__actions::before {
  content: '';
  position: absolute;
  top: 50%;
  right: 100%;
  width: calc(var(--gutter) - var(--rail-x));
  height: 1px;
  background: color-mix(in srgb, var(--copper) 60%, transparent);
}

.hero__actions::after {
  content: '';
  position: absolute;
  top: 50%;
  right: 100%;
  width: 9px;
  height: 9px;
  margin: -4px -4px 0 0;
  background: var(--volt);
  box-shadow: 0 0 10px 1px color-mix(in srgb, var(--volt) 70%, transparent);
  animation: terminalPulse 2.6s ease-in-out infinite;
}

@media (max-width: 62rem) {
  .hero__actions::before,
  .hero__actions::after { display: none; }
}

.hero__media {
  position: relative;
  overflow: hidden;
  background: var(--steel);
  border-left: 1px solid var(--line);
}

/* The photograph is taken out of flow deliberately. `height: 100%` on an image
   whose parent has no resolved height falls back to auto, so this portrait shot
   rendered at its full 3:4 aspect - about 1090px tall in this column - and drove
   the entire hero taller than the viewport, pushing the CTAs off the bottom.
   Absolutely positioned, it contributes no height and simply fills the box. */
.hero__media picture {
  position: absolute;
  inset: 0;
  display: block;
}

.hero__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;
}

/* A scrim so the image never competes with the type at its edge. */
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    var(--carbon) 0%,
    color-mix(in srgb, var(--carbon) 30%, transparent) 26%,
    transparent 60%
  );
}

/* ---- The hero circuit ----
   A schematic feed running through the hero: dim copper traces with bright
   current travelling along them. It spans the whole hero so the run visibly
   continues past the photograph and down into the rail.
   preserveAspectRatio="none" stretches the geometry to the box; the traces are
   orthogonal so they stay square, and non-scaling-stroke keeps them hairlines. */
.hero__circuit {
  position: absolute;
  /* Anchored to the rail rather than the hero edge, so viewBox x=0 IS the fixed
     conductor at every viewport width. Spanning the full hero instead would leave
     the trunk a couple of pixels off the rail and read as a doubled line. */
  left: var(--rail-x);
  top: 0;
  /* Explicit width, not left+right with width:auto. An SVG is a replaced element,
     so `width: auto` takes the viewBox's intrinsic ratio and silently ignores the
     right offset - which sized this box to 1361px instead of 1849px and dragged
     every trace inward across the headline. */
  width: calc(100% - var(--rail-x));
  height: 100%;
  /* Above the photograph, below the type. The riser crossing the top of the image
     is what ties the two halves of the hero together. */
  z-index: 2;
  pointer-events: none;
}

.hero__circuit .trace {
  fill: none;
  stroke: color-mix(in srgb, var(--copper) 55%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Each pulse is a short dash chasing along its own trace. pathLength normalises
   every path to 1000 units so one set of dash values works for all of them. */
.hero__circuit .pulse {
  fill: none;
  stroke: var(--volt);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 26 1000;
  stroke-dashoffset: 1026;
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--volt) 90%, transparent));
  animation: pulseRun 4.2s linear infinite;
}

.hero__circuit .pulse--b { animation-duration: 5.6s; animation-delay: -1.4s; }
.hero__circuit .pulse--c { animation-duration: 3.4s; animation-delay: -2.6s; }
.hero__circuit .pulse--d { animation-duration: 6.4s; animation-delay: -0.7s; }

@keyframes pulseRun { to { stroke-dashoffset: 0; } }

.hero__circuit .node {
  fill: var(--carbon);
  stroke: color-mix(in srgb, var(--copper) 70%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

@keyframes terminalPulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* Short desktop windows - a 1440x700 laptop, or a 4K monitor with a shallow
   browser window. The width-driven sizes are fine here; it is the stack height
   that overflows and pushes the CTAs off the bottom. Only the viewport-height
   caps tighten, so a tall screen keeps the large logo Dave asked for. */
@media (min-width: 62rem) and (max-height: 820px) {
  .hero__logo .cbe-logo { height: min(clamp(2.6rem, 6vw, 7.5rem), 9svh); }
  .hero__title { font-size: min(var(--step-4), 6.4svh); }
  .hero__claim { font-size: min(var(--step-2), 3.5svh); }
  .hero__lede { font-size: min(var(--step-0), 2.1svh); }
  .hero__body { padding-block: var(--space-m); }
  .hero__actions { margin-top: var(--space-m); }
  .hero__logo { margin-bottom: var(--space-s); }
}

@media (max-width: 62rem) {
  .hero {
    grid-template-columns: 1fr;
    min-height: 0;
  }
  .hero__body {
    order: 2;
    padding: var(--space-xl) var(--gutter) var(--space-2xl);
  }
  .hero__media {
    order: 1;
    border-left: 0;
    border-bottom: 1px solid var(--line);
    aspect-ratio: 4 / 3;
  }
  .hero__media::after {
    background: linear-gradient(
      to bottom,
      transparent 40%,
      color-mix(in srgb, var(--carbon) 55%, transparent) 100%
    );
  }
  /* The hero is no longer viewport-height here, so the svh caps would crush the
     type against a tall phone. Width alone governs. */
  .hero__logo .cbe-logo { height: clamp(3rem, 15vw, 5.5rem); }
  .hero__title { font-size: var(--step-4); }
  .hero__claim { font-size: var(--step-2); }
  .hero__lede { font-size: var(--step-0); }
  .hero__circuit { display: none; }
}

/* --------------------------------------------------------------------------
   8. Panel schedule (services)
   Laid out as the directory sticker inside a breaker panel. The left column uses
   real panel-directory vocabulary; it is a label, not a sequence number.
   -------------------------------------------------------------------------- */

.schedule {
  border-top: 1px solid var(--line);
}

.schedule__row {
  display: grid;
  grid-template-columns: minmax(8.5rem, 12vw) minmax(0, 1fr);
  gap: var(--space-m) var(--space-l);
  align-items: start;
  padding-block: var(--space-l);
  border-bottom: 1px solid var(--line);
  max-width: none;
  position: relative;
  transition: background 400ms var(--ease);
}

.schedule__row::before {
  content: '';
  position: absolute;
  left: calc(var(--gutter) * -1);
  right: calc(var(--gutter) * -1);
  top: 0;
  bottom: 0;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--volt) 7%, transparent),
    transparent 45%
  );
  opacity: 0;
  transition: opacity 400ms var(--ease);
  pointer-events: none;
}

.schedule__row:hover::before,
.schedule__row:focus-within::before { opacity: 1; }

.schedule__circuit {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.12em;
  color: var(--volt);
  padding-top: 0.35em;
  white-space: nowrap;
}

.schedule__name {
  font-size: var(--step-2);
  font-variation-settings: 'wdth' 104;
}

.schedule__desc {
  margin-top: var(--space-2xs);
  color: color-mix(in srgb, var(--white) 72%, transparent);
  font-size: var(--step-0);
}

/* Genuine ratings only. These are numbers a homeowner actually searches for;
   rows without a real one simply do not get a badge. */
.schedule__spec {
  display: inline-block;
  margin-top: var(--space-xs);
  padding: 0.15em 0.65em;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.1em;
  color: var(--volt);
  border: 1px solid color-mix(in srgb, var(--volt) 35%, transparent);
}

@media (max-width: 40rem) {
  .schedule__row { grid-template-columns: 1fr; gap: var(--space-2xs); }
  .schedule__circuit { padding-top: 0; }
}

/* --------------------------------------------------------------------------
   9. Inverted trust section
   Breaks the dark rhythm so it reads as an inspection report, not marketing.
   -------------------------------------------------------------------------- */

.invert {
  background: var(--paper);
  color: var(--carbon);
  border-block: 1px solid var(--carbon);
}

.invert .eyebrow { color: color-mix(in srgb, var(--carbon) 62%, transparent); }

.invert__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: var(--space-2xl);
  align-items: center;
}

.invert__media {
  position: relative;
  border: 1px solid color-mix(in srgb, var(--carbon) 25%, transparent);
}

.invert__media img { width: 100%; height: auto; }

.invert__caption {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--carbon) 55%, transparent);
  padding: var(--space-xs) var(--space-s);
  border-top: 1px solid color-mix(in srgb, var(--carbon) 18%, transparent);
  max-width: none;
}

.invert__title { font-size: var(--step-3); }

.invert__body {
  margin-top: var(--space-m);
  font-size: var(--step-1);
  line-height: 1.55;
  color: color-mix(in srgb, var(--carbon) 82%, transparent);
}

/* The licence, set as a specification because that is what it is. */
.license {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  margin-top: var(--space-l);
  padding: var(--space-s) var(--space-m);
  border: 1px solid var(--carbon);
  background: var(--carbon);
  color: var(--volt);
  font-family: var(--font-mono);
  font-size: var(--step-0);
  letter-spacing: 0.08em;
  max-width: none;
}
.license b { font-weight: inherit; white-space: nowrap; }
.license span { color: color-mix(in srgb, var(--white) 65%, transparent); font-size: var(--step--1); }

@media (max-width: 58rem) {
  .invert__grid { grid-template-columns: 1fr; gap: var(--space-xl); }
}

/* --------------------------------------------------------------------------
   10. Work gallery
   Each tile is a panel in a schematic, not a photo dropped on a page: a copper
   frame with corner nodes, a circuit label, and an energised state on hover.
   The whole tile is a link to the full-size image, so it still works with no
   JavaScript - the lightbox is an enhancement on top.
   -------------------------------------------------------------------------- */

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(15rem, 20vw, 22rem), 1fr));
  gap: clamp(var(--space-2xs), 0.9vw, var(--space-s));
  margin-top: var(--space-xl);
}

.gallery__item {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--steel);
  border: 1px solid var(--line);
  max-width: none;
  text-decoration: none;
  cursor: zoom-in;
  transition: border-color 400ms var(--ease), transform 400ms var(--ease);
}

.gallery__item img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  transition: transform 900ms var(--ease), filter 600ms var(--ease);
  filter: saturate(0.85) brightness(0.86);
}

.gallery__item:hover,
.gallery__item:focus-visible {
  border-color: var(--volt);
  transform: translateY(-3px);
}

.gallery__item:hover img,
.gallery__item:focus-visible img {
  transform: scale(1.06);
  filter: saturate(1.06) brightness(1);
}

/* Corner nodes, as on the circuit. Copper at rest, live on hover. */
.gallery__item::before,
.gallery__item::after {
  content: '';
  position: absolute;
  width: 12px;
  height: 12px;
  z-index: 2;
  border-color: color-mix(in srgb, var(--copper) 80%, transparent);
  transition: border-color 400ms var(--ease), box-shadow 400ms var(--ease);
}

.gallery__item::before {
  top: 8px; left: 8px;
  border-top: 1px solid; border-left: 1px solid;
}

.gallery__item::after {
  bottom: 8px; right: 8px;
  border-bottom: 1px solid; border-right: 1px solid;
}

.gallery__item:hover::before,
.gallery__item:hover::after,
.gallery__item:focus-visible::before,
.gallery__item:focus-visible::after {
  border-color: var(--volt);
  box-shadow: 0 0 10px -1px color-mix(in srgb, var(--volt) 70%, transparent);
}

/* The circuit label sits top-left and is always readable, so a tile says
   something even before you touch it. */
.gallery__tag {
  position: absolute;
  top: 0; left: 0;
  z-index: 2;
  padding: var(--space-s) var(--space-s) var(--space-l) calc(var(--space-s) + 14px);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--volt);
  background: linear-gradient(160deg, color-mix(in srgb, var(--carbon) 85%, transparent), transparent 70%);
  max-width: none;
  pointer-events: none;
}

.gallery__label {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  padding: var(--space-2xl) var(--space-s) var(--space-s);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--white);
  background: linear-gradient(to top, color-mix(in srgb, var(--carbon) 94%, transparent) 30%, transparent);
  max-width: none;
  opacity: 0;
  transform: translateY(0.6rem);
  transition: opacity 360ms var(--ease), transform 360ms var(--ease);
}

.gallery__item:hover .gallery__label,
.gallery__item:focus-visible .gallery__label { opacity: 1; transform: none; }

/* The affordance that was missing - these open. */
.gallery__zoom {
  position: absolute;
  right: 8px; top: 8px;
  z-index: 2;
  width: 2.1rem;
  height: 2.1rem;
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--carbon) 78%, transparent);
  border: 1px solid color-mix(in srgb, var(--volt) 45%, transparent);
  color: var(--volt);
  font-family: var(--font-mono);
  font-size: var(--step-0);
  line-height: 1;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 300ms var(--ease), transform 300ms var(--ease);
}

.gallery__item:hover .gallery__zoom,
.gallery__item:focus-visible .gallery__zoom { opacity: 1; transform: none; }

/* Tiles carry the same current as the buttons, but only while hovered. Running
   it on fourteen tiles at rest would be far too much movement on one screen. */
.gallery__item .btn__edge {
  /* Above the photograph, not below it. z-index -1 is right on a button, where
     the only thing above is text - but a tile has an opaque image in normal
     flow, and in-flow content always paints over a negative z-index. The edge
     was running the whole time, completely hidden behind the picture. */
  z-index: 3;
  /* No idle flicker either - the edge is dormant until you point at it. */
  animation: none;
}

.gallery__item .btn__edge .rail,
.gallery__item .btn__edge .charge { display: none; }

.gallery__item .btn__edge .surge {
  stroke: var(--volt);
  filter: drop-shadow(0 0 6px var(--volt));
}

.gallery__item .btn__edge .surge--b { stroke: var(--white); }

.gallery__item:hover .btn__edge .surge,
.gallery__item:focus-visible .btn__edge .surge { opacity: 1; }
.gallery__item:hover .btn__edge .surge--c,
.gallery__item:focus-visible .btn__edge .surge--c { opacity: 0.8; }

/* Touch devices never hover, so the tile has to be legible without it. */
@media (hover: none) {
  .gallery__label { opacity: 1; transform: none; }
  .gallery__zoom { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   10b. Lightbox
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: color-mix(in srgb, var(--carbon) 97%, transparent);
  backdrop-filter: blur(6px);
  opacity: 0;
  visibility: hidden;
  /* Visibility is stepped, not eased. Transitioning it over 260ms delays the
     flip to visible, and a hidden element silently refuses focus - so the close
     button could never be focused on open. Zero-duration with a delay is the
     way round it: instant on the way in, held back on the way out so the fade
     still completes. */
  transition: opacity 260ms var(--ease), visibility 0s 260ms;
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 260ms var(--ease), visibility 0s 0s;
}

.lightbox__bar {
  display: flex;
  align-items: center;
  gap: var(--space-m);
  padding: var(--space-s) var(--gutter);
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--conduit);
}

.lightbox__count { color: var(--volt); }
.lightbox__close { margin-left: auto; }

.lightbox__stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-m) var(--gutter);
  min-height: 0;
  overflow: hidden;
}

.lightbox__stage img {
  max-width: 100%;
  /* Bounded by height too, or a portrait photo runs off the bottom of the
     screen - which is exactly the problem with opening these in a new tab.
     min-height:0 is what makes that bite: a flex item's automatic minimum size
     is its intrinsic height, so a 3200px-tall photo refuses to shrink and
     max-height is quietly ignored. */
  max-height: 100%;
  min-height: 0;
  min-width: 0;
  width: auto;
  height: auto;
  object-fit: contain;
  border: 1px solid var(--line);
  opacity: 0;
  transition: opacity 220ms var(--ease);
}

.lightbox__stage img.is-ready { opacity: 1; }

.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: clamp(2.75rem, 4vw, 3.75rem);
  height: clamp(3.5rem, 6vw, 5rem);
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--steel) 82%, transparent);
  border: 1px solid var(--line-strong);
  color: var(--white);
  font-size: var(--step-1);
  cursor: pointer;
  transition: background 240ms var(--ease), border-color 240ms var(--ease), color 240ms var(--ease);
}

.lightbox__nav:hover,
.lightbox__nav:focus-visible {
  background: var(--volt);
  border-color: var(--volt);
  color: var(--carbon);
}

.lightbox__nav--prev { left: clamp(0.5rem, 1.5vw, 1.5rem); }
.lightbox__nav--next { right: clamp(0.5rem, 1.5vw, 1.5rem); }

.lightbox__caption {
  padding: var(--space-s) var(--gutter) var(--space-l);
  border-top: 1px solid var(--line);
}

.lightbox__caption strong {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--volt);
  margin-bottom: var(--space-2xs);
}

.lightbox__caption p {
  font-size: var(--step-0);
  color: color-mix(in srgb, var(--white) 82%, transparent);
}

@media (max-width: 46rem) {
  .lightbox__nav { top: auto; bottom: var(--space-s); transform: none; }
  .lightbox__caption { padding-bottom: calc(var(--space-l) + 3.5rem); }
}

/* --------------------------------------------------------------------------
   11. Pricing quote - the second honesty pillar
   -------------------------------------------------------------------------- */

.quote { background: var(--steel); border-block: 1px solid var(--line); }

.quote__mark {
  font-family: var(--font-display);
  font-size: var(--step-5);
  font-weight: 900;
  line-height: 0.6;
  color: var(--volt);
  display: block;
  margin-bottom: var(--space-xs);
}

.quote blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-3);
  font-variation-settings: 'wdth' 98;
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.012em;
  text-transform: none;
  max-width: 22ch;
}

.quote__attrib {
  margin-top: var(--space-l);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--conduit);
}

/* --------------------------------------------------------------------------
   12. Service area
   -------------------------------------------------------------------------- */

.area__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-s);
  margin-top: var(--space-l);
  font-family: var(--font-mono);
  font-size: var(--step-0);
  letter-spacing: 0.04em;
}

.area__list li {
  max-width: none;
  padding: var(--space-2xs) var(--space-s);
  border: 1px solid var(--line);
  color: color-mix(in srgb, var(--white) 84%, transparent);
}

/* --------------------------------------------------------------------------
   13. Quote form
   -------------------------------------------------------------------------- */

.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: var(--space-2xl);
  align-items: start;
}

/* Contact facts stay in plain HTML outside any embed. A JavaScript form widget
   is invisible to answer engines, so the phone, email and area must exist here. */
.contact__facts { display: grid; gap: var(--space-m); margin-top: var(--space-l); }

.contact__fact {
  display: grid;
  gap: var(--space-3xs);
  padding-bottom: var(--space-m);
  border-bottom: 1px solid var(--line);
  max-width: none;
}

.contact__fact dt {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--conduit);
}

.contact__fact dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--step-1);
  color: var(--white);
  /* charlie@charliebrown-electric.com is one 33-character token and will not
     wrap on its own. On a 390px screen it has to be allowed to break. */
  overflow-wrap: anywhere;
}
.contact__fact dd a { text-decoration: none; }
.contact__fact dd a:hover { color: var(--volt); }

@media (max-width: 58rem) {
  .contact__grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-xl); }
}

/* --------------------------------------------------------------------------
   14. Footer
   -------------------------------------------------------------------------- */

.footer {
  padding: var(--space-2xl) var(--gutter) var(--space-xl);
  border-top: 1px solid var(--line);
  background: var(--carbon);
}

.footer__grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl);
  justify-content: space-between;
  align-items: start;
}

.footer__logo img { height: clamp(3rem, 2rem + 3vw, 4.5rem); width: auto; }

.footer__meta {
  display: grid;
  gap: var(--space-2xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--conduit);
}
.footer__meta { overflow-wrap: anywhere; }
.footer__meta a { text-decoration: none; color: color-mix(in srgb, var(--white) 78%, transparent); }
.footer__meta a:hover { color: var(--volt); }

.footer__legal {
  margin-top: var(--space-xl);
  padding-top: var(--space-m);
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s) var(--space-l);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.06em;
  color: var(--conduit);
}
.footer__legal a { text-decoration: none; }
.footer__legal a:hover { color: var(--volt); }

/* --------------------------------------------------------------------------
   15. About page
   -------------------------------------------------------------------------- */

.bio__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: var(--space-2xl);
  align-items: start;
  margin-top: var(--space-xl);
}

.bio__portrait {
  position: relative;
  border: 1px solid var(--line);
  background: var(--steel);
}
.bio__portrait img { width: 100%; height: auto; }

.bio__prose { display: grid; gap: var(--space-m); }
.bio__prose p { font-size: var(--step-1); line-height: 1.6; color: color-mix(in srgb, var(--white) 84%, transparent); }

@media (max-width: 58rem) {
  .bio__grid { grid-template-columns: 1fr; gap: var(--space-xl); }
}

/* Simple centred page for privacy / 404. */
.plain { padding-top: calc(var(--header-h) + var(--space-2xl)); }
.plain h2 { font-size: var(--step-2); margin-top: var(--space-xl); }
.plain p { margin-top: var(--space-s); color: color-mix(in srgb, var(--white) 78%, transparent); }
.plain ul { margin-top: var(--space-s); display: grid; gap: var(--space-2xs); }
.plain li { padding-left: var(--space-m); position: relative; color: color-mix(in srgb, var(--white) 78%, transparent); }
.plain li::before {
  content: '';
  position: absolute;
  left: 0; top: 0.7em;
  width: 8px; height: 1px;
  background: var(--volt);
}

/* --------------------------------------------------------------------------
   16. Motion preferences
   The conductor is the whole background motif, so with reduced motion it does
   not disappear - it renders fully energised and static.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  /* The conductor is the whole background motif, so it does not disappear here -
     it renders fully energised and static. */
  .conductor__live { height: 100%; }
  .conductor__live::after { display: none; }
  .conductor__head { display: none; }

  .hero__circuit .pulse { stroke-dashoffset: 0; stroke-dasharray: none; opacity: 0.55; }
  .cbe-l__bolt { fill: var(--volt); }

  .junction { background: var(--volt); }
  .junction::after { background: var(--volt); border-color: var(--volt); }
  .junction::before { display: none; }

  .btn__edge { animation: none !important; }
  .btn__edge .charge,
  .btn__edge .surge { animation: none !important; stroke-dasharray: none; opacity: 0.45; }
  .btn__edge .surge { opacity: 0; }
  .hero__actions::after { animation: none; }
  .gallery__label { opacity: 1; transform: none; }

  /* No entrance movement - things are simply present. */
  .js [data-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; }
  .gallery__item .btn__edge .surge { animation: none !important; }
}
