/* ==========================================================================
   JANARIX — VISUAL LAYER
   Loaded after janarix.css. Nothing here redefines a brand token; it adds
   depth, imagery and motion on top of the system that is already there.

   Why a second file: janarix.css is the design system, and it is heavily
   commented for whoever maintains it. Keeping the elevation work separate
   means you can read what changed, and delete this one line from a page if
   you ever want the plainer version back.

     A  Atmosphere — light, grain, gradient fields
     B  Hero personalities — one per page, so no two openings repeat
     C  Media components — plates, bands, mosaics, captions
     D  Custom SVG marks and icon frames
     E  Cards and surfaces, lifted
     F  Statistics and proof
     G  Process timeline with photography
     H  Motion — reveal variants, parallax, sweep, glow
     I  Responsive
     J  Reduced motion
   ========================================================================== */

/* =============================== A. ATMOSPHERE =========================== */

/* A very fine noise field. It sits under content on large flat surfaces and
   stops big colour areas from banding on cheap screens. Encoded inline so it
   costs no request. */
.section.has-grain::before {
  content: '';
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  opacity: .5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.32'/%3E%3C/svg%3E");
}
.section.has-grain > * { position: relative; z-index: 1; }

/* Two soft light fields used behind dark sections. Gold is still not
   decoration here — it marks the part of the section where power is the
   subject, and it never appears on a surface that carries no energy claim. */
.section.has-glow { overflow: hidden; }
.section.has-glow::after {
  content: '';
  position: absolute; z-index: 0; pointer-events: none;
  width: min(920px, 90vw); aspect-ratio: 1;
  right: -14%; top: -34%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%,
    rgba(255,184,28,.16) 0%, rgba(255,184,28,.07) 34%, transparent 66%);
}
.section.has-glow.glow-left::after { right: auto; left: -18%; }
.section.has-glow.glow-low::after  { top: auto; bottom: -40%; }
.section.has-glow > * { position: relative; z-index: 1; }

/* A hairline that fades out at both ends. Used to separate bands without
   drawing a full-width rule across the page. */
.rule-fade {
  height: 1px; border: 0;
  background: linear-gradient(90deg, transparent, var(--rule-day-2) 18%, var(--rule-day-2) 82%, transparent);
}

/* =========================== B. HERO PERSONALITIES ======================= */
/* The brief: every page should say what it is within a second, and no two
   openings should feel the same. Each block below is one page's opening. */

/* --- Shared: film behind a hero ------------------------------------------
   A hero can now carry a video. The files are palindromes — they play
   forward then backward — so the loop point never jumps. */
.hero-film {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  will-change: transform;
}
/* The poster frame is painted on the container, not left to the <video>
   element. That matters: the film is held back until it is worth the bytes,
   and if it never arrives — autoplay refused, data saver on, the file 404s —
   what remains is a proper photograph rather than a black rectangle. */
.hero-media.is-film {
  background-color: var(--night-900);
  background-position: center 50%;
  background-size: cover;
  background-repeat: no-repeat;
}
.hero-film[data-lazy-video]:not(.is-ready) { opacity: 0; }
.hero-film { transition: opacity 700ms var(--ease-out); }

/* --- Shared: the vignette that keeps words readable over any picture ----- */
.hero-scrim.is-work {
  background:
    linear-gradient(100deg,
      rgba(4,12,24,.94) 0%, rgba(5,14,30,.86) 26%, rgba(6,18,38,.56) 48%,
      rgba(6,18,38,.24) 68%, rgba(6,18,38,.06) 100%),
    linear-gradient(180deg, rgba(4,12,24,.62) 0%, transparent 34%, rgba(4,12,24,.34) 100%);
}
.hero-scrim.is-daylight {
  background:
    linear-gradient(100deg,
      rgba(6,16,32,.92) 0%, rgba(8,20,40,.80) 28%, rgba(10,24,48,.44) 52%,
      rgba(10,24,48,.14) 74%, transparent 100%),
    linear-gradient(180deg, rgba(5,14,28,.58) 0%, transparent 30%, rgba(5,14,28,.42) 100%);
}
.hero-scrim.is-deep {
  background:
    linear-gradient(100deg,
      rgba(3,9,20,.96) 0%, rgba(4,12,26,.90) 30%, rgba(5,15,34,.70) 54%,
      rgba(5,15,34,.42) 76%, rgba(5,15,34,.24) 100%),
    linear-gradient(180deg, rgba(3,9,20,.70) 0%, transparent 32%, rgba(3,9,20,.52) 100%);
}

/* --- A slow drift on hero photography, so a still frame is not static ---- */
.hero-media.is-drift img { animation: heroPan 38s var(--ease) infinite alternate; }
@keyframes heroPan {
  from { transform: scale(1.06) translate3d(-1.1%, 0.6%, 0); }
  to   { transform: scale(1.12) translate3d(1.1%, -0.6%, 0); }
}

/* --- Light sweep. A single pass of low-angle light across the opening,
       the way sun crosses a roof. Slow enough that you notice it only if
       you are looking. --------------------------------------------------- */
.hero-sweep {
  position: absolute; inset: 0; pointer-events: none; overflow: hidden;
}
.hero-sweep::before {
  content: '';
  position: absolute; top: -60%; bottom: -60%; width: 46%;
  left: -60%;
  background: linear-gradient(100deg, transparent, rgba(255,224,160,.13) 42%, rgba(255,236,200,.20) 52%, rgba(255,224,160,.10) 62%, transparent);
  transform: skewX(-14deg);
  animation: sweep 11s var(--ease) infinite;
}
@keyframes sweep {
  0%, 62%  { left: -60%; opacity: 0; }
  68%      { opacity: 1; }
  100%     { left: 130%; opacity: 0; }
}

/* --- Floating particles: dust in a sunbeam, not snow. Six nodes, three
       sizes, all transform-only. ---------------------------------------- */
.hero-motes { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.hero-motes i {
  position: absolute; display: block;
  width: 4px; height: 4px; border-radius: 50%;
  background: rgba(255,209,102,.55);
  box-shadow: 0 0 12px rgba(255,184,28,.5);
  animation: mote 18s linear infinite;
}
.hero-motes i:nth-child(1) { left: 14%; top: 78%; animation-duration: 21s; }
.hero-motes i:nth-child(2) { left: 32%; top: 88%; width: 3px; height: 3px; animation-duration: 26s; animation-delay: -6s; }
.hero-motes i:nth-child(3) { left: 51%; top: 72%; width: 5px; height: 5px; animation-duration: 19s; animation-delay: -11s; }
.hero-motes i:nth-child(4) { left: 68%; top: 92%; animation-duration: 24s; animation-delay: -3s; }
.hero-motes i:nth-child(5) { left: 79%; top: 66%; width: 3px; height: 3px; animation-duration: 29s; animation-delay: -15s; }
.hero-motes i:nth-child(6) { left: 90%; top: 84%; width: 4px; height: 4px; animation-duration: 22s; animation-delay: -8s; }
@keyframes mote {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  12%  { opacity: .85; }
  80%  { opacity: .5; }
  100% { transform: translate3d(38px, -300px, 0); opacity: 0; }
}

/* --- The gold hairline that closes every hero, so the opening ends on a
       deliberate edge rather than a colour change. ---------------------- */
.hero::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,184,28,.42) 22%, rgba(255,184,28,.62) 50%, rgba(255,184,28,.42) 78%, transparent);
  z-index: 3;
}

/* --- Hero side plate. Used where a page benefits from a second image
       beside the words rather than only behind them. ------------------- */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.06fr) minmax(0, .72fr);
  gap: clamp(28px, 4.4vw, 68px);
  align-items: center;
}
/* A headline set for the full width of the page becomes a five-line block
   once a plate takes a third of it. These openings step down one size and
   read as a headline again rather than a wall. */
.hero-grid .hero-copy { max-width: none; }
.hero-grid h1 { font-size: clamp(2.375rem, 4.4vw, 3.75rem); }
.hero-grid .hero-lead { max-width: 40ch; }
/* --- Hero side plate.

   The first version of this looked pasted on, and measuring it said why:
   the plate was 54 to 110 levels brighter than the hero right beside it, and
   about half as saturated. A bright flat rectangle on a dark graded scene
   reads as a sticker no matter how good the photograph is.

   So it is graded into the shot rather than laid on top of it:

     · exposure pulled down and the colour pushed toward the hero's blue,
       so both pictures look lit by the same sky,
     · a real elevation shadow in three layers — contact, mid, ambient —
       instead of one soft blur,
     · a rim that is bright at the top left, where the light in these heroes
       comes from, and gone by the bottom right,
     · a soft spill of light behind it, so the background knows it is there,
     · the bottom edge sinking into the scrim rather than stopping dead.
   ------------------------------------------------------------------- */
.hero-plate {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  isolation: isolate;
  box-shadow:
    0 1px 2px rgba(0,0,0,.55),
    0 14px 34px -14px rgba(0,0,0,.72),
    0 54px 110px -48px rgba(0,0,0,.9);
}
/* the spill: the scene lights up a little where the plate sits */
.hero-plate::before {
  content: '';
  position: absolute; inset: -14%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(120,160,220,.16), transparent 72%);
  pointer-events: none;
}
.hero-plate img {
  width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4/5;
  filter: brightness(.68) saturate(.86) contrast(1.06);
}
/* the grade, the light at the top where the caption sits, and the sink into
   the scrim at the bottom */
.hero-plate::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, rgba(4,12,26,.78) 0%, rgba(4,12,26,.34) 22%, transparent 40%),
    linear-gradient(158deg, rgba(28,58,102,.34) 0%, rgba(10,26,52,.30) 46%, rgba(5,14,30,.46) 100%),
    linear-gradient(180deg, transparent 62%, rgba(4,12,26,.62) 92%, rgba(4,12,26,.82) 100%);
}
/* the rim: lit top-left, gone bottom-right */
.hero-plate .rim {
  position: absolute; inset: 0; z-index: 2; border-radius: inherit;
  pointer-events: none;
  padding: 1px;
  background: linear-gradient(150deg, rgba(255,255,255,.30), rgba(255,255,255,.07) 38%, transparent 68%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
}
/* The caption sits at the top of the plate, not the bottom. The floating
   "Get a quote" button and the announcement panel both live in the bottom
   right of the window, and on a first visit they landed straight on top of
   it. A caption nobody can read is not a caption. */
.hero-plate .cap {
  position: absolute; left: 20px; right: 20px; top: 18px; z-index: 3;
  font-size: var(--fs-xs); color: var(--on-night-2); line-height: 1.45;
}
.hero-plate .cap b { color: var(--on-night); font-weight: 600; display: block; margin-bottom: 3px; }

/* --- The charging card on the battery hero. The pack fills cell by cell;
       the figure beside it is the real pack size, not a decoration. ----- */
.hero-cellcard { padding: clamp(20px, 2.2vw, 28px); }
.hero-cellcard .hcc-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; margin-bottom: 16px;
}
.hero-cellcard .hcc-head .k {
  font-size: var(--fs-label); font-weight: 600; letter-spacing: .15em;
  text-transform: uppercase; color: var(--volt);
}
.hero-cellcard .hcc-head .v { font-size: var(--fs-sm); color: var(--on-night-2); font-variant-numeric: tabular-nums; }
.hero-cellcard .cellbar { width: 100%; height: auto; }
.hero-cellcard .hcc-foot { margin-top: 16px; font-size: var(--fs-xs); color: var(--on-night-2); line-height: 1.55; }

