/* Buttons */
.btn {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.05em;
  border: none;
  padding: 16px 28px;
  cursor: pointer;
  transition: transform 0.2s var(--ease-standard), filter 0.2s ease, border-color 0.2s ease;
  display: inline-block;
  text-decoration: none;
}

.btn--primary {
  background: var(--accent-gradient);
  color: var(--color-bg);
  font-weight: 600;
}

.btn--primary:hover {
  filter: brightness(1.15);
  color: var(--color-bg);
}

.btn--ghost {
  background: rgba(255, 255, 255, 0.03);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  color: var(--color-text);
  border: 1px solid rgba(255, 255, 255, 0.28);
}

.btn--ghost:hover {
  border-color: var(--accent-purple-strong);
  color: var(--color-text);
}

/* Magnetic wrapper: JS sets transform via inline style on mousemove */
.magnetic {
  will-change: transform;
}

/* Tags */
.tag {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--color-dim);
  border: 1px solid rgba(255, 255, 255, 0.12);
  padding: 8px 14px;
  display: inline-block;
}

/* Isotype mark — the SVG brand symbol, reusable wherever it's needed
   (currently just .nav__logo-mark, see layout.css). Injected via
   renderLogoMark() in main.js rather than duplicated as inline markup in
   index.html, so there's one source of truth for the path data.
   .edrhael-mark--animated plays the "drawing" stroke loop; without that
   modifier it just renders the static filled-in mark. */
.edrhael-mark {
  display: inline-flex;
  width: 100%;
  height: 100%;
}

.edrhael-mark svg {
  width: 100%;
  height: 100%;
  display: block;
}

.edrhael-mark__path {
  fill: none;
  stroke: currentColor;
  stroke-width: 16;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-miterlimit: 23;
}

.edrhael-mark--glow svg {
  filter: drop-shadow(0 0 4px rgba(160, 0, 160, 0.9)) drop-shadow(0 0 20px rgba(160, 0, 255, 0.65));
}

.edrhael-mark--animated .edrhael-mark__path {
  animation: edrhael-mark-draw 6s ease-in 0.25s both infinite reverse;
}

/* Total path length (measured with getTotalLength() in a browser against
   this exact "d" — recompute if the path data ever changes): the dash
   pattern is one dash the length of the whole path, so shifting its offset
   by a full length toggles between fully drawn and fully hidden. Goes
   slightly past a full cycle at 0% (1638 vs 1598) for a brief hold before
   it starts drawing in. */
@keyframes edrhael-mark-draw {
  0% {
    stroke-dashoffset: 1638px;
    stroke-dasharray: 1598px;
  }
  25% {
    stroke-dashoffset: 0;
    stroke-dasharray: 1598px;
  }
  50% {
    stroke-dashoffset: 0;
    stroke-dasharray: 1598px;
  }
  100% {
    stroke-dashoffset: -1598px;
    stroke-dasharray: 1598px;
  }
}

/* Language toggle — a switch (track + sliding thumb) with ES/EN labels
   flanking it. A single click target that flips between the two, like any
   on/off switch, rather than two independently clickable language buttons. */
.lang-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
  background: transparent;
  border: none;
  padding: 4px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
}

.lang-toggle__label {
  color: var(--color-faint);
  transition: color 0.2s ease;
}

.lang-toggle[aria-checked="false"] .lang-toggle__label--es,
.lang-toggle[aria-checked="true"] .lang-toggle__label--en {
  color: var(--color-text);
}

.lang-toggle__track {
  position: relative;
  width: 32px;
  height: 18px;
  flex-shrink: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--border-strong);
  transition: border-color 0.2s ease;
}

.lang-toggle:hover .lang-toggle__track {
  border-color: var(--accent-purple-strong);
}

.lang-toggle__thumb {
  position: absolute;
  top: 1px;
  left: 1px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent-gradient);
  transition: transform 0.25s var(--ease-standard);
}

.lang-toggle[aria-checked="true"] .lang-toggle__thumb {
  transform: translateX(14px);
}

/* Groups the language switch and the music toggle together so they read as
   one cluster at the end of the nav, regardless of how many items end up
   in it — matters once .nav__links goes into its mobile dropdown (see
   layout.css) and only two real items remain here. */
