/* ============================================================
   RAW & HUNGRY — NULL-bot companion
   ------------------------------------------------------------
   Pixel-art mascot that trails the cursor on pointer devices and
   walks a drawn SVG track on touch devices.

   The sprite is a horizontal PNG strip of 44x68 logical pixels
   rendered at 6x (264x408 per cell) — see
   tools/rh-null-bot/rh_null_bot_gen.py and the generated
   assets/sprites/null-bot/null-bot.json manifest.

   Display sizes are exact integer divisions of that 6x master
   (6 -> 2 on desktop, 6 -> 1.5 on touch) so `pixelated` lands on
   a whole-pixel grid and the art stays crisp. Changing them to a
   non-divisor is what makes pixel art look furry.

   Frame advance is driven from JS (assets/js/rh-null-bot.js) via
   --rh-bot-frame rather than a CSS steps() animation: states have
   different frame counts and rates, one-shots must finish, and the
   `look` sheet is a pose grid indexed by direction, not a loop.
   ============================================================ */

.rh-null-bot {
  --rh-bot-w: 88px;
  --rh-bot-h: 136px;
  --rh-bot-frame: 0;

  position: fixed;
  top: 0;
  left: 0;
  z-index: 85;              /* over the orbit field (90 is the scrollbar rail) */
  width: var(--rh-bot-w);
  height: var(--rh-bot-h);
  pointer-events: none;     /* never intercept a click meant for the page */
  opacity: 0;
  /* Feet anchor. JS positions the mascot by its bottom centre and composes any
     scale into the same transform, so scaling grows it upward from the floor
     instead of outward from its middle. With the default 50% 50% origin a
     scaled-up sprite overhangs the viewport edge it was just clamped inside —
     which is how it ended up clipping its own head against the top. */
  transform-origin: 50% 100%;
  transform: translate3d(-999px, -999px, 0);
  transition: opacity .45s ease;
  will-change: transform;
  /* layout + style only — `contain: paint` would clip the sprite's
     drop-shadow, and `size` is pointless next to the explicit box. */
  contain: layout style;
}

.rh-null-bot.is-visible { opacity: 1; }

.rh-null-bot__sprite {
  width: 100%;
  height: 100%;
  background-repeat: no-repeat;
  /* Scale the strip by height; each cell then lands exactly --rh-bot-w wide. */
  background-size: auto var(--rh-bot-h);
  background-position: calc(var(--rh-bot-frame) * -1 * var(--rh-bot-w)) 0;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  /* Grounds the mascot against the page without a drawn shadow sprite. */
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.55));
}

/* ---------- Runtime-rendered sprite ----------
   With assets/js/rh-null-bot-sprite.js present the sprite is a <canvas> whose
   pixels are generated per frame, so none of the sheet mechanics above apply:
   there is no strip to offset into and no cell width to line up. The canvas
   carries its own intrinsic size (the logical grid times an integer scale
   chosen from the element size and devicePixelRatio) and is laid into the same
   CSS box, which is why nothing here is ever resampled off a fixed asset. */
.rh-null-bot.is-runtime .rh-null-bot__sprite {
  background: none;
  display: block;
}

.rh-null-bot__screen-overlay {
  position: absolute;
  top: 18%;
  left: 15%;
  width: 70%;
  height: 22%;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s ease, font-size .15s ease;
  font-family: monospace, sans-serif;
  font-size: 10px;
  font-weight: 700;
  color: var(--ember, #FF4A1C);
  text-transform: uppercase;
  letter-spacing: .04em;
  text-shadow: 0 0 5px rgba(255, 74, 28, 0.85);
  z-index: 2;
  overflow: visible;
  white-space: nowrap;
  text-align: center;
}

.rh-null-bot.has-snippet .rh-null-bot__screen-overlay {
  opacity: 1;
}

/* ---------- Chat Speech Bubble (GPT-Taste & High-End Visual Design) ---------- */
/* ---------- Chat bubble ----------
   Beside the mascot, never over it. Centred above the head reads as a thought
   balloon but it sits exactly where the sprite's own antenna and screen are, so
   any bubble tall enough to wrap covered the robot — and near the top of the
   viewport it landed on the page's own header text. Anchoring it to the side
   with a gap makes the separation structural: `right: calc(100% + GAP)` cannot
   overlap the box it is measured from, whatever the text does.

   --rh-bot-bubble-gap is the breathing room between bubble and sprite. It
   scales with the element, so a pinned (enlarged) bot keeps the same optical
   gap. */
.rh-null-bot {
  --rh-bot-bubble-gap: 14px;
}

.rh-null-bot__chat-bubble {
  position: absolute;
  right: calc(100% + var(--rh-bot-bubble-gap));
  left: auto;
  /* Level with the CRT rather than the box: the screen centre sits at ~49% of
     the sprite's height, so this puts the text where the robot is "speaking"
     from. */
  top: 49%;
  transform: translateY(-50%) translateX(8px) scale(0.92);
  transform-origin: right center;
  background: rgba(11, 10, 9, 0.94);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 74, 28, 0.4);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.65), inset 0 1px 1px rgba(255, 255, 255, 0.15);
  border-radius: 12px;
  padding: 8px 14px;
  color: #EDE7DC;
  font-family: monospace, sans-serif;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: .03em;
  white-space: normal;
  /* Explicit, because shrink-to-fit has nothing to work with here: an absolutely
     positioned box anchored off `right: 100%` gets an available width of about
     zero, so it collapses to min-width and wraps one or two words per line.
     max-content sizes it to the sentence and lets max-width do the wrapping. */
  width: max-content;
  max-width: 220px;
  min-width: 90px;
  text-align: left;
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s cubic-bezier(0.16, 1, 0.3, 1), transform .3s cubic-bezier(0.16, 1, 0.3, 1);
  z-index: 10;
}