/* --- Per-page tuning. Each opening carries a different crop and a
       different weight of dark, so no two of them read the same. ------- */
.hero-about   .hero-media img,  .hero-about   .hero-film { object-position: center 58%; }
.hero-systems .hero-film { object-position: center 44%; }
/* The battery hero is the plug-in unit keeping a kitchen running through an
   outage: fridge open, laptop on, lamp lit. It is already night-graded, so it
   only needs anchoring on the unit and a little lift in the shadows. The crop
   keeps the unit clear of the charging card that sits on the right. */
.hero-battery .hero-media img { object-position: 38% 60%; filter: saturate(1.02) contrast(1.02) brightness(1.08); }
.hero-business .hero-media img { object-position: center 54%; filter: saturate(.92) contrast(1.03) brightness(1.02) hue-rotate(-6deg); }
.hero-engineered .hero-media img { object-position: 58% 52%; }
/* The assessment hero is two people reading a panel across a table, with an
   out-of-focus hard hat anchoring the left. That blurred foreground is where
   the headline sits, which is lucky: nothing in focus is lost to the type.
   The picture is bright and cool, so it is pulled down and warmed slightly to
   sit with the rest of the site. */
.hero-consult .hero-media img { object-position: 58% 46%; filter: saturate(.94) contrast(1.04) brightness(.86); }
.hero-projects .hero-media img { object-position: center 62%; }
.hero-products .hero-media img { object-position: center 50%; filter: saturate(.96) brightness(.9); }

/* Openings that carry no photograph sit shorter — there is nothing to look
   at, so holding the reader at the top would be a waste of their scroll. */
.hero-calc .hero-inner, .hero-journal .hero-inner { min-height: min(58vh, 560px); }

/* --- Calculator and form pages get a drawn opening rather than a photo.
       A tool page fronted by stock photography is the oldest tell there is;
       a diagram of the thing the tool does is honest and it is ours. ---- */
.hero-draw {
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 90% at 78% 12%, rgba(255,184,28,.13) 0%, transparent 58%),
    radial-gradient(100% 80% at 10% 100%, rgba(44,80,137,.42) 0%, transparent 62%),
    var(--night-900);
  overflow: hidden;
}
.hero-draw svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* The drawing is the background, so it gets the same treatment a photograph
   would: weight on the side the words are on, clear on the other. Without
   this the gold trace runs straight through the headline. */
.hero-draw::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(97deg,
      rgba(5,15,29,.95) 0%, rgba(5,15,29,.90) 24%, rgba(5,15,29,.62) 42%,
      rgba(5,15,29,.22) 60%, rgba(5,15,29,.02) 82%, transparent 100%),
    linear-gradient(180deg, rgba(5,15,29,.55) 0%, transparent 30%, rgba(5,15,29,.35) 100%);
}
.hero-draw .grid-line { stroke: rgba(157,176,206,.13); stroke-width: 1; }
.hero-draw .trace {
  fill: none; stroke: var(--volt); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1400; stroke-dashoffset: 1400;
  animation: draw 3.4s var(--ease-out) .35s forwards;
  filter: drop-shadow(0 0 12px rgba(255,184,28,.55));
}
.hero-draw .trace-2 {
  fill: none; stroke: rgba(157,176,206,.5); stroke-width: 1.5;
  stroke-dasharray: 1400; stroke-dashoffset: 1400;
  animation: draw 3.4s var(--ease-out) .7s forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }

.hero-draw .pulse { animation: pulseNode 3.2s var(--ease) infinite; transform-origin: center; }
@keyframes pulseNode {
  0%, 100% { opacity: .35; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.35); }
}

/* ========================== C. MEDIA COMPONENTS ========================== */

/* --- Plate: a photograph presented as an object, with a caption that says
       what you are looking at. The site's existing .split-media is a bare
       crop; this is the version that carries information. --------------- */
.plate {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--night-800);
  box-shadow: var(--lift-2);
}
.night .plate, .night-deep .plate { box-shadow: var(--lift-night); }
.plate > img,
.plate > picture > img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 900ms var(--ease-out);
}
.plate-4x3 > img, .plate-4x3 > picture > img { aspect-ratio: 4/3; }
.plate-3x2 > img, .plate-3x2 > picture > img { aspect-ratio: 3/2; }
.plate-16x9 > img, .plate-16x9 > picture > img { aspect-ratio: 16/9; }
.plate-1x1 > img, .plate-1x1 > picture > img { aspect-ratio: 1; }
.plate-4x5 > img, .plate-4x5 > picture > img { aspect-ratio: 4/5; }
.plate-21x9 > img, .plate-21x9 > picture > img { aspect-ratio: 21/9; }
.plate-fill { height: 100%; }
.plate-fill > img, .plate-fill > picture > img { height: 100%; aspect-ratio: auto; }

.plate:hover > img,
.plate:hover > picture > img { transform: scale(1.035); }

/* The caption. Sits on the picture, on a gradient rather than a solid bar,
   so it never looks like a stock-photo credit strip. */
.plate-cap {
  position: absolute; inset: auto 0 0 0; z-index: 2;
  padding: clamp(16px, 2vw, 26px);
  background: linear-gradient(180deg, transparent, rgba(3,10,22,.52) 34%, rgba(3,10,22,.88) 100%);
  color: var(--on-night);
}
.plate-cap b { display: block; font-family: var(--display); font-weight: 600; font-size: 1.0625rem; letter-spacing: -.01em; }
.plate-cap span { display: block; margin-top: 4px; font-size: var(--fs-xs); color: var(--on-night-2); line-height: 1.5; }

/* A gold corner tick — the mark used to say "this is a real Janarix job"
   rather than a picture chosen to fill a hole.

   It sits on its own dark pill rather than straight on the photograph. The
   first version relied on a text shadow, which looks fine but does not count:
   measured against the bright sky on the Care photograph the label came out
   at 3.08, under the 4.5 a label this size needs. A pill fixes it on every
   picture rather than on the one that happened to catch it. */
.plate-mark {
  position: absolute; top: 14px; left: 14px; z-index: 2;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px 6px 10px;
  border-radius: var(--r-pill);
  background: rgba(6,14,28,.72);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.14);
}
.plate-mark::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--volt); box-shadow: 0 0 0 3px rgba(255,184,28,.24);
}
.plate-mark span {
  font-size: 10.5px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase;
  color: #fff; line-height: 1;
}

/* --- Band: a full-bleed photograph between two sections. It carries the
       page's rhythm — the eye gets a rest and the story gets a beat. --- */
.band {
  position: relative;
  min-height: clamp(300px, 42vw, 520px);
  display: grid; align-items: end;
  overflow: hidden;
  background: var(--night-900);
}
.band > picture, .band > img, .band > video {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
}
.band > picture img { width: 100%; height: 100%; object-fit: cover; }
/* The same idea as the heroes: weight on the side the words are on, and
   the photograph left alone on the other. A flat wash over the whole frame
   would dull the picture and still not carry the type. */
.band::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(96deg,
      rgba(4,11,24,.90) 0%, rgba(4,12,26,.78) 26%, rgba(5,14,30,.46) 46%,
      rgba(5,14,30,.18) 66%, rgba(5,14,30,.04) 100%),
    linear-gradient(180deg, rgba(4,11,24,.46) 0%, rgba(4,11,24,.06) 34%, rgba(4,11,24,.72) 100%);
}
/* Flipped: photograph on the left, words on the right. */
.band.is-flipped::after {
  background:
    linear-gradient(264deg,
      rgba(4,11,24,.90) 0%, rgba(4,12,26,.78) 26%, rgba(5,14,30,.46) 46%,
      rgba(5,14,30,.18) 66%, rgba(5,14,30,.04) 100%),
    linear-gradient(180deg, rgba(4,11,24,.46) 0%, rgba(4,11,24,.06) 34%, rgba(4,11,24,.72) 100%);
}
.band.is-flipped .band-inner { text-align: right; }
.band.is-flipped .band-quote { margin-left: auto; }
.band-inner { position: relative; z-index: 2; padding-block: clamp(34px, 4.6vw, 66px); color: var(--on-night); }
.band-inner h2, .band-inner h3 { color: var(--on-night); }
.band-quote {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.5rem, 2.7vw, 2.375rem); line-height: 1.14; letter-spacing: -.022em;
  max-width: 20ch; text-wrap: balance;
}
.band-quote .lit { color: var(--volt); }
.band-by {
  margin-top: 14px; font-size: var(--fs-sm); max-width: 44ch;
  color: var(--on-night); opacity: .84;
  text-shadow: 0 1px 14px rgba(3,10,22,.7);
}
.band-inner .eyebrow { color: var(--on-night); opacity: .9; text-shadow: 0 1px 12px rgba(3,10,22,.8); }
.band-quote { text-shadow: 0 2px 22px rgba(3,10,22,.5); }

/* --- Mosaic: three or four photographs at different weights. Used where a
       page needs to show range without turning into a gallery. -------- */
.mosaic {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: clamp(112px, 12vw, 168px);
  gap: clamp(10px, 1.2vw, 16px);
}
.mosaic > * { border-radius: var(--r); overflow: hidden; position: relative; }
.mosaic .m-a { grid-column: span 2; grid-row: span 2; }
.mosaic .m-b { grid-column: span 2; grid-row: span 1; }
.mosaic .m-c { grid-column: span 1; grid-row: span 1; }
.mosaic img { width: 100%; height: 100%; object-fit: cover; transition: transform 800ms var(--ease-out); }
.mosaic > *:hover img { transform: scale(1.05); }

/* --- Figure with a note beside it, for pages that explain equipment --- */
.fig-note {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 48px); align-items: center;
}
.fig-note.is-flipped .plate { order: 2; }

/* --- Inline media strip used inside a text column ------------------- */
.strip { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: clamp(10px, 1.2vw, 16px); }
.strip .plate > img, .strip .plate > picture > img { aspect-ratio: 1; }

/* ====================== D. CUSTOM SVG MARKS AND ICONS ==================== */
/* One drawing language across the whole site: 1.6px strokes, round caps,
   no fills except where gold marks a live part. Drawn for Janarix rather
   than pulled from an icon set, so the roof icon is a Philippine hip roof
   and the battery is the proportion of the unit we actually install. */