.nav__actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Music toggle — a single icon button (play/pause) plus a volume slider
   that's collapsed to width:0 until playback starts, so the nav stays as
   compact as the language switch by default and only grows once the
   music is actually on. */
.music-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
}

.music-toggle__btn {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: rgba(255, 255, 255, 0.04);
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}

.music-toggle__btn:hover {
  border-color: var(--accent-purple-strong);
  background: rgba(255, 255, 255, 0.08);
}

.music-toggle__icon {
  display: none;
  align-items: center;
  justify-content: center;
}

.music-toggle__btn[aria-pressed="false"] .music-toggle__icon--off,
.music-toggle__btn[aria-pressed="true"] .music-toggle__icon--on {
  display: flex;
}

.music-toggle__volume {
  width: 0;
  height: 3px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
  background: rgba(255, 255, 255, 0.15);
  border-radius: 999px;
  outline: none;
  cursor: pointer;
  transition: width 0.25s var(--ease-standard), opacity 0.2s ease;
}

.music-toggle.is-playing .music-toggle__volume {
  width: 48px;
  opacity: 1;
  pointer-events: auto;
}

/* Narrowest phones (e.g. ~350px) are already tight with logo + lang switch
   + music button — shrink the expanded slider rather than risk an overflow
   on top of everything else, without removing volume control entirely. */
@media (max-width: 420px) {
  .music-toggle.is-playing .music-toggle__volume {
    width: 36px;
  }
}

.music-toggle__volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent-gradient);
  cursor: pointer;
}

.music-toggle__volume::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--accent-purple-strong);
  cursor: pointer;
}

.music-toggle__volume::-moz-range-track {
  background: rgba(255, 255, 255, 0.15);
  border-radius: 999px;
  height: 3px;
}

.cap-panel {
  min-height: 280px;
  background: var(--surface-2);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  padding: clamp(24px, 3vw, 40px);
}

/* Sistemas propios (accordion) */
.sistema {
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  padding: 36px 0;
}

.sistema__toggle {
  width: 100%;
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
  display: grid;
  grid-template-columns: clamp(44px, 8vw, 80px) 1fr auto;
  align-items: baseline;
  gap: clamp(12px, 2vw, 24px);
  color: inherit;
}

.sistema__toggle:hover {
  color: var(--accent-cyan);
}

.sistema__num {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--accent-purple-strong);
}

.sistema__name {
  font-size: clamp(22px, 3.2vw, 44px);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.sistema__icon {
  font-family: var(--font-mono);
  font-size: 20px;
  color: var(--color-dim);
}

.sistema__tagline {
  font-size: 15px;
  color: var(--color-muted);
  margin: 12px 0 0 clamp(0px, 8vw, 104px);
  max-width: 600px;
}

.sistema__detail {
  margin: 32px 0 0 clamp(0px, 8vw, 104px);
  display: none;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 32px;
  padding: 28px clamp(16px, 3vw, 32px);
  background: var(--surface-1);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 4px;
  animation: panel-in 0.3s var(--ease-standard);
}

.sistema.is-expanded .sistema__detail {
  display: grid;
}

.sistema__detail-label {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--accent-cyan);
  letter-spacing: 0.1em;
  margin-bottom: 10px;
}

.sistema__detail-text {
  font-size: 14px;
  color: var(--color-text-soft);
  line-height: 1.6;
}

.sistema__detail-text--outcome {
  color: var(--color-faint);
  font-style: italic;
}

/* Experiencia timeline */
.timeline {
  margin-top: 64px;
  display: flex;
  flex-direction: column;
  border-left: 1px solid rgba(255, 255, 255, 0.12);
  padding-left: clamp(24px, 4vw, 48px);
  position: relative;
}

.timeline__item {
  position: relative;
  padding-bottom: 56px;
  opacity: 0;
  transform: translateX(-16px);
  transition: opacity 0.7s var(--ease-standard), transform 0.7s var(--ease-standard);
}

.timeline__item:last-child {
  padding-bottom: 0;
}

.timeline__item.is-visible {
  opacity: 1;
  transform: translateX(0);
}

.timeline__dot {
  position: absolute;
  left: calc(clamp(24px, 4vw, 48px) * -1 - 6px);
  top: 6px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--color-bg);
  border: 2px solid rgba(255, 255, 255, 0.2);
  transition: border-color 0.4s ease;
}