.rh-null-bot.has-chat .rh-null-bot__chat-bubble {
  opacity: 1;
  transform: translateY(-50%) translateX(0) scale(1);
}

/* The global pre-footer and homepage (05) CTAs use the mascot as a silent
   graphic centred on their titles. Keep the bubble suppressed defensively even
   if another interaction target was active on the preceding frame. */
.rh-null-bot.is-centered-cta-pin .rh-null-bot__chat-bubble {
  display: none !important;
}

/* Tail points at the robot — right edge by default. */
.rh-null-bot__chat-bubble::after {
  content: '';
  position: absolute;
  right: -6px;
  top: 50%;
  transform: translateY(-50%) rotate(45deg);
  width: 10px;
  height: 10px;
  background: rgba(11, 10, 9, 0.94);
  border-top: 1px solid rgba(255, 74, 28, 0.4);
  border-right: 1px solid rgba(255, 74, 28, 0.4);
}

/* ---------- Bubble on the trailing side ----------
   The mascot follows the cursor anywhere, so on the left of the viewport there
   is no room for a bubble to its left. JS adds this class when the space runs
   out; everything mirrors, including the tail. */
.rh-null-bot.is-bubble-right .rh-null-bot__chat-bubble {
  left: calc(100% + var(--rh-bot-bubble-gap));
  right: auto;
  transform: translateY(-50%) translateX(-8px) scale(0.92);
  transform-origin: left center;
}

.rh-null-bot.is-bubble-right.has-chat .rh-null-bot__chat-bubble {
  transform: translateY(-50%) translateX(0) scale(1);
}

.rh-null-bot.is-bubble-right .rh-null-bot__chat-bubble::after {
  right: auto;
  left: -6px;
  border-top: none;
  border-right: none;
  border-bottom: 1px solid rgba(255, 74, 28, 0.4);
  border-left: 1px solid rgba(255, 74, 28, 0.4);
}

/* ---------- Call to action ----------
   The bubble on a primary button. Deliberately animates box-shadow and
   border-colour only: `transform` belongs to the enter transition and the
   left/right flip above, and an animation on it would win over both — the
   bubble would snap back to the wrong side mid-pulse. */
.rh-null-bot.is-cta .rh-null-bot__chat-bubble {
  border-color: rgba(255, 74, 28, 0.85);
  animation: rhNullBotCtaGlow 1.9s ease-in-out infinite;
}

@keyframes rhNullBotCtaGlow {
  0%, 100% {
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.65),
                0 0 0 0 rgba(255, 74, 28, 0.30),
                inset 0 1px 1px rgba(255, 255, 255, 0.15);
  }
  50% {
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.65),
                0 0 0 5px rgba(255, 74, 28, 0.10),
                inset 0 1px 1px rgba(255, 255, 255, 0.15);
  }
}