.ico {
  width: 26px; height: 26px; flex: none;
  stroke: currentColor; fill: none;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.ico-lg { width: 34px; height: 34px; }
.ico-sm { width: 18px; height: 18px; }
.ico .live { stroke: var(--volt); }
.ico .fill-live { fill: var(--volt); stroke: none; }

/* The frame an icon sits in. A soft square with a hairline, not a coloured
   circle — coloured circles are what made the old icons feel generic. */
.ico-frame {
  width: clamp(46px, 4.4vw, 58px); aspect-ratio: 1;
  display: grid; place-items: center;
  border-radius: 15px;
  background: linear-gradient(155deg, rgba(255,184,28,.13), rgba(255,184,28,.03));
  box-shadow: inset 0 0 0 1px var(--rule-day-2);
  color: var(--volt-deep);
  position: relative;
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.night .ico-frame, .night-deep .ico-frame, .card-dark .ico-frame {
  background: linear-gradient(155deg, rgba(255,184,28,.16), rgba(255,184,28,.04));
  color: var(--volt);
}
.ico-frame::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(70% 70% at 50% 0%, rgba(255,184,28,.30), transparent 70%);
  opacity: 0; transition: opacity var(--t) var(--ease);
}
.card:hover .ico-frame, .door:hover .ico-frame, .lift:hover .ico-frame { transform: translateY(-2px); }
.card:hover .ico-frame::after, .door:hover .ico-frame::after, .lift:hover .ico-frame::after { opacity: 1; }

/* A drawn diagram block — energy flow, wiring, roof section. Sits on a
   surface, sized like a photograph so it holds its place in the rhythm. */
.diagram {
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 100% at 50% -10%, rgba(255,184,28,.10), transparent 60%),
    var(--night-800);
  padding: clamp(22px, 3vw, 40px);
  position: relative; overflow: hidden;
}
.diagram svg { width: 100%; height: auto; display: block; }
.diagram .wire { stroke: rgba(157,176,206,.38); stroke-width: 1.5; fill: none; }
.diagram .wire-live {
  stroke: var(--volt); stroke-width: 2; fill: none;
  stroke-linecap: round;
  filter: drop-shadow(0 0 8px rgba(255,184,28,.55));
}
.diagram .box { stroke: rgba(157,176,206,.42); stroke-width: 1.4; fill: rgba(255,255,255,.03); }
.diagram .lbl { fill: var(--on-night-2); font-family: var(--body); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }
.diagram .val { fill: var(--on-night); font-family: var(--display); font-size: 17px; font-weight: 600; }

/* Current running along a wire. The dash travels; the wire does not move. */
.diagram .flow {
  stroke: var(--volt); stroke-width: 2.4; fill: none; stroke-linecap: round;
  stroke-dasharray: 5 22;
  animation: flow 2.4s linear infinite;
  filter: drop-shadow(0 0 7px rgba(255,184,28,.75));
}
@keyframes flow { to { stroke-dashoffset: -54; } }

/* Battery charging: cells fill in sequence. Used on the battery page. */
.cellbar rect.cell { fill: rgba(255,255,255,.07); }
.cellbar rect.cell.on { animation: cellOn 4.2s var(--ease) infinite; }
.cellbar rect.cell.on:nth-of-type(1) { animation-delay: 0s; }
.cellbar rect.cell.on:nth-of-type(2) { animation-delay: .28s; }
.cellbar rect.cell.on:nth-of-type(3) { animation-delay: .56s; }
.cellbar rect.cell.on:nth-of-type(4) { animation-delay: .84s; }
.cellbar rect.cell.on:nth-of-type(5) { animation-delay: 1.12s; }
.cellbar rect.cell.on:nth-of-type(6) { animation-delay: 1.4s; }
@keyframes cellOn {
  0%, 8%    { fill: rgba(255,255,255,.07); }
  22%, 78%  { fill: var(--volt); }
  94%, 100% { fill: rgba(255,255,255,.07); }
}

/* ======================= E. CARDS AND SURFACES, LIFTED =================== */

/* Any element can take .lift to gain the hover behaviour without inheriting
   card padding. */
.lift { transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease); }
.lift:hover { transform: translateY(-4px); box-shadow: var(--lift-2); }
.night .lift:hover, .night-deep .lift:hover { box-shadow: var(--lift-night); }

/* A card with a photograph at the top. The image is part of the card, not a
   thumbnail dropped into it — no inner padding, no rounded-inside-rounded. */
.card-media { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.card-media > .cm-img { position: relative; overflow: hidden; }
.card-media > .cm-img img { width: 100%; aspect-ratio: 16/10; object-fit: cover; transition: transform 800ms var(--ease-out); }
.card-media:hover > .cm-img img { transform: scale(1.045); }
.card-media > .cm-img::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 55%, rgba(4,12,26,.42));
}
.card-media .cm-body { padding: clamp(20px, 2.2vw, 28px); flex: 1; display: flex; flex-direction: column; }
.card-media .cm-body .ico-frame { margin-bottom: 14px; }

/* A card that carries a number as its subject. */
.card-figure { position: relative; overflow: hidden; }
.card-figure .fig-n {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(2.4rem, 4.2vw, 3.5rem); line-height: .88;
  letter-spacing: -.045em; font-variant-numeric: tabular-nums;
  color: var(--volt-deep);
}
.night .card-figure .fig-n, .night-deep .card-figure .fig-n, .card-dark .fig-n { color: var(--volt); }
.card-figure .fig-k { margin-top: 10px; font-size: var(--fs-sm); color: var(--ink-2); }
.card-figure::after {
  content: ''; position: absolute; right: -30px; top: -30px;
  width: 130px; height: 130px; border-radius: 50%;
  background: radial-gradient(circle, rgba(255,184,28,.14), transparent 68%);
  pointer-events: none;
}

/* Glass. Used only over photography, never on flat stock — a frosted panel
   on a plain background is just a grey box. */
.glass {
  background: rgba(10, 22, 42, .52);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  border-radius: var(--r);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.11);
  color: var(--on-night);
}

/* ========================= F. STATISTICS AND PROOF ======================= */

.figures {
  display: grid; grid-template-columns: repeat(4, minmax(0,1fr));
  gap: clamp(14px, 1.6vw, 22px);
}
.figure-item {
  position: relative;
  padding: clamp(20px, 2.2vw, 30px) clamp(16px, 1.8vw, 24px);
  border-radius: var(--r);
  background: rgba(255,255,255,.04);
  box-shadow: inset 0 0 0 1px var(--rule-night);
  overflow: hidden;
}
.day .figure-item, .day-white .figure-item, .day-inset .figure-item {
  background: var(--day-000);
  box-shadow: inset 0 0 0 1px var(--rule-day);
}
.figure-item .v {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.9rem, 3.2vw, 2.75rem); line-height: .9;
  letter-spacing: -.042em; font-variant-numeric: tabular-nums;
  color: var(--volt);
}
.day .figure-item .v, .day-white .figure-item .v, .day-inset .figure-item .v { color: var(--volt-deep); }
.figure-item .k { margin-top: 11px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.45; }
/* A gold hairline that draws itself in when the figure counts up. */
.figure-item::after {
  content: ''; position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
  background: linear-gradient(90deg, var(--volt), rgba(255,184,28,0));
  transform: scaleX(0); transform-origin: left;
  transition: transform 1100ms var(--ease-out);
}
.figure-item.is-in::after { transform: scaleX(1); }

/* ==================== G. PROCESS TIMELINE WITH PHOTOGRAPHY =============== */
/* The five steps already exist as an ordered list. This is the version that
   carries a photograph per step, for the page where the process IS the
   story. The numbers stay in the markup, so it degrades to a plain list. */

.track { position: relative; display: grid; gap: clamp(20px, 2.4vw, 34px); list-style: none; }
.track-step { list-style: none; }
.track::before {
  content: ''; position: absolute; left: 27px; top: 18px; bottom: 18px; width: 2px;
  background: linear-gradient(180deg, var(--rule-day-2), var(--rule-day-2));
}
.track-fill {
  position: absolute; left: 27px; top: 18px; width: 2px;
  background: linear-gradient(180deg, var(--volt), rgba(255,184,28,.25));
  height: var(--fill, 0%);
  transition: height 900ms var(--ease-out);
  box-shadow: 0 0 12px rgba(255,184,28,.5);
}
.track-step {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) minmax(0, .66fr);
  gap: clamp(16px, 2.4vw, 34px);
  align-items: center;
  position: relative;
}
/* A step with no photograph does not reserve an empty slot for one. Its text
   runs the full width and the drawn mark leads the heading instead, which
   also gives the column a rhythm rather than five identical rows. */
.track-step.is-plain { grid-template-columns: 56px minmax(0, 1fr); }
.track-step.is-plain .track-body { max-width: 62ch; }
.track-lead {
  display: inline-grid; place-items: center;
  width: 30px; height: 30px; border-radius: 9px; margin-right: 11px;
  vertical-align: -7px;
  background: rgba(255,184,28,.14);
  box-shadow: inset 0 0 0 1px var(--rule-day-2);
  color: var(--volt-deep);
}
.night .track-lead, .night-deep .track-lead { color: var(--volt); }
.track-dot {
  width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--day-050);
  box-shadow: inset 0 0 0 2px var(--rule-day-2);
  font-family: var(--display); font-weight: 600; font-size: 1rem;
  color: var(--ink-3);
  position: relative; z-index: 1;
  transition: box-shadow var(--t) var(--ease), color var(--t) var(--ease), background var(--t) var(--ease);
}
.day-white .track-dot { background: var(--day-000); }
.day-inset .track-dot { background: var(--day-100); }
.night .track-dot, .night-deep .track-dot { background: var(--night-800); }
.track-step.is-in .track-dot {
  color: var(--night-900); background: var(--volt);
  box-shadow: 0 0 0 6px rgba(255,184,28,.16);
}
.track-body h3 { margin-bottom: 7px; }
.track-body p { color: var(--ink-2); font-size: var(--fs-sm); }
.track-media { border-radius: var(--r); overflow: hidden; }
.track-media img { width: 100%; aspect-ratio: 3/2; object-fit: cover; }

/* Not every step has a photograph, and inventing one would be worse than
   leaving the slot empty. Those steps get their drawn mark on a tinted
   plate instead, so the column still has a rhythm. */
.track-mark {
  display: grid; place-items: center;
  aspect-ratio: 3/2;
  background:
    radial-gradient(120% 110% at 50% 0%, rgba(255,184,28,.13), transparent 66%),
    var(--day-000);
  box-shadow: inset 0 0 0 1px var(--rule-day);
  color: var(--volt-deep);
}
.day-inset .track-mark { background: radial-gradient(120% 110% at 50% 0%, rgba(255,184,28,.14), transparent 66%), var(--day-050); }
.night .track-mark, .night-deep .track-mark {
  background: radial-gradient(120% 110% at 50% 0%, rgba(255,184,28,.16), transparent 66%), var(--night-700);
  color: var(--volt);
}
.track-mark .ico { width: 40px; height: 40px; }
.track-step.is-in .track-mark { box-shadow: inset 0 0 0 1px rgba(255,184,28,.34); }

/* The three-things cards now open with a drawn mark. */
.step .ico-frame { margin-bottom: 18px; }
.step .ico-frame + .k { display: block; }

/* The buyer's-guide list keeps its numerals; this only tightens it now that
   it sits next to a marked list rather than alone. */
.seq-marked li h3 { letter-spacing: -.012em; }

/* ================================ H. MOTION ============================== */

/* Reveal variants. The base [data-reveal] in janarix.css is a 14px rise;
   these are the other four the pages now use. All transform and opacity. */
