/* =========================================================
   FRAMELAB · Renewable Intelligence
   The one page in the site that is not a service page.
   Dark, indigo tinted, contemplative. Loaded only here.
   ========================================================= */

:root {
  --ri-bg:      #0E0A18;   /* deep indigo, a shade off pure ink */
  --ri-bg-2:    #140E24;
  --ri-head:    #FFFFFF;
  --ri-text:    rgba(255, 255, 255, 0.80);
  --ri-dim:     rgba(255, 255, 255, 0.46);
  --ri-rule:    rgba(255, 255, 255, 0.12);
  --ri-human:   #E0A878;   /* warm, human intelligence */
  --ri-ai:      #8FB8E8;   /* cool, artificial intelligence */
  --ri-brand:   #A78BFA;   /* Framelab, the brand violet on dark */
}

body.ri {
  background: var(--ri-bg);
  color: var(--ri-text);
}

/* the page sits above the breathing loop.
   Scoped to the content only: the header and the mobile menu keep
   their own fixed positioning from styles.css. */
body.ri main,
body.ri .site-footer { position: relative; z-index: 1; }

/* atmosphere: two soft violet pools, no hard gradients */
body.ri::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 70% 45% at 50% 0%,   rgba(139, 92, 246, 0.16), transparent 62%),
    radial-gradient(ellipse 60% 50% at 50% 100%, rgba(224, 168, 120, 0.07), transparent 60%);
}