/* Points down at the button the mascot is standing on. */
.rh-null-bot__chat-cta {
  display: inline-block;
  margin-left: 6px;
  color: var(--ember, #FF4A1C);
  font-weight: 700;
  animation: rhNullBotCtaNudge 1.1s ease-in-out infinite;
}

@keyframes rhNullBotCtaNudge {
  0%, 100% { transform: translateY(0); opacity: .55; }
  50%      { transform: translateY(3px); opacity: 1; }
}

.rh-null-bot__chat-dots {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: 6px;
  vertical-align: middle;
}

.rh-null-bot__chat-dots span {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ember, #FF4A1C);
  animation: rhNullBotDotPulse 1.2s infinite ease-in-out;
}

.rh-null-bot__chat-dots span:nth-child(2) { animation-delay: 0.2s; }
.rh-null-bot__chat-dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes rhNullBotDotPulse {
  0%, 80%, 100% { opacity: 0.2; transform: scale(0.8); }
  40% { opacity: 1; transform: scale(1.2); }
}

/* ---------- Touch track ----------
   Full-viewport overlay carrying the line the bot walks. Kept dim and
   dashed so it reads as an ambient guide rather than page furniture. */
.rh-null-bot-track {
  position: fixed;
  inset: 0;
  z-index: 84;
  pointer-events: none;
  overflow: visible;
}

.rh-null-bot-track__base,
.rh-null-bot-track__progress {
  fill: none;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.rh-null-bot-track__base {
  stroke: var(--line-2, rgba(237, 231, 220, 0.18));
  stroke-width: 1;
  stroke-dasharray: 2 7;
}

/* The stretch already travelled, lit in the accent. Its dashoffset is
   written per frame by JS to trace in behind the bot. */
.rh-null-bot-track__progress {
  stroke: var(--ember, #FF4A1C);
  stroke-width: 1.5;
  opacity: 0.55;
}

/* ---------- Track line visibility ----------
   `stroke: none` rather than `display: none` on the <svg>, or dropping the
   element: the path still has to be measurable. The bot's position along the
   track comes from getPointAtLength() on that same path every frame, so the
   geometry must stay in the document — only the paint goes away. Nothing is
   rasterised for a stroke of `none`, so this costs nothing.

   Toggled by assets/js/rh-null-bot.js from RH_NULL_BOT.showTrack or
   <body data-rh-null-bot-track="off">. */
.rh-null-bot-track.is-line-hidden .rh-null-bot-track__base,
.rh-null-bot-track.is-line-hidden .rh-null-bot-track__progress {
  stroke: none !important;
  opacity: 0 !important;
}

/* Solid variant of the guide line, for RH_NULL_BOT.trackStyle = 'solid'.
   The lit progress stretch is already solid; this renders the base path as a crisp solid border line. */
.rh-null-bot-track.is-solid .rh-null-bot-track__base {
  stroke: var(--line-2, rgba(237, 231, 220, 0.25));
  stroke-dasharray: none !important;
  stroke-width: 1.5px;
}

/* ---------- Touch / small viewports ----------
   Two selectors, same values, on purpose. The media query is the progressive
   one — correct on a real touch device before any JS runs. `.is-track` is set
   by the follower once it has picked its mode, which also covers a forced mode
   (RH_NULL_BOT.mode) on a hover-capable device, where the media query would
   otherwise leave the sprite at desktop size while it walks the track. */
@media (hover: none), (pointer: coarse) {
  .rh-null-bot {
    --rh-bot-w: 66px;
    --rh-bot-h: 102px;
  }
}

.rh-null-bot.is-track {
  --rh-bot-w: 66px;
  --rh-bot-h: 102px;
}

@media (max-width: 600px) {
  .rh-null-bot,
  .rh-null-bot.is-track {
    --rh-bot-w: 55px;
    --rh-bot-h: 85px;
  }
  .rh-null-bot-track__base { stroke-dasharray: 2 6; }
}

/* ---------- Reduced motion ----------
   JS already skips the follow loop and parks a single static frame; this
   removes the fade as well and drops the track entirely. */
@media (prefers-reduced-motion: reduce) {
  .rh-null-bot { transition: none; }
  .rh-null-bot-track { display: none; }
  /* The JS already parks the mascot and never pins, so no bubble should exist
     here — but a looping glow and a bobbing arrow are exactly what this query
     is for, and they must not survive on the strength of that assumption. */
  .rh-null-bot.is-cta .rh-null-bot__chat-bubble,
  .rh-null-bot__chat-cta,
  .rh-null-bot__chat-dots span { animation: none; }
}

/* Never render inside the Elementor canvas — it would be draggable furniture
   in the editor and confuse the widget hit-testing. */
body.elementor-editor-active .rh-null-bot,
body.elementor-editor-active .rh-null-bot-track,
.elementor-editor-preview .rh-null-bot,
.elementor-editor-preview .rh-null-bot-track { display: none; }