[data-reveal="up"]    { transform: translateY(26px); }
[data-reveal="left"]  { transform: translateX(-30px); }
[data-reveal="right"] { transform: translateX(30px); }
[data-reveal="scale"] { transform: scale(.95); }
[data-reveal="blur"]  { filter: blur(9px); transform: translateY(16px); }
/* A curtain, opening downward.

   It stops at 94% rather than 100% on purpose. An element clipped to zero
   height paints nothing, and an element that paints nothing never reports
   as intersecting — so the observer that is supposed to open the curtain
   never fires and the picture stays hidden for good. Leaving a sliver keeps
   the element visible to the observer, and reads as a deliberate reveal
   rather than a pop. */
[data-reveal="mask"]  { clip-path: inset(0 0 94% 0); transform: none; opacity: 1; }
/* The clip-path reset belongs to the curtain and nothing else.

   Applied to every [data-reveal].is-in — which is what this rule used to do —
   `clip-path: inset(0)` silently clips anything that hangs outside an
   element's own box. That cut the top off the "Most homes choose this" badge,
   which is positioned to sit above its card on purpose, and would do the same
   to any tooltip, glow or focus ring on a revealed element. */
[data-reveal].is-in       { transform: none; filter: none; }
[data-reveal="mask"].is-in { clip-path: inset(0 0 0 0); }
[data-reveal="mask"] { transition: clip-path 1000ms var(--ease-out), opacity 1ms; }
[data-reveal="blur"] { transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out), filter var(--t-slow) var(--ease-out); }

/* Parallax. JS writes --par as a pixel offset; the element only translates,
   so no layout is touched at any point in the scroll. */
[data-parallax] { will-change: transform; }
[data-parallax] > img,
[data-parallax] > picture > img,
img[data-parallax] { transform: translate3d(0, var(--par, 0px), 0); }
.band > picture img, .band > img { transform: translate3d(0, var(--par, 0px), 0) scale(1.09); }

/* A warmer halo on the one action that starts the relationship. The design
   system already puts a gold ring on :hover; this deepens it.

   It is a box-shadow rather than a pseudo-element on purpose. The first
   version used ::before with a negative inset and z-index -1, which the
   sweep's own `overflow: hidden` below then clipped to the button's edge,
   so it drew nothing at all. A shadow cannot be clipped by its own element. */
.btn-primary:hover {
  box-shadow: 0 0 0 4px rgba(255,184,28,.26),
              0 12px 34px -12px rgba(255,184,28,.75);
}

/* Light sweep across a button on hover.

   `position: relative` is not decoration here, it is what keeps the sweep
   inside the button. Without it the pseudo-element's containing block is
   whatever positioned ancestor happens to be above it — on the home page
   that was `.wrap.hero-inner`, 1280px wide — and `left: 130%` threw a
   500px streak of light across the hero. `overflow: hidden` on the button
   cannot clip a child whose containing block sits outside the button. */
.btn-lg { overflow: hidden; position: relative; }
.btn-lg::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: 42%; left: 0;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.30), transparent);
  /* Swept with transform rather than by animating `left`. Identical on
     screen, but `left` makes the browser redo layout on every frame of the
     sweep, and transform is handled by the compositor. It is the widest
     element that moves on the site, so it is the one worth getting right. */
  transform: translateX(-160%) skewX(-18deg);
  transition: transform 620ms var(--ease-out);
  will-change: transform;
  pointer-events: none;
}
.btn-lg:hover::after { transform: translateX(310%) skewX(-18deg); }

/* A decorative sweep is exactly the sort of thing somebody asking for less
   motion does not want. */
@media (prefers-reduced-motion: reduce) {
  .btn-lg::after { display: none; }
}

/* A slow gradient drift for dark call-to-action surfaces. */
.drift-bg {
  background-size: 220% 220%;
  animation: driftBg 26s var(--ease) infinite alternate;
}
@keyframes driftBg {
  from { background-position: 12% 20%; }
  to   { background-position: 88% 80%; }
}

/* Pointer-reactive tilt. JS writes --mx/--my in the -1..1 range. Capped at
   a few degrees: any more and a card reads as a toy. */
.tilt { transform: perspective(900px) rotateX(calc(var(--my, 0) * -3.2deg)) rotateY(calc(var(--mx, 0) * 3.2deg)); transition: transform 220ms var(--ease-out); }
.tilt-glare { position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background: radial-gradient(38% 38% at calc(50% + var(--mx, 0) * 42%) calc(50% + var(--my, 0) * 42%), rgba(255,255,255,.16), transparent 68%);
  opacity: 0; transition: opacity 260ms var(--ease); }
.tilt:hover .tilt-glare { opacity: 1; }

/* ============================== I. RESPONSIVE ============================ */