/* header and footer adapt to the dark surface */
body.ri header {
  background: rgba(14, 10, 24, 0.72);
  border-bottom-color: var(--ri-rule);
}
body.ri .wordmark { color: var(--ri-head); }
body.ri .mark .fl-black  { fill: #FFFFFF; }
body.ri .mark .fl-purple { fill: var(--ri-brand); }
body.ri .wordmark .reg { color: var(--ri-brand); }
body.ri .nav-links a { color: rgba(255, 255, 255, 0.66); }
body.ri .nav-links a:hover,
body.ri .nav-links a[aria-current="page"] { color: var(--ri-head); }
body.ri .lang-toggle { color: rgba(255,255,255,0.55); border-color: var(--ri-rule); }
body.ri .lang-toggle:hover { color: #fff; border-color: rgba(255,255,255,0.4); }
body.ri .nav-toggle span { background: #fff; }
body.ri .mobile-menu { background: var(--ri-bg); }
body.ri .mobile-menu a { color: var(--ri-head); border-bottom-color: var(--ri-rule); }
body.ri .site-footer { border-top-color: var(--ri-rule); background: transparent; }
body.ri .footer-row { border-bottom-color: var(--ri-rule); }
body.ri .footer-nav a, body.ri .footer-tel { color: rgba(255,255,255,0.62); }
body.ri .footer-nav a:hover, body.ri .footer-tel:hover { color: #fff; }
body.ri .footer-mail { color: var(--ri-brand); }
body.ri .footer-bar span, body.ri .footer-bar .label { color: rgba(255,255,255,0.42); }

/* =========================================================
   VISUAL 1 · the breathing loop
   Two lines, warm and cool, spiralling around each other.
   The path tiles every 400 units so the drift never seams.
   ========================================================= */
.ri-loop {
  position: fixed;
  top: 0; left: 50%;
  /* --ri-converge is written by scroll.js: 1 is the authored width,
     smaller values braid the two strands together as you read. */
  transform: translateX(-50%) scaleX(var(--ri-converge, 1));
  transform-origin: 50% 50%;
  will-change: transform;
  width: min(760px, 96vw);
  height: 100vh;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.ri-loop svg { width: 100%; height: 300%; display: block; }
.ri-loop path { fill: none; stroke-width: 1.6; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.ri-loop .h { stroke: var(--ri-human); opacity: calc(0.26 * var(--ri-strength, 1)); animation: riDrift 44s linear infinite, riBreatheA 19s ease-in-out infinite; }
.ri-loop .a { stroke: var(--ri-ai);    opacity: calc(0.27 * var(--ri-strength, 1)); animation: riDrift 44s linear infinite, riBreatheB 23s ease-in-out infinite; }

@keyframes riDrift    { from { transform: translateY(0); }        to { transform: translateY(-400px); } }
@keyframes riBreatheA { 0%,100% { stroke-width: 1.9; }  50% { stroke-width: 2.9; } }
@keyframes riBreatheB { 0%,100% { stroke-width: 2.6; }  50% { stroke-width: 1.8; } }

/* the drift and the breath live on different elements so they compose */
.ri-loop g.drift { animation: riDrift 44s linear infinite; }

/* =========================================================
   VISUAL 3 · the circle
   One line. Two points, different speeds, always approaching,
   never catching.
   ========================================================= */
.ri-circle { display: block; }
.ri-circle circle.ring { fill: none; stroke: rgba(255,255,255,0.22); stroke-width: 1; }
.ri-circle .orb-h, .ri-circle .orb-a { transform-origin: 50% 50%; }
.ri-circle .orb-h { animation: riSpin 13s linear infinite; }
.ri-circle .orb-a { animation: riSpin 17s linear infinite reverse; }
.ri-circle .orb-h circle { fill: var(--ri-human, #E0A878); }
.ri-circle .orb-a circle { fill: var(--ri-ai, #8FB8E8); }
@keyframes riSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* =========================================================
   THE PAGE
   ========================================================= */
.ri-page { padding-top: calc(var(--nav-h) + 90px); padding-bottom: 40px; }
.ri-col { max-width: 680px; margin: 0 auto; padding: 0 24px; }

.ri-open { text-align: center; }
.ri-open .ri-circle { margin: 0 auto 26px; }

.ri-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(38px, 7.6vw, 88px);
  line-height: 0.94;
  letter-spacing: -0.045em;
  color: var(--ri-head);
  margin: 0;
  text-wrap: balance;
}
.ri-sub {
  font-family: var(--display);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--ri-dim);
  margin-top: 22px;
}

.ri-rule {
  width: 1px;
  height: 40px;
  margin: 26px auto 0;
  background: linear-gradient(to bottom, transparent, var(--ri-rule), transparent);
}

/* the text itself */
.ri-body { margin-top: 26px; }
.ri-body p {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.65;
  color: var(--ri-text);
  margin: 0 0 16px;
  text-wrap: pretty;
}
.ri-body p:last-child { margin-bottom: 0; }
.ri-body strong { font-weight: 600; color: #fff; }

/* short declarative lines carry the argument, so they stand alone */
.ri-body p.beat {
  text-align: center;
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: -0.03em;
  font-size: clamp(24px, 3.4vw, 33px);
  line-height: 1.22;
  color: var(--ri-head);
  margin: 40px 0;
  max-width: 20ch;
  margin-left: auto;
  margin-right: auto;
}
.ri-body p.beat strong { font-weight: 700; }

/* the single moment the brand names itself */
.ri-brand {
  color: var(--ri-brand);
  font-weight: 700;
  font-size: 1.14em;
  letter-spacing: -0.02em;
}

/* =========================================================
   VISUAL 2 · the words in dialogue
   ---------------------------------------------------------
   In the versus sentence the two phrases trade colour outright
   on a 3s loop: warm crosses to cool as cool crosses to warm,
   so the exchange is impossible to miss.

   Every other appearance breathes on a 2s loop, moving between
   a deeper and a brighter version of its own colour. Neither
   ever passes through white or drops out of view.
   ========================================================= */
.hi-h, .hi-a {
  font-weight: 600;
  will-change: color;
}

/* ---- the exchange: text and colour together ---- */
.hi-swap {
  display: inline-block;
  white-space: nowrap;
  font-weight: 600;
  transition: opacity 700ms ease-in-out,
              filter  700ms ease-in-out,
              color   900ms ease-in-out,
              width   700ms ease-in-out;
}
/* colour follows whichever reading is currently showing */
.hi-swap[data-showing="human"] { color: #E8A76F; text-shadow: 0 0 16px rgba(232, 167, 111, 0.28); }
.hi-swap[data-showing="ai"]    { color: #7FB4EE; text-shadow: 0 0 16px rgba(127, 180, 238, 0.28); }

/* the melt: dims and softens, but never reaches zero */
.hi-swap.dissolving { opacity: 0.30; filter: blur(2px); }

/* ---- the breath, every other appearance ---- */
.hi-h:not(.hi-swap) { animation: riBreatheWarm 2s ease-in-out infinite; }
.hi-a:not(.hi-swap) { animation: riBreatheCool 2s ease-in-out infinite; }

/* deep to bright inside the same hue: clearly alive, never white */
@keyframes riBreatheWarm {
  0%, 100% { color: #B97C4C; text-shadow: none; }
  50%      { color: #FFCB98; text-shadow: 0 0 20px rgba(255, 203, 152, 0.55); }
}
@keyframes riBreatheCool {
  0%, 100% { color: #6489BE; text-shadow: none; }
  50%      { color: #B8DAFF; text-shadow: 0 0 20px rgba(184, 218, 255, 0.55); }
}

/* hover lifts either behaviour without interrupting it */
.hi-h, .hi-a { transition: filter 0.5s ease; }
.ri-body p:hover .hi-h,
.ri-body p:hover .hi-a { filter: brightness(1.15); }

/* closing */
.ri-close { margin-top: 88px; padding-bottom: 40px; text-align: center; }
.ri-close .ri-circle { margin: 0 auto 44px; }
.ri-close .btn {
  background: transparent;
  border-color: rgba(255,255,255,0.3);
  color: #fff;
}
.ri-close .btn:hover { background: rgba(255,255,255,0.07); border-color: #fff; }

@media (prefers-reduced-motion: reduce) {
  .ri-loop .h, .ri-loop .a, .ri-loop g.drift,
  .ri-circle .orb-h, .ri-circle .orb-a,
  .hi-h, .hi-a { animation: none; }
  .hi-swap { transition: none; }
  .hi-h { color: var(--ri-human); }
  .hi-a { color: var(--ri-ai); }
}

@media (max-width: 720px) {
  .ri-page { padding-top: calc(var(--nav-h) + 56px); }
  .ri-body { margin-top: 20px; }
  .ri-body p { margin-bottom: 14px; }
  .ri-body p.beat { margin: 34px auto; }
  .ri-rule { height: 28px; margin-top: 18px; }
  .ri-open .ri-circle { margin-bottom: 20px; }
  .ri-close { margin-top: 84px; }
}


/* =========================================================
   THE LOOP, RUN BY THE READER
   ---------------------------------------------------------
   The manifesto's central claim has never had a picture: the
   loop is continuous, compounding, self-renewing, and it does
   not deplete when it is used. So the reader's scroll drives a
   pulse around a closed circuit, crossing from the human half
   into the AI half and back, and every lap leaves the ring
   stronger. Stop scrolling and the loop stops: it runs because
   you engage with it, which is the other half of the claim.
   ========================================================= */
.ri-cycle {
  margin: 54px auto;
  width: min(300px, 62vw);
}
.ri-cycle svg { display: block; width: 100%; height: auto; overflow: visible; }

/* the two halves. They gain presence as laps accumulate: the
   ring is stronger at the end of the passage than at the start */
.ri-cycle .arc {
  fill: none;
  stroke-linecap: round;
  stroke-width: calc(1.7px + 1.5px * var(--cyc-gain, 0));
  opacity: calc(0.20 + 0.46 * var(--cyc-gain, 0));
}
.ri-cycle .arc--h { stroke: var(--ri-human); }
.ri-cycle .arc--a { stroke: var(--ri-ai); }

/* the travelling trail. Dash geometry is written by scroll.js in
   user units, so this must not use non-scaling-stroke: that would
   measure the dash in device space and the trail would never
   match the path it is running along. */
.ri-cycle .trail {
  fill: none;
  stroke: var(--ri-brand);
  stroke-linecap: round;
  stroke-width: calc(2.2px + 1.6px * var(--cyc-gain, 0));
  opacity: 0.55;
}

/* the head carries whichever intelligence it is currently passing
   through, so the crossing is legible rather than decorative */
.ri-cycle .head {
  transform: rotate(calc(var(--cyc-pos, 0) * 1turn));
  transform-origin: 50% 50%;
}
.ri-cycle .head circle {
  fill: var(--cyc-col, var(--ri-brand));
  r: calc(4.6px + 1.4px * var(--cyc-gain, 0));
}

/* never fully inert: a slow breath on its own clock, so a reader
   who has stopped scrolling still sees a living thing */
.ri-cycle .arc { animation: riCycleBreath 7s ease-in-out infinite; }
.ri-cycle .arc--a { animation-delay: -3.5s; }
@keyframes riCycleBreath { 0%,100% { filter: none; } 50% { filter: brightness(1.35); } }

@media (max-width: 720px) { .ri-cycle { margin: 40px auto; width: min(230px, 66vw); } }

@media (prefers-reduced-motion: reduce) {
  .ri-cycle .arc { animation: none; }
}
