/* ==========================================================================
   Tech4TIME — layout.css
   Page skeleton: sections, grids, the sticky site header and the site footer.
   Structural rules only; visual component styling lives in components.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Page shell
   -------------------------------------------------------------------------- */

.page {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
}

.page__main {
  flex: 1;
}

/* --------------------------------------------------------------------------
   2. Sections
   -------------------------------------------------------------------------- */

.section {
  padding-block: var(--section-padding);
}

.section--tight {
  padding-block: calc(var(--section-padding) * 0.6);
}

.section--surface {
  background-color: var(--bg-surface);
}

/* Hairline silver rule between bands, echoing the clock hands. */
.section--ruled {
  border-block-start: var(--border-width) solid transparent;
  border-image: var(--silver-gradient-rule) 1;
}

.section__header {
  max-width: 48rem;
  margin-inline: auto;
  margin-block-end: var(--space-xl);
  text-align: center;
}

.section__eyebrow {
  display: block;
  margin-block-end: var(--space-2xs);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--accent-text);
}

.section__title {
  margin-block-end: var(--space-sm);
}

.section__lead {
  font-size: var(--text-md);
  color: var(--text-secondary);
}

/* Banner every inner page opens with: the page name in the silver gradient over
   a tagline. Shared rather than per-page, so all fourteen read as one family. */
.page-hero {
  padding-block: clamp(4rem, 10vw, 7.5rem) clamp(3.5rem, 8vw, 6rem);
  text-align: center;
  background-color: var(--bg-surface);
  border-block-end: var(--border-width) solid var(--border-subtle);
}

.page-hero__inner {
  max-width: 50rem;
  margin-inline: auto;
}