@media (max-width: 1080px) {
  .figures { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .hero-grid { grid-template-columns: minmax(0, 1fr); }
  .hero-plate { display: none; }   /* the film or photo behind is enough */
}

@media (max-width: 960px) {
  .fig-note { grid-template-columns: minmax(0, 1fr); }
  .fig-note.is-flipped .plate { order: 0; }
  .track-step, .track-step.is-plain { grid-template-columns: 46px minmax(0, 1fr); }
  .track-media { grid-column: 2; margin-top: 4px; }
  .track-dot { width: 46px; height: 46px; }
  .track::before, .track-fill { left: 22px; }
}

@media (max-width: 780px) {
  .mosaic { grid-template-columns: repeat(2, minmax(0,1fr)); grid-auto-rows: 42vw; }
  .mosaic .m-a { grid-column: span 2; grid-row: span 1; }
  .mosaic .m-b { grid-column: span 2; }
  .mosaic .m-c { grid-column: span 1; }
  .strip { grid-template-columns: repeat(3, minmax(0,1fr)); gap: 8px; }
  .band { min-height: 340px; }
  .band-quote { max-width: 100%; }

  /* Horizontal reveals go vertical on a phone.

     A block that is already the full width of the screen cannot be nudged
     sideways without sticking out of it, and a page you can accidentally
     drag left is a worse fault than a less interesting entrance. Measured
     at 390px: Why Janarix was 403px wide until this. */
  [data-reveal="left"], [data-reveal="right"] { transform: translateY(22px); }
  /* A moving background behind body text is a phone battery for no reward. */
  .hero-motes, .hero-sweep { display: none; }

  /* The same correction janarix.css makes for its own two scrims. On a phone
     the picture is cropped hard and the words sit over the middle of it, so
     the gradient turns vertical and holds an even weight top to bottom.
     Without this the lead paragraph lands on bright sky. */
  .hero-scrim.is-work {
    background: linear-gradient(180deg,
      rgba(4,11,24,.92) 0%, rgba(4,12,26,.84) 28%,
      rgba(5,14,30,.82) 56%, rgba(4,12,26,.90) 100%);
  }
  .hero-scrim.is-daylight {
    background: linear-gradient(180deg,
      rgba(6,16,32,.90) 0%, rgba(7,18,36,.82) 28%,
      rgba(8,20,40,.80) 56%, rgba(6,16,32,.90) 100%);
  }
  .hero-scrim.is-deep {
    background: linear-gradient(180deg,
      rgba(3,9,20,.94) 0%, rgba(4,12,26,.88) 28%,
      rgba(5,15,34,.86) 56%, rgba(3,9,20,.93) 100%);
  }
  /* The drawn openings go vertical like the photographic ones — but a scrim
     heavy enough for a photograph erases a line drawing. Measured on a
     390px screen, the first version left the calculator hero looking like a
     plain dark rectangle. So the wash is lighter, the strokes are thicker,
     and the crop is anchored where the interesting part of the curve is. */
  .hero-draw::after {
    background: linear-gradient(180deg,
      rgba(5,15,29,.86) 0%, rgba(5,15,29,.60) 34%,
      rgba(5,15,29,.52) 62%, rgba(5,15,29,.88) 100%);
  }
  .hero-draw .trace { stroke-width: 3.4; }
  .hero-draw .trace-2 { stroke-width: 2.4; }
  .hero-draw .grid-line { stroke-width: 1.4; stroke: rgba(157,176,206,.18); }
  .hero-draw .pulse { r: 9; }
}

@media (max-width: 560px) {
  .figures { grid-template-columns: minmax(0,1fr); }
  .plate-cap { padding: 14px 16px; }
}

/* ============================ J. REDUCED MOTION ========================== */
/* Everything decorative stops. Anything carrying information keeps its
   final state, because a figure that never arrives is a broken page, not a
   calm one. */

@media (prefers-reduced-motion: reduce) {
  .hero-media.is-drift img,
  .hero-sweep::before,
  .hero-motes i,
  .drift-bg,
  .diagram .flow,
  .cellbar rect.cell.on,
  .hero-draw .pulse { animation: none !important; }

  .hero-motes, .hero-sweep { display: none; }
  .hero-draw .trace, .hero-draw .trace-2 { stroke-dashoffset: 0; animation: none; }
  .diagram .flow { stroke-dasharray: none; opacity: .9; }
  .cellbar rect.cell.on { fill: var(--volt); }

  [data-parallax] > img,
  [data-parallax] > picture > img,
  img[data-parallax],
  .band > picture img, .band > img { transform: none; }

  [data-reveal] { opacity: 1 !important; transform: none !important; filter: none !important; clip-path: none !important; }
  .figure-item::after { transform: scaleX(1); }
  .track-fill { height: 100%; transition: none; }
  .tilt { transform: none; }
  .plate:hover > img, .plate:hover > picture > img,
  .card-media:hover > .cm-img img, .mosaic > *:hover img { transform: none; }
}

/* ==========================================================================
   K. THE FLOW DIAGRAM

   Three moments — day, night, and the minute the power cuts — on one drawing,
   because the argument the site makes is about all three and a still frame
   can only make it about one.

   Everything that changes between states changes by opacity or by which paths
   are allowed to animate. No geometry moves, so switching state cannot cause
   a re-layout however fast somebody clicks the tabs.
   ========================================================================== */

.flowd {
  margin: 0;
  border-radius: var(--r-lg);
  background:
    radial-gradient(130% 100% at 50% -20%, rgba(255,184,28,.10), transparent 62%),
    linear-gradient(180deg, var(--night-800), var(--night-900));
  box-shadow: inset 0 0 0 1px var(--rule-night);
  overflow: hidden;
}

/* --- the tabs ---------------------------------------------------------- */
.fd-tabs {
  display: flex; flex-wrap: wrap; gap: 8px;
  padding: clamp(16px, 1.8vw, 22px) clamp(18px, 2.2vw, 30px) 0;
}
.fd-tab {
  appearance: none; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  padding: 9px 17px; border-radius: var(--r-pill); border: 0;
  background: rgba(255,255,255,.05);
  box-shadow: inset 0 0 0 1px var(--rule-night);
  color: var(--on-night-2);
  transition: background var(--t) var(--ease), color var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.fd-tab:hover { color: var(--on-night); background: rgba(255,255,255,.09); }
.fd-tab.is-on {
  background: var(--volt); color: var(--night-900);
  box-shadow: 0 0 0 1px rgba(255,184,28,.5), 0 6px 20px -8px rgba(255,184,28,.7);
}
.fd-tab:focus-visible { outline: 2px solid var(--volt); outline-offset: 3px; }

.fd-stage { padding: clamp(6px, 1vw, 14px) clamp(10px, 1.6vw, 24px) 0; }
.flowd svg { width: 100%; height: auto; display: block; overflow: visible; }

/* --- the drawing ------------------------------------------------------- */
.fd-box  { fill: rgba(255,255,255,.028); stroke: rgba(157,176,206,.44); stroke-width: 1.5; }
.fd-line { fill: none; stroke: rgba(157,176,206,.38); stroke-width: 1.4; stroke-linecap: round; }
.fd-win  { fill: rgba(255,184,28,.16); stroke: rgba(157,176,206,.38); stroke-width: 1.2; }
.fd-wire { fill: none; stroke: rgba(157,176,206,.30); stroke-width: 1.5; }
.fd-lbl  {
  fill: var(--on-night-2); font-family: var(--body); font-size: 13px;
  letter-spacing: .12em; text-transform: uppercase;
}
.fd-inner-lbl { fill: var(--on-night-2); font-family: var(--body); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; }
.fd-note { fill: var(--volt); font-family: var(--body); font-size: 13px; letter-spacing: .06em; }

/* The house keeps its light on in every state. That is the whole point. */
.fd-bolt { fill: var(--volt); opacity: .95; }

/* --- what "off" looks like --------------------------------------------- */
/* Anything tagged with the states it belongs to fades out of the others.
   It fades rather than disappears, so the shape of the system stays legible
   and you can see what is idle as opposed to what is missing. */
.flowd [data-on] { transition: opacity 520ms var(--ease-out); }
.flowd[data-state="daylight"] [data-on]:not([data-on~="daylight"]),
.flowd[data-state="night"]    [data-on]:not([data-on~="night"]),
.flowd[data-state="brownout"] [data-on]:not([data-on~="brownout"]),
.flowd[data-state="mains"]    [data-on]:not([data-on~="mains"]),
.flowd[data-state="restored"] [data-on]:not([data-on~="restored"]) { opacity: 0; }

/* --- current on the wire ----------------------------------------------- */
.fd-flow {
  fill: none; stroke: var(--volt); stroke-width: 2.6; stroke-linecap: round;
  stroke-dasharray: 6 20; stroke-dashoffset: 0;
  filter: drop-shadow(0 0 7px rgba(255,184,28,.7));
  animation: fdFlow 2.1s linear infinite;
}
/* Export runs the other way down the same cable, so the dash travels the
   other way. This is the one place the diagram earns its keep. */
.fd-flow[data-reverse] { animation-name: fdFlowRev; }
@keyframes fdFlow    { to { stroke-dashoffset: -52; } }
@keyframes fdFlowRev { to { stroke-dashoffset:  52; } }

/* --- sunlight ---------------------------------------------------------- */
.fd-beam {
  fill: none; stroke: #FFB81C; stroke-width: 1.6; stroke-linecap: round;
  stroke-dasharray: 3 10; opacity: .6;
  animation: fdBeam 1.9s linear infinite;
}
@keyframes fdBeam { to { stroke-dashoffset: -52; } }

.fd-pulse { animation: fdPulse 4.2s var(--ease) infinite; transform-origin: center; }
@keyframes fdPulse {
  0%   { r: 19; opacity: .5; }
  100% { r: 36; opacity: 0; }
}

/* --- the pack, filling ------------------------------------------------- */
.fd-cell { fill: rgba(255,255,255,.07); transition: fill 400ms var(--ease); }
/* Charging: the cells fill left to right. */
.flowd[data-state="daylight"] .fd-cell,
.flowd[data-state="mains"] .fd-cell,
.flowd[data-state="restored"] .fd-cell { animation: fdCell 3.6s var(--ease) infinite; }
.fd-cell:nth-of-type(1) { animation-delay: 0s; }
.fd-cell:nth-of-type(2) { animation-delay: .3s; }
.fd-cell:nth-of-type(3) { animation-delay: .6s; }
.fd-cell:nth-of-type(4) { animation-delay: .9s; }
@keyframes fdCell {
  0%, 6%    { fill: rgba(255,255,255,.07); }
  24%, 76%  { fill: var(--volt); }
  94%, 100% { fill: rgba(255,255,255,.07); }
}
/* Discharging: the pack is full and steady, then visibly working. */
.flowd[data-state="night"] .fd-cell,
.flowd[data-state="brownout"] .fd-cell { fill: var(--volt); }
.flowd[data-state="brownout"] .fd-cell:nth-of-type(1) { fill: rgba(255,184,28,.35); }

.fd-led { animation: fdLed 2.4s var(--ease) infinite; }
@keyframes fdLed { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

/* --- the grid, when it goes -------------------------------------------- */
.fd-outage { transition: opacity 420ms var(--ease-out); }
.flowd[data-state="brownout"] .fd-outage { opacity: 1; }
.flowd[data-state="brownout"] .fd-pylon .fd-line { stroke: rgba(157,176,206,.16); }
.flowd[data-state="brownout"] .fd-gridwire.fd-wire { stroke: rgba(224,82,60,.34); stroke-dasharray: 5 7; }
.flowd[data-state="brownout"] .fd-lbl-grid { fill: #E0523C; }

/* --- the caption under it ---------------------------------------------- */
/* All three captions occupy the same grid cell, so the block is exactly as
   tall as the longest of them and no caption can ever be cut off. Reserving
   a fixed height instead — which is what this did first — clipped the last
   line the moment the daytime copy grew. */
.fd-caps {
  display: grid;
  padding: clamp(12px, 1.6vw, 18px) clamp(18px, 2.2vw, 30px) clamp(18px, 2.2vw, 26px);
}
.fd-cap {
  grid-area: 1 / 1;
  margin: 0; max-width: 62ch;
  font-size: var(--fs-sm); line-height: 1.6; color: var(--on-night-2);
  opacity: 0; visibility: hidden;
  transform: translateY(6px);
  transition: opacity 380ms var(--ease-out), transform 380ms var(--ease-out), visibility 0s linear 380ms;
}
.fd-cap.is-on {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity 380ms var(--ease-out) 60ms, transform 380ms var(--ease-out) 60ms, visibility 0s;
}

@media (max-width: 720px) {
  .fd-lbl { font-size: 17px; letter-spacing: .08em; }
  .fd-inner-lbl { font-size: 16px; }
  .fd-note { font-size: 17px; }
  .fd-tab { font-size: var(--fs-xs); padding: 8px 13px; }
}

@media (prefers-reduced-motion: reduce) {
  .fd-flow { animation: none; stroke-dasharray: none; opacity: .95; }
  .fd-beam { animation: none; stroke-dasharray: 3 10; }
  .fd-pulse, .fd-led { animation: none; }
  .fd-pulse { opacity: 0; }
  .fd-cell, .flowd[data-state] .fd-cell { animation: none; fill: var(--volt); }
  .flowd [data-on], .fd-cap, .fd-outage { transition: none; }
}

/* The windows carry the argument. In daylight the house is lit by the sun and
   the lamps are off; after dark they come on; during a brownout they stay on
   while the pylon beside them has a red cross through it. That contrast is
   the entire product, so it is worth drawing rather than only captioning. */
.fd-win { transition: fill 520ms var(--ease-out); }
.flowd[data-state="daylight"] .fd-win,
.flowd[data-state="mains"] .fd-win     { fill: rgba(157,176,206,.10); }
.flowd[data-state="night"] .fd-win,
.flowd[data-state="restored"] .fd-win  { fill: rgba(255,184,28,.62); }
.flowd[data-state="brownout"] .fd-win  { fill: rgba(255,184,28,.9); }
.flowd[data-state="brownout"] .fd-bolt,
.flowd[data-state="night"] .fd-bolt    { filter: drop-shadow(0 0 10px rgba(255,184,28,.6)); }
@media (prefers-reduced-motion: reduce) { .fd-win { transition: none; } }

/* The heavy-industry hero is a warm flare, not a blue sky, so its plate is
   graded warm to match. A plate is only in the scene if it is lit by the same
   thing the scene is. */
.hero-engineered .hero-plate img { filter: brightness(.66) saturate(.78) contrast(1.06) sepia(.16) hue-rotate(-12deg); }
.hero-engineered .hero-plate::after {
  background:
    linear-gradient(180deg, rgba(16,9,10,.78) 0%, rgba(16,9,10,.34) 22%, transparent 40%),
    linear-gradient(158deg, rgba(96,54,30,.34) 0%, rgba(48,26,20,.32) 46%, rgba(20,12,12,.48) 100%),
    linear-gradient(180deg, transparent 62%, rgba(14,8,10,.62) 92%, rgba(14,8,10,.84) 100%);
}
.hero-engineered .hero-plate::before {
  background: radial-gradient(closest-side, rgba(230,150,80,.16), transparent 72%);
}

/* --------------------------------------------------------------------------
   The hero eyebrow, on a photograph

   Measured against the actual painted background rather than the stylesheet,
   the small label over a hero came out at 3.48 on the home page, 4.00 on
   thanks and 4.46 on the consultation page. At 12px it is not "large text",
   so it needs 4.5 and it was not getting it — the brighter parts of those
   three photographs sit right under it.

   Section eyebrows on flat surfaces are untouched; this is only the one that
   has to survive a picture behind it.
   -------------------------------------------------------------------------- */
.hero .hero-copy .eyebrow,
.band-inner .eyebrow {
  color: var(--on-night);
  text-shadow: 0 1px 3px rgba(3,10,22,.55), 0 0 16px rgba(3,10,22,.45);
}
.hero .hero-copy .eyebrow::before {
  background: var(--volt);
  box-shadow: 0 0 10px rgba(255,184,28,.55);
}

/* The same system, drawn as a column for a phone.

   Squeezed into 350 pixels the wide version puts its labels at about six
   pixels tall, which is a decoration pretending to be a diagram. The stacked
   one uses the height a phone actually has, so the type is readable and the
   eye follows the current downward. Only one of the two is ever in the
   accessibility tree: the wide one carries the title and description, and
   the stacked one is marked aria-hidden. */
.flowd .fd-svg-sm { display: none; }
@media (max-width: 720px) {
  .flowd .fd-svg-lg { display: none; }
  .flowd .fd-svg-sm { display: block; }
  /* the stacked drawing sets its own type scale */
  .fd-svg-sm .fd-lbl { font-size: 21px; letter-spacing: .09em; }
  .fd-svg-sm .fd-inner-lbl { font-size: 17px; }
  .fd-svg-sm .fd-note { font-size: 21px; }
  .fd-svg-sm .fd-wire { stroke-width: 2; }
  .fd-svg-sm .fd-flow { stroke-width: 3.4; stroke-dasharray: 7 22; }
  .fd-svg-sm .fd-box { stroke-width: 1.8; }
  .fd-svg-sm .fd-line { stroke-width: 1.7; }
  .fd-stage { padding-bottom: 6px; }
}

/* Size. The drawing was filling more of the screen than it earns — on a wide
   monitor it grew to whatever the container gave it, and on a phone it pushed
   the caption below the fold. It is capped now, and the grid's own line is
   drawn lighter than the roof's, because the grid is the supporting act. */
.flowd .fd-svg-lg { max-width: 780px; margin-inline: auto; }
.flowd .fd-svg-sm { max-width: 320px; margin-inline: auto; }

/* The grid supporting the house is a quieter event than the sun running it. */
.fd-flow.fd-gridwire { stroke-width: 2; opacity: .62; stroke-dasharray: 4 18; }
.fd-svg-sm .fd-flow.fd-gridwire { stroke-width: 2.6; }

/* ==========================================================================
   L. UNDER CONSTRUCTION

   A page with nothing on it yet should look deliberate rather than broken.
   The hazard band along the top is the plainest signal there is that
   something is being worked on, and it means the reader stops asking whether
   the page failed to load.
   ========================================================================== */

.empty-build {
  position: relative;
  overflow: hidden;
  padding-top: clamp(46px, 6vw, 78px);
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255,184,28,.10), transparent 62%),
    var(--day-100);
  box-shadow: inset 0 0 0 1px var(--rule-day);
}
.day-white .empty-build, .day .empty-build { background:
  radial-gradient(120% 90% at 50% 0%, rgba(255,184,28,.12), transparent 62%), var(--day-050); }

/* the hazard band */
.empty-build::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 7px;
  background: repeating-linear-gradient(
    -45deg,
    var(--volt) 0 12px,
    var(--night-900) 12px 24px);
  opacity: .85;
}

.empty-mark {
  display: inline-grid; place-items: center;
  width: 62px; aspect-ratio: 1; border-radius: 17px;
  margin-bottom: 18px;
  background: linear-gradient(155deg, rgba(255,184,28,.18), rgba(255,184,28,.05));
  box-shadow: inset 0 0 0 1px var(--rule-day-2);
  color: var(--volt-deep);
}
.empty-build .empty-tag {
  font-size: var(--fs-label); font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; color: var(--volt-deep);
  margin-bottom: 10px;
}
.empty-build h3 { font-size: clamp(1.25rem, 2.2vw, 1.625rem); letter-spacing: -.02em; }
.empty-build p { font-size: var(--fs-sm); color: var(--ink-2); margin-top: 12px; max-width: 52ch; margin-inline: auto; }
.empty-build .empty-alt {
  margin-top: 22px; padding-top: 20px;
  border-top: 1px solid var(--rule-day);
  max-width: 46ch;
  color: var(--ink-3);
}
@media (prefers-reduced-motion: reduce) { .empty-build::before { opacity: .75; } }

/* ==========================================================================
   M. SHARING

   Platform marks are filled glyphs rather than the stroke language used
   everywhere else on the site. That is deliberate: a logo has to be
   recognised at a glance, and a redrawn outline version is not.
   ========================================================================== */

.share {
  margin-top: clamp(36px, 4.5vw, 60px);
  padding-top: clamp(24px, 3vw, 34px);
  border-top: 1px solid var(--rule-day);
}
.share-lbl {
  display: flex; align-items: center; gap: 9px;
  font-size: var(--fs-label); font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 16px;
}
.share-lbl .ico { color: var(--volt-deep); }

.share-row { display: flex; flex-wrap: wrap; gap: 10px; }

.sh {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 10px 16px 10px 13px;
  border: 0; border-radius: var(--r-pill);
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  background: var(--day-100);
  box-shadow: inset 0 0 0 1px var(--rule-day);
  color: var(--ink);
  cursor: pointer; text-decoration: none;
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease),
              background var(--t) var(--ease), color var(--t) var(--ease);
}
.sh:hover { transform: translateY(-2px); background: var(--day-000); box-shadow: var(--lift-1), inset 0 0 0 1px var(--rule-day-2); }
.sh:focus-visible { outline: 2px solid var(--volt-deep); outline-offset: 3px; }
.sh-glyph { width: 17px; height: 17px; flex: none; }
.sh .ico { width: 17px; height: 17px; }

