/* ==========================================================================
   Enerzinx — ENE Full Website
   Rebuilt 1:1 from Figma "ENE - Full Website" (page CR 2).
   Canvas width 1728px. Every coordinate below is taken from the Figma export.
   ========================================================================== */

/* --- design tokens ------------------------------------------------------ */
:root {
  --orange:  #F35307;
  --orange2: #FF5200;
  --yellow:  #FEFF04;
  --ink:     #060606;
  --paper:   #F4F4F4;
  --slate:   #25292C;
  --grey:    #515151;
  --hairline:#E5E5E5;

  --brand-gradient: linear-gradient(90deg, #F35307 0%, #FEFF04 100%);

  --page: 1728px;   /* Figma frame width */
}

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

html { background: var(--ink); }

body {
  margin: 0;
  font-family: Geist, system-ui, -apple-system, sans-serif;
  color: var(--paper);
  background: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
}

/* Full-bleed canvas. Bands span the whole viewport width; the content grid
   stays 1728px wide and is centred by --gutter, so on a wider screen the
   backgrounds, video and grid reach the edges while nothing reflows.
   Below 1728px the gutter is 0 and the page behaves as a fixed canvas. */
.page {
  width: 100%;
  min-width: var(--page);    /* narrow viewports scroll, they do not squash */
  margin: 0;
  position: relative;
}

/* Percentages here resolve against the band, whose width is the page width,
   so this is the same value everywhere and excludes the scrollbar. */
.sec, .navbar, form { --gutter: max(0px, calc((100% - 1728px) / 2)); }

/* --- sections ----------------------------------------------------------- */
/* Each section is a positioned band of the exact Figma height, stacked in
   document order. Children carry absolute Figma coordinates; see --top below. */
/* Every coordinate in the markup is the ABSOLUTE Figma frame coordinate, so it
   can be diffed straight against the export. Each band declares its own --top
   and the placement rules below subtract it. */
.sec {
  position: relative;
  width: 100%;
  overflow: visible;         /* the turbine art deliberately breaks its band */
  --top: 0;
}

/* A <nav> stays static so its absolutely positioned children resolve against
   the section, not the nav box. */
.navbar { position: static; }

/* Full-bleed fills layered inside a band (background, photo, grid, scrim). */
.fill {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* The section grid overlays. Figma draws these as an image-fill pattern: a
   245.22 tile with a 5px cross at its centre, scaled by patternTransform. As
   two repeating gradients they tile to any width with no seam and no
   distortion, which a stretched 1728px SVG could not do.
   --gt tile, --glw line width, --gpx/--gpy phase, --gc stroke.  */
.grid {
  background-image:
    linear-gradient(to right,  var(--gc) 0 calc(var(--glw) * 1px), transparent calc(var(--glw) * 1px)),
    linear-gradient(to bottom, var(--gc) 0 calc(var(--glw) * 1px), transparent calc(var(--glw) * 1px));
  background-size: calc(var(--gt) * 1px) 100%, 100% calc(var(--gt) * 1px);
  background-repeat: repeat-x, repeat-y;
  /* Phased from the viewport edge, not the content grid: a percentage in
     background-position resolves against the image, not the box, so --gutter
     cannot be used here. A tiling grid reads the same either way. */
  background-position:
    calc(var(--gpx) * 1px) 0,
    0 calc(var(--gpy) * 1px);
  pointer-events: none;
}
.fill-img   { object-fit: cover; }

/* --- typography --------------------------------------------------------- */
/* Figma reports a text run's first BASELINE; CSS positions the box top, so
   every run is placed at `baseline - firstBaselineOffset`.

   That offset is NOT (L - S*(asc+desc))/2 + S*asc: Chrome rounds the font's
   ascent/descent when it builds a line box, so the real offset always lands on
   a whole pixel and the formula drifts up to 0.7px at small sizes. The --bo
   values below are therefore measured from the loaded webfonts, per
   (family, font-size, line-height) pair — see _figma_export/measure2.html. */
.t {
  position: absolute;
  margin: 0;
  font-weight: 400;
  white-space: nowrap;
  font-size:   calc(var(--fs) * 1px);
  line-height: calc(var(--lh) * 1px);
  left: calc(var(--x) * 1px + var(--gutter));
  top:  calc((var(--b) - var(--top) - var(--bo)) * 1px);
}

.mono { font-family: "Geist Mono", ui-monospace, "SFMono-Regular", monospace; }
.sans { font-family: Geist, system-ui, -apple-system, sans-serif; }

/* Figma text styles, as published in the file's Scale / Utility styles. */
.display     { --bo: 78; --fs: 86; --lh: 95;  letter-spacing: -0.03em; }
.h1          { --bo: 62; --fs: 69; --lh: 76;  letter-spacing: -0.03em; }
.h2          { --bo: 50; --fs: 55; --lh: 61;  letter-spacing: -0.03em; }
.h3          { --bo: 37; --fs: 44; --lh: 44;  letter-spacing: -0.03em; }
.h4          { --bo: 30; --fs: 35; --lh: 35;  letter-spacing: -0.03em; }
.h5          { --bo: 24; --fs: 28; --lh: 28;  letter-spacing: -0.03em; }
.h6          { --bo: 19; --fs: 23; --lh: 23;  letter-spacing: -0.03em; }
.body        { --bo: 20; --fs: 18; --lh: 27;  letter-spacing: -0.01em; }
.body-lg     { --bo: 23; --fs: 22; --lh: 31;  letter-spacing: -0.01em; }
.body-small  { --bo: 16; --fs: 14; --lh: 22;  letter-spacing: -0.01em; }
.footer-txt  { --bo: 12; --fs: 14; --lh: 14;  letter-spacing: -0.01em; }
.eyebrow     { --bo: 15; --fs: 18; --lh: 18;  letter-spacing: 0.1em; }
.label       { --bo: 15; --fs: 18; --lh: 18;  letter-spacing: 0.1em; }
.stat        { --bo: 41; --fs: 48; --lh: 48;  letter-spacing: -0.02em; font-weight: 500; }
.nav-cta     { --bo: 13; --fs: 16; --lh: 16;  letter-spacing: -0.03em; font-weight: 700; }
.btn-lg      { --bo: 15; --fs: 18; --lh: 18;  letter-spacing: -0.03em; font-weight: 700; }

/* Gradient-filled type. --gw is the gradient's span in px, matching the
   userSpaceOnUse gradient width in the Figma file. */
.gt {
  background: var(--brand-gradient);
  background-size: calc(var(--gw) * 1px) 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* --- links / buttons ---------------------------------------------------- */
a { color: inherit; text-decoration: none; }

.btn {
  position: absolute;
  display: block;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width:  calc(var(--w) * 1px);
  height: calc(var(--h) * 1px);
  background: var(--brand-gradient);
}
.btn-dark { background: var(--ink); }

/* "Learn More ↗" tertiary link: label plus a 10x10 arrow, both placed
   exactly where Figma puts them. */
.tertiary { color: var(--paper); }
.tertiary.on-light { color: var(--ink); }

.arrow {
  position: absolute;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width:  10px;
  height: 10px;
  display: block;
}

/* --- art placement ------------------------------------------------------ */
.art {
  position: absolute;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width:  calc(var(--w) * 1px);
  height: calc(var(--h) * 1px);
  display: block;
}
.art-cover { object-fit: cover; }
.flip-x    { transform: scaleX(-1); }

/* Hairline rules and solid blocks pulled straight from the export. */
.rule, .block {
  position: absolute;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width:  calc(var(--w) * 1px);
  height: calc(var(--h) * 1px);
}
.block-grad { background: var(--brand-gradient); }
.block-ink  { background: var(--ink); }
.block-orange { background: var(--orange); }
.outline-grey { border: 1px solid var(--grey); }

/* carousel dots */
.dot {
  position: absolute;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--ink);
}
.dot-dim { opacity: 0.3; }

/* --- contact form ------------------------------------------------------- */
/* Labels are Geist 16/500 #060606; inputs are 48px-tall #25292C blocks. */
.field-label {
  --bo: 13; --fs: 16; --lh: 16;
  font-weight: 500;
  color: var(--ink);
}
.field-input {
  position: absolute;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width:  calc(var(--w) * 1px);
  height: calc(var(--h) * 1px);
  background: var(--slate);
  border: 0;
  color: var(--paper);
  font: 400 16px/1 Geist, system-ui, sans-serif;
  letter-spacing: -0.01em;
  padding: 0 16px;
}
.field-input:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }

/* <button class="btn"> needs the UA chrome stripped so it matches the rect. */
button.btn { border: 0; padding: 0; cursor: pointer; -webkit-appearance: none; appearance: none; }

/* A <form> must not create a containing block for the absolute children. */
form { position: static; margin: 0; }

/* --- resources: filter dropdowns ---------------------------------------- */
.filter-label { --bo: 13; --fs: 16; --lh: 16; letter-spacing: 0; color: var(--paper); }
.chev {
  position: absolute;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width:  10.392px;
  height: 6px;
  display: block;
  color: var(--paper);
}

/* 20x20 gradient-filled arrow used by the Related Services cards */
.arrow-lg { width: 20px; height: 20px; }

/* Geist 18px set on a 32px line (About differentiators, Contact addresses,
   the article body, the Generator Interconnection columns). */
.lh32 { --lh: 32; --bo: 22; }

/* ==========================================================================
   Interaction
   Hover behaviour follows the design's own vocabulary: the brand gradient is
   the "active" surface, and text on it inverts to --ink. The card fill below
   is taken from the Services Hub hover state; the social icon swap uses the
   -orange icon variants shipped in the asset pack.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  a, .btn, .arrow, .art-cover, .field-input { transition: color .15s ease, border-color .15s ease,
                                               transform .18s ease, filter .15s ease; }
  .card                 { transition: background-image .15s ease; }
  .card .t, .card .block { transition: color .15s ease, opacity .15s ease; }
}

/* --- nav + breadcrumb --------------------------------------------------- */
.navbar a.nav-cta:hover { color: var(--orange); }
/* the Contact chip is already gradient; lift it instead of recolouring */
.navbar a.btn:hover { filter: brightness(1.08); transform: translateY(-1px); }

/* --- tertiary "Learn More / View" + its 10px arrow ---------------------- */
/* the label and the arrow are adjacent siblings, so one can drive the other */
a.tertiary:hover { color: var(--orange); }
a.tertiary:hover + .arrow { color: var(--orange); transform: translate(2px, -2px); }
.arrow:hover { color: var(--orange); }

/* --- primary buttons ---------------------------------------------------- */
a.btn:hover, button.btn:hover { filter: brightness(1.08); transform: translateY(-1px); }
a.btn:active, button.btn:active { transform: translateY(0); filter: brightness(.96); }

/* --- photographs -------------------------------------------------------- */
.art-cover:hover { transform: scale(1.02); filter: brightness(1.05); }

/* --- form --------------------------------------------------------------- */
.field-input:hover { box-shadow: inset 0 0 0 1px var(--grey); }

/* --- resources filter dropdowns ----------------------------------------- */
.filter:hover { border-color: var(--orange) !important; }
.filter:hover ~ .chev { color: var(--orange); }

/* --- footer ------------------------------------------------------------- */
/* 40x40 icon boxes; the pack ships an -orange twin of each */
.social {
  background-repeat: no-repeat;
  background-position: center;
  background-size: 40px 40px;
}
.social:hover { background-image: var(--hover-icon) !important; }
.footer-txt:hover { color: var(--orange); }

/* --- hoverable cards ---------------------------------------------------- */
/* A card wrapper is placed at the card's Figma rect. Its children keep their
   absolute frame coordinates and are re-based against the card via --cx/--cy,
   so the authored numbers still diff against the export. */
.card {
  position: absolute;
  display: block;
  left:   calc(var(--x) * 1px + var(--gutter));
  top:    calc((var(--y) - var(--top)) * 1px);
  width:  calc(var(--w) * 1px);
  height: calc(var(--h) * 1px);
  --cx: var(--x);
  --cy: var(--y);
  /* children below are placed relative to the card and never add --gutter,
     so the card itself must keep the inherited one. */
}
.card .t {
  left: calc((var(--x) - var(--cx)) * 1px);
  top:  calc((var(--b) - var(--cy) - var(--bo)) * 1px);
}
.card .arrow, .card .block, .card .rule, .card .art {
  left: calc((var(--x) - var(--cx)) * 1px);
  top:  calc((var(--y) - var(--cy)) * 1px);
}

/* Hover: the card becomes the brand gradient and its type inverts to ink.
   Inline colours have to be beaten, hence !important. */
.card:hover { background: var(--brand-gradient); }
.card:hover .t,
.card:hover .tertiary,
.card:hover .arrow      { color: var(--ink) !important; }
.card:hover .gt {
  background: none;
  color: var(--ink) !important;
  -webkit-text-fill-color: var(--ink);
}
/* the gradient underline is absorbed by the fill */
.card:hover .block-grad { opacity: 0; }
.card:hover .arrow      { transform: translate(2px, -2px); }

/* --- ring-node grid (brand-gradient bands) ------------------------------ */
/* The gradient bands use a different tile from the solid bands: a ring at
   each intersection with the cross lines stopping at the ring edge. The tile
   is an SVG in assets/patterns/, sized to the band's own pattern scale, with
   the ring at the tile centre so the tile origin is simply (tx, ty) mod tile. */
.grid-ring {
  background-size: calc(var(--gt) * 1px) calc(var(--gt) * 1px);
  background-repeat: repeat;
  background-position: calc(var(--gox) * 1px) calc(var(--goy) * 1px);
  pointer-events: none;
}
/* Declared here, not in a custom property: a url() inside a custom property
   resolves against the stylesheet that consumes it, which silently 404s. */
.ring-98  { background-image: url(../assets/patterns/ring-tile-98.svg); }
.ring-122 { background-image: url(../assets/patterns/ring-tile-122.svg); }

/* --- tertiary links: the arrow turns from ↗ to → ------------------------ */
/* the glyph is a north-east arrow, so +45deg points it due east */
a.tertiary:hover + .arrow,
.arrow:hover {
  color: var(--orange);
  transform: rotate(45deg) translateX(1.5px);
}

/* --- resource-card strips: link-only hover ------------------------------ */
/* These do NOT take the gradient fill — only "View" and its arrow change.
   Declared after the .card rules so it wins the tie on specificity. */
.card-link:hover { background: none; }
.card-link:hover .t            { color: var(--paper) !important; }
.card-link:hover .tertiary,
.card-link:hover .arrow        { color: var(--orange) !important; }
.card-link:hover .block-grad   { opacity: 1; }
.card-link:hover .arrow        { transform: rotate(45deg) translateX(1.5px); }
.card-link:hover .gt {
  background: var(--brand-gradient);
  -webkit-text-fill-color: transparent;
  color: transparent !important;
}

/* --- tertiary hover: the label becomes gradient type -------------------- */
/* Not solid orange. The ramp spans the label's own width, so a short label
   like "View" and a long one like "Generator Interconnection" each carry a
   full orange→yellow sweep, as the prototype shows. */
a.tertiary:hover,
.card-link:hover .tertiary {
  background: var(--brand-gradient);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent !important;
  -webkit-text-fill-color: transparent;
}
/* the 10px arrow is too narrow to read as a ramp, so it takes the ramp's
   starting colour */
a.tertiary:hover + .arrow,
.card-link:hover .arrow { color: var(--orange) !important; }

/* ── capability index rail ─────────────────────────────────────────────
   Each rail entry on a service page is an anchor to its matching capability
   heading. This affects only scroll anchoring, never static layout, so the
   pixel verification is untouched: the heading would otherwise land flush
   against the top edge of the viewport. The page nav is position:static, so
   this offset is the only allowance needed.

   Deliberately no `scroll-behavior: smooth`. Smooth scrolling is driven by
   animation frames, so it silently does nothing wherever those are throttled
   (a background or hidden tab, some embedded webviews): the hash changes and
   the page never moves. The instant jump always works, and the design never
   asked for an animated scroll. */
[id^="cap-"] { scroll-margin-top: 40px; }

/* A related-services card is an opaque ink panel, so the gradient card fill
   would be painted behind it and never seen -- card-link cancels that fill.
   The affordance is on the title instead, using the same gradient-label
   treatment the tertiary links use. */
.card-related:hover .t {
  background: var(--brand-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent !important;
  -webkit-text-fill-color: transparent;
}

/* ── pinned capability rail ────────────────────────────────────────────
   js/rail.js drives --rail-shift so the rail stays put while the capability
   column scrolls past it, per the Figma annotation on the service-page
   template. A transform is used rather than position:sticky because every
   element here is absolutely positioned, and sticky has no effect on an
   absolutely positioned box. The shift is 0 at the top of the document, so
   this changes nothing about the verified layout. */
.rail-panel,
.rail-nav a { transform: translateY(var(--rail-shift, 0px)); }

/* the global anchor transition covers transform; left in place the rail would
   ease along a frame behind the scroll position */
.rail-nav a { transition: color .15s ease; }

/* The export paints entry 1 orange and the rest paper, inline. Those inline
   colours stay as the no-JS baseline; once rail.js is running it owns the
   highlight, which is why these need !important to outrank them. */
.rail-nav.rail-live a { color: var(--paper) !important; }
.rail-nav.rail-live a.is-active,
.rail-nav.rail-live a:hover { color: var(--orange) !important; }

/* ── modeling wheel (About, "Our Approach") ────────────────────────────
   The wheel is inline SVG rather than <img> so that Geist Mono actually
   applies to its labels: an SVG loaded through <img> is font-isolated, and
   the whole graphic was rendering in a fallback serif. The right-hand panel
   is ordinary HTML for the same reason, and because a five-chip tool row is
   wider than the SVG's viewBox. Sizes are the export's, so they are
   fractional; the --bo values are measured, not derived. */
.mw-eyebrow { --fs:14.9294; --lh:14.9294; --bo:12;
              font-weight:500; letter-spacing:2.48823px; color:#9EA3AD }
.mw-title   { --fs:49.7646; --lh:54; --bo:44;
              font-weight:600; letter-spacing:0.622058px; color:var(--paper) }
.mw-desc    { --fs:19.9058; --lh:30; --bo:22; color:#A8ADB8 }
.mw-chip-t  { --fs:14.9294; --lh:14.9294; --bo:12;
              font-weight:500; color:var(--paper) }

/* Figma strokes straddle the edge, so the chip box is the fill box grown by
   half the stroke on each side and drawn with border-box sizing. */
.mw-chip { background:#1A1A1E; border:1.24412px solid #33343A;
           box-sizing:border-box }
.mw-dot  { background:var(--orange); border-radius:50% }

.mw-wheel { overflow:visible }          /* let the wedge glow spill */

/* Only one wedge shows at a time; cross-fading opacity animates everywhere,
   whereas transitioning a path's `d` does not. */
.mw-wedge { opacity:0; transition:opacity .28s ease }
.mw-wedge.is-on { opacity:1 }

.mw-hit { cursor:pointer }
.mw-hit:focus { outline:none }
.mw-hit:focus-visible { outline:2px solid var(--yellow); outline-offset:-4px }

/* The panel fades its new content in on each change. Opacity only, and no
   fill mode: on a pixel-exact page an animation that also moves the text can
   strand it off its baseline if the animation is not advancing (a throttled
   background tab), and `both` would hold the from-state indefinitely. The
   class is added only on a real change, never on the initial paint, so the
   default state renders exactly as designed. */
.mw-panel.is-swapping .t { animation:mw-in .28s ease }
@keyframes mw-in { from { opacity:0 } }

@media (prefers-reduced-motion: reduce) {
  .mw-wedge { transition:none }
  .mw-panel.is-swapping .t { animation:none }
}