.page-hero__title {
  margin-block-end: var(--space-2xs);
  font-size: var(--text-4xl);
  font-weight: 800;
  background-image: var(--silver-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.page-hero__subtitle {
  font-size: var(--text-lg);
  font-weight: 500;
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   3. Grid helpers
   Auto-fit grids so columns reflow fluidly instead of snapping at breakpoints.
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  gap: var(--space-md);
}

.grid--auto {
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
}

.grid--auto-sm {
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}

.grid--auto-lg {
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}

/* Two-up split that collapses to a single column below the tablet band. */
.grid--split {
  grid-template-columns: 1fr;
  gap: var(--space-xl);
}

@media (min-width: 64rem) {
  .grid--split {
    grid-template-columns: 1fr 1fr;
    align-items: center;
  }
}

/* --------------------------------------------------------------------------
   4. Site header
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  border-block-end: var(--border-width) solid var(--border-subtle);
}

/* The frosted background is on a pseudo-element rather than on .site-header
   itself, and that is not a stylistic choice.

   backdrop-filter makes an element the containing block for any
   position: fixed DESCENDANT. The mobile nav drawer is fixed and lives inside
   the header, so with the blur on .site-header its inset:0 resolved against
   the header's own box instead of the viewport: the drawer opened 120px tall
   instead of full height, and all six links fell outside it and could not be
   hit. Nothing looked broken in the markup, the attribute said data-open=true,
   and the transition ran. Moving the blur one level down leaves the drawer's
   containing block as the viewport, where it belongs.

   Negative z-index keeps it behind the header's own content while staying
   inside the header's stacking context, so it blurs the page scrolling under
   it and nothing else. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: color-mix(in srgb, var(--bg-base) 85%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

/* Browsers without color-mix get the solid base tone. */
@supports not (background-color: color-mix(in srgb, red 50%, transparent)) {
  .site-header::before {
    background-color: var(--bg-base);
  }
}

.site-header--scrolled {
  box-shadow: var(--shadow);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  min-height: var(--header-height);
}

/* The drawer is a positioned sibling with a higher z-index, so it would paint
   over the logo and — critically — over the hamburger, leaving no way to tap
   the menu closed again. Lifting the bar's own controls above it keeps the
   header usable while the drawer is open. */
.site-header__brand,
.site-header__actions {
  position: relative;
  z-index: calc(var(--z-overlay) + 1);
}

.site-header__brand {
  flex-shrink: 0;
  line-height: 0;
}

.site-header__logo {
  width: auto;
  height: clamp(1.75rem, 1.4rem + 1.7vw, 2.5rem);
}

/* Mode-aware artwork. Two variants ship; CSS shows the one that reads against
   the active surface. This mirrors the theme-toggle icon rules, so it follows
   both the OS preference and an explicit data-theme choice — which a
   <picture media="(prefers-color-scheme: …)"> could not do.

   .theme-swap--light / --dark are the general-purpose pair for page content;
   the header and footer keep their own class names. */
.theme-swap--dark,
.site-header__logo-wrap--dark,
.site-footer__logo-wrap--dark {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-swap--light,
  :root:not([data-theme="light"]) .site-header__logo-wrap--light,
  :root:not([data-theme="light"]) .site-footer__logo-wrap--light {
    display: none;
  }
  :root:not([data-theme="light"]) .theme-swap--dark,
  :root:not([data-theme="light"]) .site-header__logo-wrap--dark,
  :root:not([data-theme="light"]) .site-footer__logo-wrap--dark {
    display: block;
  }
}

:root[data-theme="dark"] .theme-swap--light,
:root[data-theme="dark"] .site-header__logo-wrap--light,
:root[data-theme="dark"] .site-footer__logo-wrap--light {
  display: none;
}

:root[data-theme="dark"] .theme-swap--dark,
:root[data-theme="dark"] .site-header__logo-wrap--dark,
:root[data-theme="dark"] .site-footer__logo-wrap--dark {
  display: block;
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

/* Desktop navigation ------------------------------------------------------ */

.site-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(var(--space-xs), 1.6vw, var(--space-md));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Below the laptop band the header nav is not shown at all. Navigation there
   is the dock — a floating bar at the bottom of the viewport with a panel of
   sections above it, styled in section 10 of components.css.

   The header nav used to become a full-screen drawer here instead. Hiding it
   outright is what keeps exactly one navigation on screen at any width: the
   drawer needed a hamburger in the header to open it, and a second control
   that duplicates a nav already visible is how the hamburger ended up sitting
   beside the full desktop nav.

   The links stay in the markup rather than being removed, so the header nav
   is still there for anything reading the document rather than viewing it. */
@media (max-width: 63.999em) {
  .site-nav {
    display: none;
  }
}

/* Keep the last of the footer clear of the floating bar. --dock-height is
   declared on .dock in components.css; the fallback covers the case where
   that rule has not applied. */
@media (max-width: 63.999em) {
  .page {
    padding-block-end: calc(
      var(--dock-height, 4rem) + var(--space-lg) + env(safe-area-inset-bottom, 0px)
    );
  }
}

/* --------------------------------------------------------------------------
   5. Site footer
   -------------------------------------------------------------------------- */

.site-footer {
  background-color: var(--bg-surface);
  border-block-start: var(--border-width) solid var(--border-subtle);
  padding-block: var(--space-2xl) var(--space-lg);
}

.site-footer__main {
  display: grid;
  gap: var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  padding-block-end: var(--space-xl);
}

/* The brand column carries a logo and a paragraph, so it gets more room. */
.site-footer__brand {
  max-width: 24rem;
}

.site-footer__logo {
  width: auto;
  height: 2.25rem;
  margin-block-end: var(--space-sm);
}

.site-footer__tagline {
  margin-block-end: var(--space-2xs);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--accent-text);
}

.site-footer__description {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.site-footer__social {
  display: flex;
  gap: var(--space-2xs);
  margin: var(--space-md) 0 0;
  padding: 0;
  list-style: none;
}

.site-footer__section {
  min-width: 0;
}

.site-footer__heading {
  margin-block-end: var(--space-sm);
  font-size: var(--text-base);
  font-weight: 600;
}

.site-footer__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-footer__link {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-decoration: none;
}

.site-footer__link:hover {
  color: var(--accent-text);
  text-decoration: underline;
}

/* Contact column ---------------------------------------------------------- */

.site-footer__contact {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
  /* <address> is italic by default; the semantics are wanted, the slant is not. */
  font-style: normal;
}

.contact-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.contact-item__icon {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  margin-block-start: 0.3em;
  color: var(--accent-text);
}

.contact-item__label {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-primary);
}

/* A second label inside the same item starts a new group (BD then Malaysia),
   so it needs breathing room above it. */
.contact-item__label ~ .contact-item__label,
.contact-item__note + .contact-item__label {
  margin-block-start: var(--space-2xs);
}

.contact-item__note {
  display: block;
  font-size: var(--text-xs);
  font-style: italic;
  color: var(--text-muted);
}

.contact-item a {
  color: inherit;
  text-decoration: none;
}

.contact-item a:hover {
  color: var(--accent-text);
  text-decoration: underline;
}

/* Footer bottom bar ------------------------------------------------------- */

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block-start: var(--space-lg);
  border-block-start: var(--border-width) solid var(--border-subtle);
}

.site-footer__copyright {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-xs);
}