/* Each platform takes its own colour on hover only. At rest the row stays
   quiet, so a post ends on its own words rather than on five brand logos. */
.sh-fb:hover  { background: #1877F2; color: #fff; box-shadow: 0 8px 20px -8px rgba(24,119,242,.7); }
.sh-wa:hover  { background: #25D366; color: #06331A; box-shadow: 0 8px 20px -8px rgba(37,211,102,.7); }
.sh-x:hover   { background: #14171A; color: #fff; box-shadow: 0 8px 20px -8px rgba(20,23,26,.7); }
.sh-li:hover  { background: #0A66C2; color: #fff; box-shadow: 0 8px 20px -8px rgba(10,102,194,.7); }
.sh-native:hover, .sh-copy:hover { background: var(--volt); color: var(--night-900); }
.sh-native:hover .ico .live, .sh-copy:hover .ico .live { stroke: var(--night-900); }

.sh-copy.is-done {
  background: var(--volt); color: var(--night-900);
  box-shadow: inset 0 0 0 1px rgba(255,184,28,.6);
}
.sh-copy.is-done .ico .live { stroke: var(--night-900); }

@media (max-width: 560px) {
  .share-row { gap: 8px; }
  .sh { padding: 10px 14px 10px 12px; }
  /* the labels go, the marks stay — five words per row does not fit a phone */
  .sh:not(.sh-native):not(.sh-copy) span { position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
  .sh:not(.sh-native):not(.sh-copy) { padding: 12px; }
}
@media (prefers-reduced-motion: reduce) { .sh:hover { transform: none; } }

/* ==========================================================================
   N. THE WORDMARK, IN MOTION

   Artwork from janarix-logo-animations.html, motion "07 Daylight" — the one
   that file marks as a loop and describes as being for site headers and
   screens. A glint tracks the arc while light travels up the ray.

   The two placements behave differently on purpose:

     footer   the loop, slowed from 6s to 9s. The footer sits at the bottom
              of every page, so it is only ever moving while somebody has
              scrolled down and stopped.

     header   not a loop. The header is on screen for the entire visit, and
              continuous motion in permanent chrome is how a page becomes
              tiring to sit with. It plays once when the page settles, and
              again on hover or keyboard focus. Otherwise it is a still logo.
   ========================================================================== */

.jx-logo { display: block; width: auto; height: 34px; overflow: visible; }
.jx-ink  { fill: #FFFFFF; }
.jx-arc  { fill: #FFFFFF; }
.jx-ray  { fill: var(--volt); }

/* the two moving parts, parked and invisible until something plays them */
.jx-glint {
  offset-path: path("M88 91 Q661.5 -61.2 1235 91");
  offset-rotate: 0deg;
  opacity: 0;
}
.jx-shim { opacity: 0; }
.jx-shimwrap { transform: translateX(-560px); }

@keyframes jxGlintMove { from { offset-distance: 0%; } to { offset-distance: 100%; } }
@keyframes jxGlintFade { 0% { opacity: 0; } 12% { opacity: .85; } 84% { opacity: .85; } 100% { opacity: 0; } }
@keyframes jxShim {
  0%   { opacity: 0;   transform: translateX(-560px); }
  14%  { opacity: .85; }
  80%  { opacity: .85; }
  100% { opacity: 0;   transform: translateX(1500px); }
}

/* --- footer: the slow loop ------------------------------------------- */
.jx-logo.is-loop.is-live .jx-glint {
  animation: jxGlintMove 9s linear infinite, jxGlintFade 9s linear infinite;
}
.jx-logo.is-loop.is-live .jx-shimwrap {
  animation: jxShim 9s cubic-bezier(.5, 0, .5, 1) infinite;
}

/* --- header: once on arrival, and on hover ---------------------------- */
.brand .jx-logo { height: 34px; }
.brand.jx-play .jx-glint,
.brand:hover .jx-glint,
.brand:focus-visible .jx-glint {
  animation: jxGlintMove 2.6s linear, jxGlintFade 2.6s linear;
}
.brand.jx-play .jx-shimwrap,
.brand:hover .jx-shimwrap,
.brand:focus-visible .jx-shimwrap {
  animation: jxShim 2.6s cubic-bezier(.5, 0, .5, 1);
}

.foot-brand .jx-logo { height: 30px; }

/* Nothing moves, and the logo is simply a logo. */
@media (prefers-reduced-motion: reduce) {
  .jx-glint, .jx-shim, .jx-shimwrap,
  .jx-logo.is-loop.is-live .jx-glint,
  .jx-logo.is-loop.is-live .jx-shimwrap,
  .brand.jx-play .jx-glint, .brand:hover .jx-glint,
  .brand.jx-play .jx-shimwrap, .brand:hover .jx-shimwrap { animation: none !important; opacity: 0; }
}

/* The hero's two buttons.

   The copy column is capped at 640px so the headline and the lead break in
   a readable measure. The button row lives inside that column, and when the
   calculator link was renamed to "How long would your house last?" the pair
   came to 663px and wrapped onto two lines.

   Line length is a constraint on prose, not on buttons. The row is allowed
   to run to its own content width and sit a little wider than the paragraph
   above it, into hero space that was empty anyway. It still wraps on a
   narrow screen, where stacking is what you want. */
@media (min-width: 1000px) {
  .hero-copy .hero-actions {
    width: max-content;
    max-width: min(780px, 74vw);
  }
}

/* ==========================================================================
   O. HOW A SYSTEM GROWS

   Replaces a price ladder that was only ever true of the sealed cabinet. Two
   platforms, two step sizes, and the three things that actually happen on the
   day. Numbered, because it is a sequence and the reader is deciding whether
   it sounds like a morning's work or a rebuild.
   ========================================================================== */

.grow { display: grid; gap: clamp(14px, 1.6vw, 20px); }

.grow-card {
  position: relative;
  padding: clamp(20px, 2.2vw, 26px);
  border-radius: var(--r);
  background: var(--day-000);
  box-shadow: inset 0 0 0 1px var(--rule-day);
}
.day-white .grow-card { background: var(--day-050); }
.night .grow-card, .night-deep .grow-card {
  background: rgba(255,255,255,.04);
  box-shadow: inset 0 0 0 1px var(--rule-night);
}

.grow-tag {
  font-size: var(--fs-label); font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--volt-deep);
}
.night .grow-tag, .night-deep .grow-tag { color: var(--volt); }

.grow-step {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.0625rem, 1.6vw, 1.25rem); letter-spacing: -.018em;
  margin-top: 8px;
}
.grow-fig {
  margin-top: 6px;
  font-size: var(--fs-sm); color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.grow-steps {
  list-style: none;
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--rule-day);
  counter-reset: grow;
  display: grid; gap: 10px;
}
.night .grow-steps, .night-deep .grow-steps { border-top-color: var(--rule-night); }
.grow-steps li {
  counter-increment: grow;
  display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 12px;
  align-items: start;
  font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.55;
}
.grow-steps li::before {
  content: counter(grow);
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--display); font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--volt-deep);
  background: rgba(255,184,28,.15);
  box-shadow: inset 0 0 0 1px var(--rule-day-2);
}
.night .grow-steps li::before, .night-deep .grow-steps li::before { color: var(--volt); }

/* These stack, and stay stacked inside a two-column section.

   The first attempt put them side by side above 1140px, but that measured
   the window rather than the column they sit in. Inside `.split` the column
   is about 600px at 1440, so two cards came out at 290px each and the step
   text broke into four-word lines. Stacked, each one gets the full measure.

   Side by side is only allowed where the block has the full width to itself. */
.wrap > .grow { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }

/* ==========================================================================
   P. THE LOAD PICKER ON A PHONE

   Three options reading "Essentials · 0.6 kW", "+ One aircon · 1.4 kW" and
   "+ Two aircons · 2.2 kW" cannot sit on one line at 390px. They were
   wrapping inside a pill-shaped track, which left a two-row blob with a
   pill radius and one option stranded on the second line.

   Below 620px it stops pretending to be a segmented control and becomes what
   it actually is: a list of three choices, each the full width of the column.
   ========================================================================== */

@media (max-width: 620px) {
  .toggle {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
    padding: 6px;
    border-radius: var(--r);
    width: 100%;
  }
  .toggle button {
    justify-content: flex-start;
    text-align: left;
    width: 100%;
    min-height: 46px;
    padding: 0 15px;
    border-radius: calc(var(--r) - 4px);
    font-size: var(--fs-sm);
  }
  /* A chosen option should be legible as chosen without relying on colour
     alone, so the selected one also carries a gold marker. */
  .toggle button[aria-pressed="true"] { position: relative; padding-left: 32px; }
  .toggle button[aria-pressed="true"]::before {
    content: ''; position: absolute; left: 15px; top: 50%;
    width: 7px; height: 7px; margin-top: -3.5px; border-radius: 50%;
    background: var(--volt);
  }
}

/* ==========================================================================
   Q. THE UPGRADE LADDER

   Two routes, six rungs each, and the point of it is a feeling rather than a
   fact: that you can start where your budget is and climb later.

   Figures come from the two datasheets in `datasheets/`. The V6000 sheet is
   the one that settles the shape — it grows two independent ways, batteries
   for capacity and whole sets in parallel for power, and that is what makes
   the two platforms genuinely different rather than one being a lesser
   version of the other.
   ========================================================================== */

.lad {
  border-radius: var(--r-lg);
  background: var(--day-000);
  box-shadow: inset 0 0 0 1px var(--rule-day);
  overflow: hidden;
}
.night .lad, .night-deep .lad {
  background: rgba(255,255,255,.035);
  box-shadow: inset 0 0 0 1px var(--rule-night);
}

/* --- the two routes --------------------------------------------------- */
.lad-routes {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--rule-day);
}
.night .lad-routes, .night-deep .lad-routes { background: var(--rule-night); }

/* A line above the two routes, because without it the pair reads as a header
   rather than a choice and the second one gets missed entirely. */
.lad-lead {
  margin: 0;
  padding: clamp(15px, 1.8vw, 20px) clamp(16px, 2vw, 24px) 0;
  font-size: var(--fs-sm); color: var(--ink-2);
}
.night .lad-lead, .night-deep .lad-lead { color: var(--on-night-2); }

.lad-route {
  appearance: none; border: 0; cursor: pointer; font: inherit;
  display: grid; gap: 3px; justify-items: start; text-align: left;
  padding: clamp(15px, 1.8vw, 20px) clamp(16px, 2vw, 24px);
  background: var(--day-100);
  color: var(--ink-2);
  transition: background var(--t) var(--ease), color var(--t) var(--ease);
}

/* The one you are not looking at says so, and says how to get to it. The
   first version relied on the dark fill alone to mark the active tab, which
   left the inactive one looking like a static heading. */
.lad-route-go {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 7px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase;
  color: var(--volt-deep);
}
.lad-route-go svg { width: 13px; height: 13px; transition: transform var(--t) var(--ease); }
.lad-route:hover .lad-route-go svg { transform: translateX(3px); }
.lad-route.is-on .lad-route-go { color: var(--volt); opacity: .8; }
/* the inactive tab keeps a gold edge, so the pair reads as two options */
.lad-route:not(.is-on) { box-shadow: inset 0 -3px 0 rgba(255,184,28,.55); }
.lad-route:not(.is-on):hover { box-shadow: inset 0 -3px 0 var(--volt); }
.night .lad-route, .night-deep .lad-route { background: var(--night-800); }
.lad-route:hover { background: var(--day-050); color: var(--ink); }
.lad-route.is-on { background: var(--night-900); color: var(--on-night); }
.lad-route:focus-visible { outline: 2px solid var(--volt); outline-offset: -3px; }
.lad-route-t { font-weight: 600; font-size: var(--fs-sm); }
.lad-route-d { font-size: var(--fs-xs); opacity: .74; }
.lad-route.is-on .lad-route-d { color: var(--volt); opacity: 1; }

/* --- the rungs -------------------------------------------------------- */
/* Flex rather than a fixed column count: one route has six rungs and the
   other has seven, and a six-column grid dropped the seventh onto its own
   line. Every rung takes an equal share of whatever is there. */
.lad-rungs {
  display: flex; flex-wrap: wrap;
  gap: 6px;
  padding: clamp(16px, 2vw, 22px) clamp(16px, 2vw, 24px) 0;
}
.lad-rung { flex: 1 1 92px; min-width: 0; }
.lad-rung {
  appearance: none; border: 0; cursor: pointer; font: inherit;
  display: grid; gap: 4px; justify-items: center;
  padding: 10px 4px 11px;
  border-radius: var(--r-sm);
  background: var(--day-100);
  color: var(--ink-3);
  position: relative;
  transition: background var(--t) var(--ease), color var(--t) var(--ease), transform var(--t) var(--ease);
}
.night .lad-rung, .night-deep .lad-rung { background: rgba(255,255,255,.05); }
.lad-rung:hover { transform: translateY(-2px); color: var(--ink); }
.lad-rung.is-on { background: var(--volt); color: var(--night-900); }
.lad-rung:focus-visible { outline: 2px solid var(--volt-deep); outline-offset: 2px; }
.lad-n {
  font-size: 10px; font-weight: 700; letter-spacing: .12em; opacity: .6;
}
.lad-cap {
  font-family: var(--display); font-weight: 600; font-size: .9375rem;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1;
}
.lad-cap i { font-style: normal; font-size: 9.5px; margin-left: 2px; opacity: .7; letter-spacing: .04em; }

/* A rail behind the rungs was tried and removed: as a grid item spanning
   every column it pushed the six rungs into implicit columns and left a
   stray line to the left of them. The rungs already read as a climb from
   their numbers and their rising capacity, so the rail was decoration that
   cost a layout bug. */

/* --- the stage -------------------------------------------------------- */
.lad-stage {
  display: grid;
  padding: clamp(18px, 2.4vw, 28px) clamp(16px, 2vw, 24px);
}
.lad-panel {
  grid-area: 1 / 1;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(18px, 3vw, 40px);
  align-items: center;
  opacity: 0; visibility: hidden;
  transform: translateY(8px);
  transition: opacity 340ms var(--ease-out), transform 340ms var(--ease-out), visibility 0s linear 340ms;
}
.lad-panel.is-on {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity 340ms var(--ease-out) 40ms, transform 340ms var(--ease-out) 40ms, visibility 0s;
}

.lad-art svg { width: 100%; height: auto; max-height: 190px; display: block; }
[data-route-panel="mars"] .lad-art svg { max-width: 120px; margin-inline: auto; }

/* A unit that is not bought yet is drawn as an outline, so the ladder shows
   the ceiling as well as where you are on it. */
.lad-body {
  fill: none; stroke: var(--rule-day-2); stroke-width: 1.4;
  transition: fill 380ms var(--ease-out), stroke 380ms var(--ease-out);
}
.lad-screen { fill: var(--rule-day-2); opacity: .5; transition: fill 380ms var(--ease-out), opacity 380ms var(--ease-out); }
.lad-foot { stroke: var(--rule-day-2); stroke-width: 1.4; stroke-linecap: round; fill: none; }
.lad-u.is-on .lad-body { fill: var(--night-900); stroke: var(--night-900); }
.lad-u.is-on .lad-screen { fill: var(--volt); opacity: 1; }
.lad-u.is-on .lad-screen.lad-blank { fill: rgba(255,255,255,.16); }
.lad-u.is-on .lad-foot { stroke: var(--ink-3); }
.night .lad-u.is-on .lad-body, .night-deep .lad-u.is-on .lad-body {
  fill: var(--volt); stroke: var(--volt);
}

.lad-name {
  font-size: var(--fs-label); font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--volt-deep);
}
.night .lad-name, .night-deep .lad-name { color: var(--volt); }
.lad-nums {
  font-family: var(--display); font-size: clamp(1.25rem, 2.2vw, 1.75rem);
  letter-spacing: -.028em; margin-top: 7px; font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}
.lad-nums b { color: var(--ink); font-weight: 600; }
.night .lad-nums b, .night-deep .lad-nums b { color: var(--on-night); }
.lad-sep { opacity: .45; margin: 0 6px; }
.lad-price {
  margin-top: 10px; font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.lad-why { margin-top: 10px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.55; max-width: 44ch; }

.lad-ceiling {
  margin: 0;
  padding: clamp(14px, 1.8vw, 18px) clamp(16px, 2vw, 24px);
  border-top: 1px solid var(--rule-day);
  background: var(--day-050);
  font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.55;
}
.night .lad-ceiling, .night-deep .lad-ceiling {
  border-top-color: var(--rule-night); background: rgba(0,0,0,.16); color: var(--on-night-2);
}
.lad-ceiling b { color: var(--ink); }
.night .lad-ceiling b, .night-deep .lad-ceiling b { color: var(--on-night); }

@media (max-width: 780px) {
  .lad-routes { grid-template-columns: minmax(0, 1fr); }
  .lad-rungs { gap: 8px; }
  .lad-rung { flex: 1 1 82px; }
  .lad-panel { grid-template-columns: minmax(0, 1fr); }
  .lad-art { order: 2; }
  [data-route-panel="v6000"] .lad-art svg { max-height: 120px; }
  [data-route-panel="mars"] .lad-art svg { max-width: 96px; }
}

@media (prefers-reduced-motion: reduce) {
  .lad-panel, .lad-body, .lad-screen { transition: none; }
  .lad-rung:hover { transform: none; }
}

/* The two parallel photographs under the ladder. They are the proof that the
   ceiling is real: six cabinets in a row, and four inverters working as one. */
.fig-pair {
  margin: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(12px, 1.6vw, 20px);
}
@media (max-width: 900px) { .fig-pair { grid-template-columns: minmax(0, 1fr); } }

/* --- The power rung.

   Both ladders end on a step where the answer to "my house needs more" stops
   being another battery and becomes another inverter. It is a different kind
   of decision, so it looks like one: a gold outline rather than a gold fill,
   and the second unit arrives rather than simply being there. */
.lad-rung.is-pwr {
  background: rgba(255,184,28,.12);
  box-shadow: inset 0 0 0 1.5px var(--volt-deep);
  color: var(--volt-deep);
}
.night .lad-rung.is-pwr, .night-deep .lad-rung.is-pwr { color: var(--volt); box-shadow: inset 0 0 0 1.5px var(--volt); }
.lad-rung.is-pwr.is-on { background: var(--volt); color: var(--night-900); box-shadow: none; }
.lad-rung.is-pwr .lad-n { opacity: .85; letter-spacing: .06em; }

/* the cable between two paralleled units, with current on it */
.lad-link { fill: none; stroke: var(--rule-day-2); stroke-width: 1.6; }
.lad-flow {
  fill: none; stroke: var(--volt); stroke-width: 2.2; stroke-linecap: round;
  stroke-dasharray: 4 12; opacity: 0;
}
.lad-panel.is-pwr.is-on .lad-flow {
  opacity: 1;
  animation: ladFlow 1.9s linear infinite .55s;
}
@keyframes ladFlow { to { stroke-dashoffset: -32; } }

/* the second set arriving beside the first */
.lad-panel.is-pwr .lad-u[data-g="b"] { opacity: 0; }
.lad-panel.is-pwr.is-on .lad-u[data-g="b"] {
  animation: ladJoin 620ms var(--ease-out) forwards;
}
.lad-panel.is-pwr.is-on g:nth-child(n+1) > .lad-u[data-g="b"] { animation-delay: .16s; }
.lad-panel.is-pwr.is-on g:nth-child(n+3) > .lad-u[data-g="b"] { animation-delay: .26s; }
.lad-panel.is-pwr.is-on g:nth-child(n+5) > .lad-u[data-g="b"] { animation-delay: .36s; }
.lad-panel.is-pwr.is-on g:nth-child(n+7) > .lad-u[data-g="b"] { animation-delay: .46s; }
@keyframes ladJoin {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: none; }
}
.lad-panel.is-pwr .lad-u { transform-box: view-box; }

[data-route-panel="mars"] .lad-panel.is-pwr .lad-art svg { max-width: 200px; }

@media (prefers-reduced-motion: reduce) {
  .lad-panel.is-pwr .lad-u[data-g="b"] { opacity: 1; animation: none; }
  .lad-panel.is-pwr.is-on .lad-flow { animation: none; stroke-dasharray: none; opacity: .9; }
}

/* ==========================================================================
   R. VIEW LARGER

   The two parallel photographs are the proof that the ceiling is real, and
   at a third of the page width you cannot count the cabinets. They open.
   ========================================================================== */

.zoom-open {
  position: absolute; right: 12px; top: 12px; z-index: 4;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 13px 8px 10px;
  border: 0; border-radius: var(--r-pill);
  font: inherit; font-size: var(--fs-xs); font-weight: 600;
  background: rgba(10,23,41,.62);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #fff;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.18);
  cursor: pointer; opacity: 0;
  transition: opacity var(--t) var(--ease), background var(--t) var(--ease);
}
.zoom-open svg { width: 16px; height: 16px; }
[data-zoom]:hover .zoom-open,
[data-zoom]:focus-within .zoom-open { opacity: 1; }
.zoom-open:hover { background: var(--volt); color: var(--night-900); box-shadow: none; }
.zoom-open:focus-visible { opacity: 1; outline: 2px solid var(--volt); outline-offset: 2px; }
/* On a touch screen there is no hover to reveal it, so it is simply there. */
@media (hover: none) { .zoom-open { opacity: 1; } }

.zoomer {
  border: 0; padding: 0; margin: auto;
  max-width: min(1180px, 94vw); max-height: 92vh;
  width: max-content;
  background: transparent;
  overflow: visible;
}
.zoomer::backdrop { background: rgba(4,10,20,.86); backdrop-filter: blur(6px); }
.zoomer figure { margin: 0; }
.zoomer img {
  display: block;
  max-width: min(1180px, 94vw); max-height: 78vh;
  width: auto; height: auto;
  border-radius: var(--r);
  box-shadow: 0 40px 90px -30px rgba(0,0,0,.8);
  background: var(--night-900);
}
.zoomer figcaption {
  margin-top: 14px;
  font-size: var(--fs-sm); line-height: 1.55;
  color: var(--on-night-2);
  max-width: 70ch;
}
.zoom-x {
  position: absolute; right: -6px; top: -46px;
  width: 38px; height: 38px; border: 0; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  background: rgba(255,255,255,.12); color: #fff;
}
.zoom-x svg { width: 19px; height: 19px; }
.zoom-x:hover { background: var(--volt); color: var(--night-900); }
.zoom-x:focus-visible { outline: 2px solid var(--volt); outline-offset: 2px; }

@media (max-width: 620px) {
  .zoom-x { top: auto; bottom: -50px; right: 50%; transform: translateX(50%); }
  .zoomer figcaption { font-size: var(--fs-xs); }
}

/* The directions link beside the address. It only has an href because
   `mapUrl` is set in js/site-config.js; empty that field and the link points
   nowhere, so it is written to disappear rather than dangle. */
.map-link {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 7px;
  font-size: var(--fs-xs); font-weight: 600;
  color: var(--volt-deep); text-decoration: none;
  transition: color var(--t) var(--ease);
}
.night .map-link, .night-deep .map-link, .foot .map-link { color: var(--volt); }
.map-link svg { width: 14px; height: 14px; flex: none; }
.map-link:hover { text-decoration: underline; text-underline-offset: 3px; }
.map-link:focus-visible { outline: 2px solid var(--volt); outline-offset: 3px; border-radius: 3px; }
.map-link[href="#"], .map-link:not([href]) { display: none; }

/* The tab label has to change when you switch to it. The first version wrote
   "Showing" and "See this one" into the markup once, so clicking Mars I Plus
   left it still saying "See this one" while you were looking at it. Both
   labels now ship on both tabs and the class decides which one is visible. */
.lad-route-go b, .lad-route-go i { font: inherit; font-style: normal; display: none; align-items: center; gap: 5px; }
.lad-route.is-on .lad-route-go b { display: inline-flex; }
.lad-route:not(.is-on) .lad-route-go i { display: inline-flex; }

/* The kitchen scene is already shot at night, so the standard scrim crushed
   the one thing worth seeing in it: the unit itself, sitting dark on dark in
   the middle of the frame. This one holds the left for the headline and then
   clears quickly, so the unit, the open fridge and the lit lamp all survive. */
.hero-battery .hero-scrim.is-work {
  background:
    linear-gradient(100deg,
      rgba(4,12,24,.93) 0%, rgba(5,14,30,.80) 24%, rgba(6,18,38,.40) 44%,
      rgba(6,18,38,.14) 62%, transparent 84%),
    linear-gradient(180deg, rgba(4,12,24,.56) 0%, transparent 30%, rgba(4,12,24,.30) 100%);
}
@media (max-width: 780px) {
  .hero-battery .hero-scrim.is-work {
    background: linear-gradient(180deg,
      rgba(4,11,24,.88) 0%, rgba(4,12,26,.76) 30%,
      rgba(5,14,30,.74) 60%, rgba(4,12,26,.88) 100%);
  }
}

/* ==========================================================================
   S. PORTABLE POWER

   The battery page covered the one that stays at the house and stopped. This
   is the half it was missing: the ones you pick up and take with you, which
   are a different product with a different job rather than a smaller version
   of the same thing.
   ========================================================================== */

.ps-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: clamp(14px, 1.8vw, 22px);
}