.timeline__item.is-visible .timeline__dot {
  border-color: var(--accent-purple-strong);
}

.timeline__num {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent-purple-strong);
  margin-bottom: 6px;
}

.timeline__title {
  font-size: clamp(19px, 2.2vw, 26px);
  font-weight: 600;
  margin: 0 0 8px;
}

.timeline__desc {
  font-size: 14px;
  color: var(--color-muted);
  max-width: 620px;
  line-height: 1.6;
}

/* Terminal */
.terminal-toggle {
  position: fixed;
  left: 20px;
  bottom: 20px;
  z-index: 120;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.05em;
  color: var(--color-text);
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255, 255, 255, 0.16);
  padding: 12px 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: border-color 0.2s ease, opacity 0.15s ease;
}

.terminal-toggle:hover {
  border-color: var(--accent-purple-strong);
}

/* Icon-only on mobile: the full "terminal" label made this button much
   wider than the plain menu button, which meant nothing could reliably
   center between them without fragile width guessing (see .section-index
   in layout.css) — narrower phones (~350px) still clipped even with a
   generous estimated offset. Matching the menu button's compact 36x36
   footprint here fixes that at its root instead of tuning more pixel
   values, at any width. */
@media (max-width: 760px) {
  .terminal-toggle {
    width: 36px;
    height: 36px;
    padding: 0;
    justify-content: center;
    gap: 0;
  }

  #terminal-toggle-label {
    display: none;
  }
}

.terminal-toggle__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent-cyan);
  box-shadow: 0 0 6px var(--accent-cyan);
}

.terminal {
  position: fixed;
  right: 20px;
  bottom: 76px;
  left: 20px;
  max-width: 440px;
  margin-right: auto;
  z-index: 120;
  height: min(52vh, 420px);
  background: rgba(14, 12, 17, 0.82);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--border-soft);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  display: none;
  flex-direction: column;
  font-family: var(--font-mono);
  animation: panel-in 0.25s var(--ease-standard);
}

.terminal.is-open {
  display: flex;
}

.terminal__bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  flex-shrink: 0;
}

.terminal__bar-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
}

.terminal__bar-label {
  font-size: 11px;
  color: var(--color-dim);
  margin-left: 8px;
  letter-spacing: 0.05em;
}

.terminal__history {
  flex: 1;
  overflow-y: auto;
  padding: 14px;
  font-size: 12.5px;
  line-height: 1.6;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Firefox: the default gray scrollbar otherwise clashes with the panel's
     dark glass background (see the ::-webkit-scrollbar rules below for
     Chrome/Safari/Edge, which don't support this property). */
  scrollbar-width: thin;
  scrollbar-color: var(--accent-purple-strong) rgba(255, 255, 255, 0.06);
}

.terminal__history::-webkit-scrollbar {
  width: 6px;
}

.terminal__history::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.04);
}

.terminal__history::-webkit-scrollbar-thumb {
  background: var(--accent-purple-strong);
  border-radius: 999px;
}

.terminal__history::-webkit-scrollbar-thumb:hover {
  background: var(--accent-magenta);
}

.terminal__line {
  /* pre-wrap (not the default) so padded columns in commands like "ls"
     render with real spacing instead of collapsing to a single space —
     still wraps normally on long lines, same as a real terminal. */
  white-space: pre-wrap;
  word-break: break-word;
}

.terminal__line--input .terminal__prompt {
  color: var(--accent-cyan);
}

.terminal__line--input {
  color: var(--color-text);
}

.terminal__line--output {
  color: var(--color-muted);
}

.terminal__input-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  flex-shrink: 0;
}

.terminal__input-row .terminal__prompt {
  color: var(--accent-cyan);
  font-size: 12.5px;
}

.terminal__input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: 12.5px;
}

/* Custom cursor (desktop only, pointer:fine): a small "planet" plus a
   satellite that orbits it in a real, tilted 3D ellipse — perspective on
   the pivot plus transform-style:preserve-3d down through the rotating
   plane means the browser actually z-sorts the satellite against the
   planet each frame, so it genuinely passes behind/in front of it rather
   than just an animated flat ring. Only #cursor-orbit's own left/top are
   written per rAF tick (see main.js) — everything else (the spin, the
   hover-triggered size/radius changes) is plain CSS, so nothing here
   fights that per-frame write with a competing transition. */