@media (max-width: 47.999em) {
  .site-footer__bottom {
    flex-direction: column;
    text-align: center;
  }
}

/* --------------------------------------------------------------------------
   The circuit around the page title

   Six layers: a chevron band across the top and the bottom, and a cluster
   emerging from each of the four corners. The drawing is the company's own,
   taken from its printed material.

   One set of geometry, declared once in the first layer and mirrored here —
   the top-right corner is the top-left one flipped, the bottom band is the top
   band turned over. What keeps that from reading as a mirror is the timing:
   every corner charge runs on its own number of seconds, so no two layers ever
   line up.

   Motion here is suppressed by the prefers-reduced-motion block in base.css,
   which leaves the traces as a still drawing. That works only because every
   animation below rests on its declared value: the charges end at
   stroke-dashoffset 0, which is also their base, and the nodes revert to the
   opacity declared on the rule. An animation that ended anywhere else would
   collapse to something nobody designed.
   -------------------------------------------------------------------------- */

.page-hero {
  position: relative;
  /* The layers are inset to the edges of this box; without this they would
     resolve against the page. */
  isolation: isolate;
  overflow: hidden;
}

.hero-circuit {
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Read by circuit.js, which paints the charges. Declared here so the colour
     lives with the rest of the drawing's and follows the theme by itself. */
  --charge-ink: var(--accent-text);
  /* Inherited, so it covers every shape inside. Without it the artwork sits in
     front of the title for hit-testing and check_focus.py reports the focus
     ring as covered. */
  pointer-events: none;
}

.page-hero__inner {
  position: relative;
}

.hero-circuit__layer {
  position: absolute;
  display: block;
}

/* THE CIRCUITS BRANCH OUT FROM THEIR OWN EDGE, ONCE
   Every trace in a corner starts on one of the two edges that meet there, and
   every trace in a band starts at the outer edge. So a clip expanding from that
   same origin uncovers each trace from its root outward, which is the growth
   the artwork is meant to have — and it costs six animated elements rather than
   the two hundred it would take to draw every trace on individually.

   The resting value is the fully revealed one, declared on the rule. That is
   load-bearing: base.css sets no animation-fill-mode, so under reduced motion
   the collapsed animation reverts to whatever is declared here. Had the rule
   said "hidden" and the keyframes said "revealed", asking for less motion would
   have emptied the band. */
.hero-circuit__layer--band-top,
.hero-circuit__layer--band-bottom {
  clip-path: inset(0 0 0 0);
  animation: hero-emerge-band 1.5s cubic-bezier(0.22, 0.7, 0.3, 1) both;
}

.hero-circuit__layer--corner-tl,
.hero-circuit__layer--corner-tr,
.hero-circuit__layer--corner-bl,
.hero-circuit__layer--corner-br {
  clip-path: circle(160% at 0 0);
  /* No stagger between the four: they are asked to emerge together. */
  animation: hero-emerge-corner 1.5s cubic-bezier(0.22, 0.7, 0.3, 1) both;
}