.ps-card {
  display: flex; flex-direction: column;
  border-radius: var(--r-lg);
  background: var(--day-000);
  box-shadow: inset 0 0 0 1px var(--rule-day);
  overflow: hidden;
}

/* The product shots are on white, so they get their own light plate rather
   than being dropped onto the page's warm grey. */
.ps-shot {
  position: relative;
  background: linear-gradient(165deg, #FFFFFF, var(--day-100));
  padding: clamp(18px, 2.4vw, 30px);
  border-bottom: 1px solid var(--rule-day);
}
.ps-shot img {
  width: 100%; aspect-ratio: 4/3; object-fit: contain; display: block;
  transition: transform 700ms var(--ease-out);
}
.ps-card:hover .ps-shot img { transform: scale(1.04); }

.ps-status {
  position: absolute; top: 12px; left: 12px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  padding: 5px 10px; border-radius: var(--r-pill);
}
.ps-status.is-stock { background: rgba(38,138,86,.14); color: #1C6B43; }
.ps-status.is-pre   { background: rgba(255,184,28,.20); color: var(--volt-deep); }

.ps-body { padding: clamp(18px, 2.2vw, 26px); display: flex; flex-direction: column; flex: 1; }
.ps-body h3 { font-size: 1.0625rem; letter-spacing: -.014em; }

.ps-nums {
  margin-top: 8px;
  font-family: var(--display); font-size: 1.375rem; letter-spacing: -.028em;
  font-variant-numeric: tabular-nums; color: var(--ink-3);
}
.ps-nums b { color: var(--ink); font-weight: 600; }
.ps-sep { opacity: .4; margin: 0 5px; }

.ps-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 13px; }
.ps-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px 5px 8px; border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: 600;
  background: rgba(255,184,28,.13);
  color: var(--volt-deep);
}
.ps-badge .ico { width: 15px; height: 15px; }