.cursor-orbit {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 150;
  perspective: 240px;
  transform-style: preserve-3d;
  display: none;
}

/* Outlined, not filled/glowing: a stroke ring with only a faint tint
   behind it, matching .cursor-orbit__path's own "just a line" look instead
   of reading as a bright glowing orb — but a touch more present than the
   first pass (thicker stroke, a soft glow behind it) since that read as
   too faint to notice against the page. */
.cursor-orbit__planet {
  position: absolute;
  top: 0;
  left: 0;
  width: 15px;
  height: 15px;
  margin: -7.5px 0 0 -7.5px;
  border-radius: 50%;
  border: 2px solid var(--accent-cyan);
  background: rgba(91, 224, 224, 0.16);
  box-shadow: 0 0 8px rgba(91, 224, 224, 0.5);
  transition: width 0.25s var(--ease-standard), height 0.25s var(--ease-standard), margin 0.25s var(--ease-standard);
}

.cursor-orbit__plane {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  animation: cursor-orbit-spin 13s linear infinite;
}

/* Back inside .cursor-orbit__plane (not a flat standalone ellipse) so it
   z-sorts in front of/behind the planet like the satellite does — a flat
   ellipse sibling looked cleaner up close but always rendered fully
   behind the planet, losing that crossing. A plain circle, not an arc —
   since it's rotationally symmetric, sharing the plane's rotateZ spin
   makes no visual difference. Diameter matches 2x the satellite's own
   orbit radius below. A uniform-width border on a circle tilted in 3D
   still projects with somewhat uneven apparent thickness (inherent to
   faking depth with a flat stroke — no CSS trick removes it entirely
   while keeping genuine z-sorting), but cursor-orbit-spin's tilt was
   brought down from the original 68deg to 60deg (went as low as 50deg at
   one point, then back up for a shorter/flatter ring now that the
   thickness issue reads as fixed) to keep that variation less pronounced
   without flattening the ellipse as much as 68deg did. */
.cursor-orbit__path {
  position: absolute;
  top: 0;
  left: 0;
  width: 38px;
  height: 38px;
  margin: -19px 0 0 -19px;
  border-radius: 50%;
  border: 2px solid rgba(160, 91, 255, 0.75);
  transition: width 0.3s var(--ease-standard), height 0.3s var(--ease-standard), margin 0.3s var(--ease-standard),
    border-color 0.3s var(--ease-standard);
}

/* Carries the satellite to its orbit radius while still inheriting
   .cursor-orbit__plane's tilt+spin (needed so it correctly z-sorts in
   front of/behind the planet) — the counter-rotation that keeps the
   satellite itself circular instead of squashed lives one level deeper,
   on .cursor-orbit__satellite, so it doesn't cancel this element's actual
   orbital position too. */
.cursor-orbit__satellite-carrier {
  position: absolute;
  top: 0;
  left: 0;
  transform-style: preserve-3d;
  transform: translateX(19px);
  transition: transform 0.3s var(--ease-standard);
}

.cursor-orbit__satellite {
  position: absolute;
  top: 0;
  left: 0;
  /* Solid, not outlined like the planet — at this size a stroke ring reads
     as a thin blob rather than a clean circle; a filled dot doesn't have
     that problem. */
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--accent-purple-strong);
  box-shadow: 0 0 7px rgba(160, 91, 255, 0.85);
  /* A flat disc that inherited the plane's rotateX(60deg) tilt + animated
     spin would render as a squashed ellipse, not a circle — a real sphere
     looks circular from any angle, but a 2D disc only does if it's always
     facing the camera. This "billboards" it: same spin animation as the
     plane but in reverse (cursor-orbit-counter-spin, synced to the same
     duration below) plus the opposite static tilt, exactly cancelling the
     inherited rotation so the disc itself stays flat-on while still
     travelling around the tilted orbit via the carrier above. */
  animation: cursor-orbit-counter-spin 13s linear infinite;
}