/* Each layer is clipped in its own coordinate space, which the mirrors carry
   round with them — so "from the top edge" and "from this corner" stay true for
   all six without a rule each. */
@keyframes hero-emerge-band {
  from {
    clip-path: inset(0 0 100% 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

@keyframes hero-emerge-corner {
  from {
    clip-path: circle(0% at 0 0);
  }
  to {
    clip-path: circle(160% at 0 0);
  }
}

/* --- the chevron bands, top and bottom ----------------------------------- */

.hero-circuit__layer--band-top,
.hero-circuit__layer--band-bottom {
  inset-inline: 0;
  width: 100%;
  height: clamp(2.75rem, 8vw, 6rem);
  /* The fade begins late. It is there to keep the title clear of the drawing,
     not to hide the drawing: the field of diagonals is the half of this a
     visitor recognises from the printed sheet. */
  -webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
}

.hero-circuit__layer--band-top {
  top: 0;
}

/* The same drawing turned over, so the traces run out of the bottom edge
   rather than into it. The mask turns over with it. */
.hero-circuit__layer--band-bottom {
  bottom: 0;
  transform: scaleY(-1);
}

/* --- the four corner clusters -------------------------------------------- */

.hero-circuit__layer--corner-tl,
.hero-circuit__layer--corner-tr,
.hero-circuit__layer--corner-bl,
.hero-circuit__layer--corner-br {
  width: clamp(6.5rem, 20vw, 15rem);
  /* The box keeps the viewBox's own ratio, so "meet" never leaves a margin and
     the scale is width / 260 at every size — which is what the pen weights
     further down are calculated against. */
  aspect-ratio: 13 / 10;
  /* Fades on a diagonal away from its own corner. The gradient is anchored at
     0% 0%, which each mirror carries round to the corner it belongs to. */
  -webkit-mask-image: radial-gradient(130% 130% at 0% 0%, #000 40%, transparent 96%);
  mask-image: radial-gradient(130% 130% at 0% 0%, #000 40%, transparent 96%);
}

.hero-circuit__layer--corner-tl { top: 0; left: 0; }
.hero-circuit__layer--corner-tr { top: 0; right: 0; transform: scaleX(-1); }
.hero-circuit__layer--corner-bl { bottom: 0; left: 0; transform: scaleY(-1); }
.hero-circuit__layer--corner-br { bottom: 0; right: 0; transform: scale(-1, -1); }

/* --- the three static layers --------------------------------------------- */

.hero-circuit__wires {
  fill: none;
  stroke: var(--silver-accent-mid);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.3;
}

/* Pads and the small rectangles a board carries where a component lands. */
.hero-circuit__pads {
  fill: var(--silver-accent-mid);
  opacity: 0.34;
}

/* Vias, and the short parallel runs of a bus leaving a connector. */
.hero-circuit__rings {
  fill: none;
  stroke: var(--silver-accent-mid);
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0.3;
}

/* A corner is drawn at about 0.92 of its own units and holds thirty traces on
   an eighteen-unit pitch, so at the shared weight it renders under a pixel and
   turns to haze. It gets its own, heavier, or the cluster stops being legible
   as a circuit. */
.hero-circuit__layer--corner-tl .hero-circuit__wires,
.hero-circuit__layer--corner-tr .hero-circuit__wires,
.hero-circuit__layer--corner-bl .hero-circuit__wires,
.hero-circuit__layer--corner-br .hero-circuit__wires {
  stroke-width: 1.9;
  opacity: 0.38;
}

.hero-circuit__layer--corner-tl .hero-circuit__pads,
.hero-circuit__layer--corner-tr .hero-circuit__pads,
.hero-circuit__layer--corner-bl .hero-circuit__pads,
.hero-circuit__layer--corner-br .hero-circuit__pads {
  opacity: 0.44;
}

.hero-circuit__layer--corner-tl .hero-circuit__rings,
.hero-circuit__layer--corner-tr .hero-circuit__rings,
.hero-circuit__layer--corner-bl .hero-circuit__rings,
.hero-circuit__layer--corner-br .hero-circuit__rings {
  stroke-width: 1.7;
  opacity: 0.36;
}

/* --- the charge ---------------------------------------------------------- */

/* Every trace carries pathLength="100", so one dash length works for a 150-unit
   corner elbow and a 380-unit band bus alike: 22 units lit against 78 dark is
   one charge crossing, then a rest before the next. 100 is exactly one period,
   so the loop closes on itself with no visible jump.

   The class is on a <g> holding a <use> of a group of traces, not on one trace.
   stroke, stroke-width, stroke-dasharray and stroke-dashoffset all inherit, so
   the animated offset reaches every path inside — which is how all 216 traces
   in the drawing carry a charge while only forty elements are animated. */
.hero-circuit__charge {
  fill: none;
  stroke: var(--accent-text);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 22 78;
  animation-name: hero-charge;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

@keyframes hero-charge {
  from {
    stroke-dashoffset: 100;
  }
  to {
    stroke-dashoffset: 0;
  }
}

/* preserveAspectRatio="none" squashes the band's 120-unit box to about 76
   pixels, and a stroke is squashed with it: a horizontal run comes out at
   roughly six tenths of its stated width. The bands ask for more so that both
   halves of the drawing carry the same weight on screen. */
.hero-circuit__layer--band-top .hero-circuit__charge,
.hero-circuit__layer--band-bottom .hero-circuit__charge {
  stroke-width: 3.6;
}

/* Three durations, shared by all four clusters — and that sharing is measured,
   not lazy. Giving the twelve corner charges twelve distinct durations gives
   them twelve distinct computed styles, which Chrome can then share between no
   two elements: 55ms of style recalculation per second against 35ms for the
   same twenty-four charges on three. The clusters are mirror images anyway, so
   a shared phase reads as the board lighting symmetrically.

   They are the slow half of the drawing: the bands run their whole circuit in
   4s, three to five times faster, which is the difference in pace the artwork
   is meant to have — the bands are the current, the corners are the board it
   runs on.

   Adding a charge costs about 1.1ms of style recalculation per second, every
   second the page is open. Run tools/check_style_budget.py, and read the table
   in docs/10-development/frontend/motion.md, before raising the count. */
.hero-circuit__charge--c1 { animation-duration: 12s; }
.hero-circuit__charge--c2 { animation-duration: 13s; }
.hero-circuit__charge--c3 { animation-duration: 14s; }

/* Consecutive traces are dealt round-robin into the six groups, so no two
   neighbours share one. Reversing the odd groups is therefore what makes
   adjacent lines in a cluster run against each other. */
.hero-circuit__charge--back {
  animation-direction: reverse;
}

/* THE TWO BANDS ARE A MATCHED PAIR, AND THAT IS WHY THEY SHARE A DURATION
   Everywhere else here a shared duration is the fault being avoided. Along the
   top and bottom edges it is the requirement: the flow runs left to right
   across the top and right to left across the bottom, at one speed, so the two
   read as a single current going round the band rather than as two unrelated
   animations that happen to be nearby. One duration is what makes the speeds
   equal; the delays only stagger where each charge sits on its circuit. */
.hero-circuit__charge--band { animation-duration: 4s; }

.hero-circuit__charge--p1 { animation-delay: 0s; }
.hero-circuit__charge--p2 { animation-delay: -1s; }
.hero-circuit__charge--p3 { animation-delay: -2s; }
.hero-circuit__charge--p4 { animation-delay: -3s; }

/* The right half of each band is the left half mirrored, so a charge running
   forward along its path travels right to left there. Reversing it puts both
   halves back in step with each other — and because the delays are shared, the
   two halves reach the centre line together and close the chevron. */
.hero-circuit__charge--mirrored {
  animation-direction: reverse;
}

/* And the bottom band is the top one turned over, so the whole flow inverts:
   what ran left to right along the top runs right to left along the bottom. */
.hero-circuit__layer--band-bottom .hero-circuit__charge {
  animation-direction: reverse;
}

.hero-circuit__layer--band-bottom .hero-circuit__charge--mirrored {
  animation-direction: normal;
}

/* --- the charges, once a canvas has taken them over ----------------------- */

/* THE SVG CHARGES ARE THE FALLBACK, AND THEY ARE SWITCHED OFF LAST
   circuit.js adds this class only after its canvas is measured and drawing.
   Until then — and forever, without JavaScript — the CSS charges below run,
   so the band is never left with no charge at all. Once the canvas is up they
   are display:none, which cancels their animations outright: that is the
   point, because a charge on every one of 216 traces is affordable on a
   canvas and was not affordable as 216 style recalculations a frame.
   docs/10-development/frontend/motion.md */
.hero-circuit--canvas .hero-circuit__charges,
.hero-circuit--canvas .hero-circuit__nodes {
  display: none;
}

.hero-circuit__charge-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Measured NEUTRAL, and kept anyway: 37ms of style recalculation per second
     against 40ms without, which is inside the noise. It is here because
     telling the browser that a leaf which repaints thirty times a second
     cannot affect anything outside itself is true and costs nothing, not
     because it was shown to buy anything.

     An earlier note here claimed it halved the cost. That was a measurement
     run without warm-up, where the uncontained case went first and paid for a
     cold cache. It did not survive being measured properly. */
  contain: layout paint style;
}

/* --- the junctions ------------------------------------------------------- */

/* opacity is declared here, not only in the keyframes: under reduced motion the
   animation is collapsed and the element reverts to this value, so this is the
   still drawing's brightness. */
.hero-circuit__node {
  fill: var(--silver-accent-mid);
  opacity: 0.3;
  transform-box: fill-box;
  transform-origin: center;
  animation-name: hero-pulse;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

@keyframes hero-pulse {
  from {
    opacity: 0.16;
    scale: 0.85;
  }
  to {
    opacity: 0.5;
    scale: 1.15;
  }
}

.hero-circuit__node--a { animation-duration: 7s; }
.hero-circuit__node--b { animation-duration: 11s; }
.hero-circuit__node--c { animation-duration: 13s; }
.hero-circuit__node--d { animation-duration: 17s; }
.hero-circuit__node--e { animation-duration: 19s; }
.hero-circuit__node--f { animation-duration: 23s; }
.hero-circuit__node--g { animation-duration: 29s; }
.hero-circuit__node--h { animation-duration: 31s; }
.hero-circuit__node--i { animation-duration: 37s; }
.hero-circuit__node--j { animation-duration: 41s; }
.hero-circuit__node--k { animation-duration: 43s; }
.hero-circuit__node--l { animation-duration: 47s; }
.hero-circuit__node--m { animation-duration: 53s; }
.hero-circuit__node--n { animation-duration: 59s; }
.hero-circuit__node--o { animation-duration: 61s; }
.hero-circuit__node--p { animation-duration: 67s; }
.hero-circuit__node--q { animation-duration: 71s; }
.hero-circuit__node--r { animation-duration: 73s; }
.hero-circuit__node--s { animation-duration: 79s; }
.hero-circuit__node--t { animation-duration: 83s; }
.hero-circuit__node--u { animation-duration: 89s; }
.hero-circuit__node--v { animation-duration: 97s; }
.hero-circuit__node--w { animation-duration: 101s; }
.hero-circuit__node--x { animation-duration: 103s; }

/* --- the pen, against the scale it is drawn at --------------------------- */

/* An SVG stroke is scaled with its drawing. The corner renders at width / 260,
   which is 0.92 on a desktop and 0.40 on a phone; the band is worse, because
   preserveAspectRatio="none" squashes it unevenly and a diagonal comes out at
   about the geometric mean of the two — 0.89 wide, 0.29 narrow.
   Left alone, every line here falls under a pixel below about 800px and the
   whole drawing turns to haze. That is the state these tiers exist to prevent:
   the artwork is asked to be legible on a phone, not merely present, so as the
   drawing shrinks the pen is widened to hold the rendered weight between
   roughly one and two pixels throughout. The numbers are that arithmetic, not
   taste. */

@media (max-width: 63.999em) {
  .hero-circuit__layer--band-top .hero-circuit__wires,
  .hero-circuit__layer--band-bottom .hero-circuit__wires {
    stroke-width: 2.2;
  }

  .hero-circuit__layer--band-top .hero-circuit__rings,
  .hero-circuit__layer--band-bottom .hero-circuit__rings {
    stroke-width: 1.9;
  }

  .hero-circuit__layer--band-top .hero-circuit__charge,
  .hero-circuit__layer--band-bottom .hero-circuit__charge {
    stroke-width: 5;
  }

  .hero-circuit__layer--corner-tl .hero-circuit__wires,
  .hero-circuit__layer--corner-tr .hero-circuit__wires,
  .hero-circuit__layer--corner-bl .hero-circuit__wires,
  .hero-circuit__layer--corner-br .hero-circuit__wires {
    stroke-width: 2.4;
  }

  .hero-circuit__layer--corner-tl .hero-circuit__rings,
  .hero-circuit__layer--corner-tr .hero-circuit__rings,
  .hero-circuit__layer--corner-bl .hero-circuit__rings,
  .hero-circuit__layer--corner-br .hero-circuit__rings {
    stroke-width: 2.1;
  }

  .hero-circuit__layer--corner-tl .hero-circuit__charge,
  .hero-circuit__layer--corner-tr .hero-circuit__charge,
  .hero-circuit__layer--corner-bl .hero-circuit__charge,
  .hero-circuit__layer--corner-br .hero-circuit__charge {
    stroke-width: 4;
  }
}

/* Below about 520px the corner stops shrinking — its clamp has bottomed out at
   6.5rem — so one tier covers everything from here down for the clusters. */
@media (max-width: 47.999em) {
  .hero-circuit__layer--band-top .hero-circuit__wires,
  .hero-circuit__layer--band-bottom .hero-circuit__wires {
    stroke-width: 2.9;
  }

  .hero-circuit__layer--band-top .hero-circuit__rings,
  .hero-circuit__layer--band-bottom .hero-circuit__rings {
    stroke-width: 2.5;
  }

  .hero-circuit__layer--band-top .hero-circuit__charge,
  .hero-circuit__layer--band-bottom .hero-circuit__charge {
    stroke-width: 6.6;
  }

  .hero-circuit__layer--corner-tl .hero-circuit__wires,
  .hero-circuit__layer--corner-tr .hero-circuit__wires,
  .hero-circuit__layer--corner-bl .hero-circuit__wires,
  .hero-circuit__layer--corner-br .hero-circuit__wires {
    stroke-width: 3.4;
  }

  .hero-circuit__layer--corner-tl .hero-circuit__rings,
  .hero-circuit__layer--corner-tr .hero-circuit__rings,
  .hero-circuit__layer--corner-bl .hero-circuit__rings,
  .hero-circuit__layer--corner-br .hero-circuit__rings {
    stroke-width: 3;
  }

  .hero-circuit__layer--corner-tl .hero-circuit__charge,
  .hero-circuit__layer--corner-tr .hero-circuit__charge,
  .hero-circuit__layer--corner-bl .hero-circuit__charge,
  .hero-circuit__layer--corner-br .hero-circuit__charge {
    stroke-width: 5.4;
  }
}

/* The band alone goes on shrinking: it is the viewport wide however narrow that
   is, so at 320px it is drawing 1440 units into 320 pixels. */
@media (max-width: 29.999em) {
  .hero-circuit__layer--band-top .hero-circuit__wires,
  .hero-circuit__layer--band-bottom .hero-circuit__wires {
    stroke-width: 3.6;
  }

  .hero-circuit__layer--band-top .hero-circuit__rings,
  .hero-circuit__layer--band-bottom .hero-circuit__rings {
    stroke-width: 3.2;
  }

  .hero-circuit__layer--band-top .hero-circuit__charge,
  .hero-circuit__layer--band-bottom .hero-circuit__charge {
    stroke-width: 8;
  }
}