.ps-why { margin-top: 14px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.55; }
.ps-solar, .ps-grow {
  margin-top: 10px; font-size: var(--fs-xs); color: var(--ink-3); line-height: 1.5;
}
.ps-solar b, .ps-grow b { color: var(--ink-2); font-weight: 600; }
.ps-grow { padding-top: 10px; border-top: 1px solid var(--rule-day); }
/* The one that does not expand says so, rather than staying quiet about it. */
.ps-grow-no { opacity: .8; }
.ps-body .tlink { margin-top: auto; padding-top: 16px; }


/* ==========================================================================
   USER PREFERENCES FOR TRANSPARENCY AND CONTRAST

   Three translucent surfaces are defined above — the plate mark, .glass and
   the zoom button — plus the header, which fades its own backdrop in as you
   scroll. Blur is lovely and it is also the first thing to fail somebody who
   has trouble reading text over a busy background, so the browser lets them
   ask for less of it. Nothing was listening.

   These are the two settings people actually turn on. On iOS they are
   Settings > Accessibility > Display & Text Size > Reduce Transparency and
   Increase Contrast; on macOS and Windows they sit in the same part of the
   system settings. They are separate from Reduce Motion, which the site has
   respected from the start.

   Neither one changes the layout. A surface stops being see-through and
   becomes a solid panel in a colour the site already uses, so the page looks
   like itself, just plainer and easier to read. */

@media (prefers-reduced-transparency: reduce) {
  .plate-mark,
  .glass,
  .zoom-open {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  /* Opaque versions of the same colours, so nothing shifts in hue — only
     the see-through part goes. */
  .plate-mark { background: var(--night-900); }
  .glass      { background: var(--night-800); }
  .zoom-open  { background: var(--night-900); }

  .site-header::before { backdrop-filter: none; -webkit-backdrop-filter: none; }
  .site-header.is-solid::before,
  .site-header.is-stuck::before { background: var(--night-900); }
}

/* Increase Contrast asks for defined edges and near-solid surfaces. The
   translucency goes for the same reason as above, and every floating panel
   gains a border so it is obvious where it stops. */
@media (prefers-contrast: more) {
  .plate-mark, .glass, .zoom-open {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--night-900);
    border: 1px solid var(--on-night-3);
  }
  .nav-panel, .sheet { border-color: var(--on-night-3); }
  /* Text that sits at a muted grey for tone goes back to full strength. */
  .lead, .fine, .note p { color: var(--ink); }
  .night .lead, .night .fine, .hero .lead { color: var(--on-night); }
}

/* Windows High Contrast replaces the palette wholesale. The one thing that
   genuinely breaks there is a shape drawn as a background colour, because
   the background is thrown away — so the rail dots, which are pure colour,
   are given a border to survive it. */
@media (forced-colors: active) {
  .rail-dot::before { border: 1px solid currentColor; }
  .rail-dot.is-on::before { background: currentColor; }
  .btn { border: 1px solid currentColor; }
}