/* The wobble lives on rotateY (swinging the ellipse's own vertical axis
   left/right, +/-5deg = 5*sin(progress)), not rotateX: rotateX only
   changes how steep/flat the ellipse looks, which barely reads from a
   normal head-on view of the screen. rotateY visibly rocks the ellipse's
   width/orientation from that same viewpoint, one full wobble cycle per
   orbit revolution. rotateX stays fixed at 60deg; rotateZ still advances
   linearly 0->360 at the same stops. */
@keyframes cursor-orbit-spin {
  0% { transform: rotateX(60deg) rotateY(0deg) rotateZ(0deg); }
  12.5% { transform: rotateX(60deg) rotateY(3.5deg) rotateZ(45deg); }
  25% { transform: rotateX(60deg) rotateY(5deg) rotateZ(90deg); }
  37.5% { transform: rotateX(60deg) rotateY(3.5deg) rotateZ(135deg); }
  50% { transform: rotateX(60deg) rotateY(0deg) rotateZ(180deg); }
  62.5% { transform: rotateX(60deg) rotateY(-3.5deg) rotateZ(225deg); }
  75% { transform: rotateX(60deg) rotateY(-5deg) rotateZ(270deg); }
  87.5% { transform: rotateX(60deg) rotateY(-3.5deg) rotateZ(315deg); }
  100% { transform: rotateX(60deg) rotateY(0deg) rotateZ(360deg); }
}

/* Exact inverse of cursor-orbit-spin (reversed rotation order, negated
   angles at each matching stop — including the wobbling rotateY, not just
   the constant rotateX) so it stays in lockstep, same duration/timing —
   see the comment on .cursor-orbit__satellite. */
@keyframes cursor-orbit-counter-spin {
  0% { transform: rotateZ(0deg) rotateY(0deg) rotateX(-60deg); }
  12.5% { transform: rotateZ(-45deg) rotateY(-3.5deg) rotateX(-60deg); }
  25% { transform: rotateZ(-90deg) rotateY(-5deg) rotateX(-60deg); }
  37.5% { transform: rotateZ(-135deg) rotateY(-3.5deg) rotateX(-60deg); }
  50% { transform: rotateZ(-180deg) rotateY(0deg) rotateX(-60deg); }
  62.5% { transform: rotateZ(-225deg) rotateY(3.5deg) rotateX(-60deg); }
  75% { transform: rotateZ(-270deg) rotateY(5deg) rotateX(-60deg); }
  87.5% { transform: rotateZ(-315deg) rotateY(3.5deg) rotateX(-60deg); }
  100% { transform: rotateZ(-360deg) rotateY(0deg) rotateX(-60deg); }
}

/* Hovering anything clickable grows the planet and swings the satellite
   further out on a faster orbit — the same "something's interactive here"
   signal a scaling ring gave before, expressed through the 3D system
   instead. */
.cursor-orbit.is-active .cursor-orbit__planet {
  width: 21px;
  height: 21px;
  margin: -10.5px 0 0 -10.5px;
}

.cursor-orbit.is-active .cursor-orbit__plane {
  animation-duration: 5.4s;
}

.cursor-orbit.is-active .cursor-orbit__path {
  width: 60px;
  height: 60px;
  margin: -30px 0 0 -30px;
  border-color: rgba(91, 224, 224, 0.55);
}

.cursor-orbit.is-active .cursor-orbit__satellite-carrier {
  transform: translateX(30px);
}

/* Kept in sync with .cursor-orbit__plane's own hover speed-up above —
   otherwise the counter-spin would fall out of lockstep with the spin
   it's meant to exactly cancel, and the satellite would visibly wobble/
   tilt instead of staying billboarded. */
.cursor-orbit.is-active .cursor-orbit__satellite {
  animation-duration: 5.4s;
}

.has-fine-pointer.no-reduced-motion .cursor-orbit {
  display: block;
}

/* The OS cursor is hidden site-wide once the custom one takes over, so it
   actually reads as the cursor rather than a decoration next to it. */
.has-fine-pointer.no-reduced-motion {
  cursor: none;
}

/* ...except inside the Capacidades 3D scene: it captures wheel/touch for
   orbit-zoom and already has its own grab/grabbing affordance (see
   .planets__mount in planets.css), which needs the real OS cursor. */
#planets-root {
  cursor: auto;
}
