/* ============================================================
   CTRL-V page sections
   Hero (desktop cover) → Details (FileSys desktop with three
   Win98 windows) → … → taskbar above the site footer.
   ============================================================ */

body {
  background-color: var(--ctrlv-desktop-blue);
}

/* ============================================================
   HERO — the infected desktop (cover art)
   ============================================================ */

/* NOTE: the site-wide style.css turns <body> into a centering flex
   column, so top-level sections must claim their own width or they
   shrink-to-fit (the hero collapses to 0 — it only has absolutely
   positioned children). */

/* Back button sits below the video instead of over it — its frosted-glass
   look doesn't read well over the busy, moving footage, so it's simpler to
   just clear the video entirely rather than fight for contrast/legibility. */
.back-button-container {
  position: absolute;
  top: calc(min(56.25vw, 62svh) + 8px);
  left: 0;
  z-index: 4;
}

.ctrlv-hero {
  position: relative;
  width: 100%;
  height: 100svh;
  overflow: hidden;
  background-color: var(--ctrlv-desktop-blue);
  /* Video is 16:9 (56.25vw tall at full width) — capped so wide/short
     viewports (e.g. a 1920x1080 window) always leave room for the content
     block below instead of the video swallowing the whole hero. */
  --ctrlv-hero-video-h: min(56.25vw, 62svh);
}

/* On mobile the hero doesn't need to claim the full viewport — leaving it
   short of 100svh lets the desktop section peek in as a scroll hint. */
@media (max-width: 700px) {
  .ctrlv-hero {
    height: 88svh;
  }
}

/* Anchored to the top and sized by width, not height — the video keeps its
   native aspect ratio and whatever space is left below it becomes the
   content zone, instead of cropping the footage to fill the viewport.
   Only once the height cap above kicks in does it crop (top-anchored)
   rather than shrink off the full width. */
.ctrlv-hero-wallpaper {
  position: absolute;
  inset: 0 0 auto 0;
  width: 100%;
  height: var(--ctrlv-hero-video-h);
  object-fit: cover;
  object-position: top center;
}

/* Video is the default hero; the poster <img> is a static fallback only
   (no motion preference, or a browser that can't play either <source>). */
.ctrlv-hero-poster-fallback {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .ctrlv-hero-video { display: none; }
  .ctrlv-hero-poster-fallback { display: block; }
}

/* Short fade at the video's own bottom edge so it doesn't end on a hard
   line — sits over the video, not over the content zone below it. Scaled
   to the video's own height (not a flat vh value) so it stays proportional
   on mobile, where the video itself is much shorter. */
.ctrlv-hero-video-fade {
  position: absolute;
  left: 0;
  top: calc(var(--ctrlv-hero-video-h) - clamp(24px, calc(var(--ctrlv-hero-video-h) * 0.16), 96px));
  width: 100%;
  height: clamp(24px, calc(var(--ctrlv-hero-video-h) * 0.16), 96px);
  background: linear-gradient(to bottom, transparent, var(--ctrlv-desktop-blue));
  pointer-events: none;
}

/* Scene filling the space below the video: player + logo + Lampy, with the
   team byline underneath. Everything staggers in with the same fade-up. */
.ctrlv-hero-content {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--ctrlv-hero-video-h);
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Bottom padding reserves the strip the scroll cue occupies (24px offset
     + its own height), so centered content can never sit on top of it. */
  padding: 2rem 6% calc(5.5rem + env(safe-area-inset-bottom));
  box-sizing: border-box;
  text-align: center;
  /* logo.png is a 900x900 canvas whose artwork only spans rows 252–647 —
     the transparent margins (~28% of the width, top and bottom) would push
     the byline far below the visible logo, so the scene compensates.
     The svh cap keeps logo + byline clear of the scroll cue on short/wide
     desktop viewports, where the content zone is only ~38svh tall. */
  --ctrlv-hero-logo-w: min(360px, 60vw, 28svh);
}

.ctrlv-hero-scene {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(12px, 3vw, 40px);
  margin: calc(var(--ctrlv-hero-logo-w) * -0.24) 0;
}

.ctrlv-hero-logo {
  width: var(--ctrlv-hero-logo-w);
  height: auto;
  image-rendering: pixelated;

  view-transition-name: project-icon;
}

.ctrlv-hero-player {
  width: clamp(48px, 6.5vw, 88px);
  height: auto;
  image-rendering: pixelated;
}

/* lampy.png is a 2-frame sheet side by side (77x77 frames) — crop to the
   first frame */
.ctrlv-hero-lampy {
  width: clamp(48px, 6vw, 72px);
  aspect-ratio: 1;
  object-fit: cover;
  object-position: left;
  image-rendering: pixelated;
}

.ctrlv-hero-byline {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin: 0;
}

.ctrlv-hero-byline-text {
  font-family: var(--ctrlv-font-pixel);
  font-size: 0.7rem;
  color: #fff;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.35);
}

.ctrlv-hero-team-logo {
  width: min(120px, 28vw, 13svh);
  height: auto;
}

/* Staggered entrance, matching the DormMate hero pattern */
.ctrlv-hero-logo,
.ctrlv-hero-player,
.ctrlv-hero-lampy,
.ctrlv-hero-byline {
  opacity: 0;
  transform: translateY(26px) scale(0.94);
  filter: blur(8px);
  animation: ctrlv-hero-fade-up 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.15s forwards;
  will-change: transform, opacity, filter;
}

.ctrlv-hero-player { animation-delay: 0.4s; }
.ctrlv-hero-lampy { animation-delay: 0.55s; }
.ctrlv-hero-byline { animation-delay: 0.7s; }

@keyframes ctrlv-hero-fade-up {
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
    filter: blur(0);
  }
}

/* Once they've landed, the sprites idle-bob in counter-phase — steps()
   keeps the motion chunky like an actual low-fps sprite animation. */
@keyframes ctrlv-hero-idle-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}

.ctrlv-hero-player {
  animation: ctrlv-hero-fade-up 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.4s forwards,
             ctrlv-hero-idle-bob 1.6s steps(2, end) 1.5s infinite;
}

.ctrlv-hero-lampy {
  animation: ctrlv-hero-fade-up 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.55s forwards,
             ctrlv-hero-idle-bob 1.6s steps(2, end) 2.3s infinite;
}

@media (max-width: 700px) {
  /* Mobile's content zone is tall (short video + 88svh hero), so the svh
     cap is unnecessary — width is the only real constraint. */
  .ctrlv-hero-content {
    --ctrlv-hero-logo-w: min(360px, 52vw);
  }

  .ctrlv-hero-team-logo {
    width: min(120px, 28vw);
  }

  .ctrlv-hero-player {
    width: 48px;
  }

  .ctrlv-hero-lampy {
    width: 44px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ctrlv-hero-logo,
  .ctrlv-hero-player,
  .ctrlv-hero-lampy,
  .ctrlv-hero-byline {
    animation: none;
    opacity: 1;
    transform: none;
    filter: none;
  }
}

.ctrlv-hero-scroll-cue {
  position: absolute;
  left: 50%;
  bottom: calc(24px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  color: #fff;
  text-decoration: none;
  font-family: var(--ctrlv-font-pixel);
  font-size: 0.75rem;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.35);
}

@keyframes ctrlv-cue-bob {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50% { transform: translateX(-50%) translateY(6px); }
}

.ctrlv-hero-scroll-cue {
  animation: ctrlv-cue-bob 1.2s steps(2, end) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .ctrlv-hero-scroll-cue { animation: none; }
}

.ctrlv-hero-scroll-cue .material-symbols-outlined {
  font-size: 2rem;
}

/* ============================================================
   DETAILS — FileSys desktop with three windows
   Same 3-pane layout/content as the other project pages;
   the panes are dressed as Win98 windows.
   ============================================================ */

.ctrlv-desktop {
  position: relative;
  display: flex;
  gap: 24px;
  align-items: flex-start;
  padding: 64px 24px;
  width: 100%;
  max-width: 1500px;
  box-sizing: border-box;
  margin: 0 auto;
  text-align: left;   /* body sets text-align: center */
  /* Subtle 2px dither, like the FileSys level backdrop */
  background-image: repeating-conic-gradient(
    rgba(255, 255, 255, 0.025) 0% 25%,
    transparent 0% 50%
  );
  background-size: 4px 4px;
}

.left-detail-container,
.right-detail-container {
  flex: 1;
  min-width: 0;
}

.middle-content-container {
  flex: 2.5;
  min-width: 0;
}

@media (max-width: 1000px) {
  .ctrlv-desktop {
    flex-direction: column;
    align-items: stretch;
    padding: 40px 16px;
  }
}

/* Slight offsets so the windows feel scattered on a desk */
@media (min-width: 1001px) {
  .left-detail-container  { margin-top: 28px; }
  .right-detail-container { margin-top: 12px; }
}

/* ---------- Window content ---------- */

.xp-window-body .subsection-header-label {
  display: block;
  font-family: var(--ctrlv-font-pixel);
  font-size: 0.75rem;
  color: #3a3a3a;
  margin: 0 0 6px;
}

.xp-window-body .project-exam-course {
  margin: 0 0 12px;
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.25rem;
  color: #000;
}

.ctrlv-polimi-logo {
  width: 120px;
  margin-bottom: 16px;
  image-rendering: auto;
}

/* Win98 re-skin of the shared degree badge */
.xp-window-body .degree-badge-container {
  border-radius: 0;
  opacity: 1;
  margin-left: 0;
  margin-right: 0;
  background: var(--ctrlv-chrome);
  box-shadow:
    inset -1px -1px var(--ctrlv-chrome-darker),
    inset 1px 1px var(--ctrlv-chrome-lighter),
    inset -2px -2px var(--ctrlv-chrome-dark),
    inset 2px 2px var(--ctrlv-chrome-light);
}

.xp-window-body .degree-badge-text {
  font-family: var(--ctrlv-font-pixel);
  font-size: 0.7rem;
  color: #000;
}

.xp-window-body .skills-container {
  margin-top: 18px;
}

/* Win98 re-skin of the shared skill pills (markup from shared JS) */
.xp-window-body .skill-pill {
  background: var(--ctrlv-chrome);
  border-radius: 0;
  box-shadow:
    inset -1px -1px var(--ctrlv-chrome-darker),
    inset 1px 1px var(--ctrlv-chrome-lighter),
    inset -2px -2px var(--ctrlv-chrome-dark),
    inset 2px 2px var(--ctrlv-chrome-light);
}

.xp-window-body .project-skill-text {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1rem;
  color: #000;
}

/* "What is it?" window */

.ctrlv-readme-body .middle-section-title {
  font-family: var(--ctrlv-font-pixel);
  font-size: 1.4rem;
  color: var(--ctrlv-navy);
  margin: 0 0 16px;
}

.ctrlv-readme-body .content {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.3rem;
  line-height: 1.5;
  color: #111;
  margin: 0;
}

/* VT323 has no bold weight — emphasize like a terminal highlight instead */
.ctrlv-readme-body .content b {
  font-weight: normal;
  color: var(--ctrlv-banner-blue);
}

/* Contributors window */

.ctrlv-contributors-body .project-type-container {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 14px;
}

.ctrlv-contributors-body .project-type-icon {
  font-family: "Material Symbols Outlined";
  font-size: 1.4rem;
  color: #000;
  margin: 0;
}

.ctrlv-contributors-body .project-type-text {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.15rem;
  color: #000;
  margin: 0;
}

.ctrlv-contributors-body .github-link-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  color: #000;
}

.ctrlv-contributors-body .github-icon {
  width: 20px;
  height: 20px;
}

.ctrlv-contributors-body .github-link-text {
  font-family: var(--ctrlv-font-pixel);
  font-size: 0.8rem;
}

.ctrlv-contributors-body .partner {
  margin: 6px 0;
}

.ctrlv-contributors-body .partner-link {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: #000;
  padding: 4px;
}

.ctrlv-contributors-body .partner-link:hover {
  background: var(--ctrlv-titlebar);
  color: #fff;
}

.ctrlv-contributors-body .partner-avatar {
  width: 32px;
  height: 32px;
  /* Square avatars: it's 1998 */
  border-radius: 0;
  box-shadow:
    inset -1px -1px var(--ctrlv-chrome-dark),
    inset 1px 1px var(--ctrlv-chrome-lighter);
}

.ctrlv-contributors-body .partner-username {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.1rem;
}

.ctrlv-contributors-body .partner-role-icon {
  font-size: 1rem;
  margin-left: auto;
  opacity: 0.65;
}

/* Desktop decorations (floating file icons, like the FileSys level) */

.ctrlv-desktop-icon {
  position: absolute;
  image-rendering: pixelated;
  pointer-events: none;
  z-index: 0;
}

/* Copyable decorations need pointer events for the scanner hover */
.ctrlv-desktop-icon[data-copyable] {
  pointer-events: auto;
}

.ctrlv-desktop > .xp-window {
  position: relative;
  z-index: 1;
}

@media (max-width: 1000px) {
  .ctrlv-desktop-icon { display: none; }
}

/* ============================================================
   TASKBAR — sits above the site footer
   ============================================================ */

.ctrlv-taskbar {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  background: var(--ctrlv-chrome);
  box-shadow: inset 0 1px var(--ctrlv-chrome-lighter), inset 0 2px var(--ctrlv-chrome-light);
  border-top: 1px solid var(--ctrlv-chrome-darker);
  padding: 4px 6px;
  padding-bottom: calc(4px + env(safe-area-inset-bottom));
  margin-bottom: 2rem;
  position: relative;
  z-index: 3;
}

.ctrlv-taskbar-start {
  font-weight: bold;
}

.ctrlv-taskbar .xp-btn {
  padding: 5px 14px;
  font-size: 0.75rem;
}

.ctrlv-taskbar .xp-btn:active,
.ctrlv-taskbar .xp-btn.is-pressed {
  padding: 6px 13px 4px 15px;
}

.ctrlv-taskbar-divider {
  width: 2px;
  align-self: stretch;
  background: var(--ctrlv-chrome-dark);
  border-right: 1px solid var(--ctrlv-chrome-lighter);
  margin: 2px 2px;
}

.ctrlv-taskbar-spacer {
  flex: 1;
}

.ctrlv-taskbar-clock {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1rem;
  color: #000;
  background: var(--ctrlv-chrome);
  box-shadow:
    inset 1px 1px var(--ctrlv-chrome-dark),
    inset -1px -1px var(--ctrlv-chrome-lighter);
  padding: 5px 12px;
  user-select: none;
}

@media (max-width: 600px) {
  .ctrlv-taskbar-tasks { display: none; }
}

/* ============================================================
   ABILITIES — the Boot Sector
   ============================================================ */

.ctrlv-abilities {
  position: relative;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  background-color: var(--ctrlv-boot-black);
  background-image: url("/assets/images/projects/CTRL-V/backgrounds/boot.png");
  background-size: cover;
  background-position: center;
  image-rendering: pixelated;
  padding: 80px 24px 96px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}

/* Loose data blocks scattered around the section, same sprites the
   game lets you copy/cut/paste onto platforms */
.ctrlv-block-decor {
  position: absolute;
  image-rendering: pixelated;
  pointer-events: auto;
  z-index: 0;
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.6));
}

@media (max-width: 760px) {
  .ctrlv-block-decor { display: none; }
}

.ctrlv-abilities-banner h2 {
  font-size: 1.6rem;
  letter-spacing: 0.04em;
}

.ctrlv-abilities-intro {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.35rem;
  color: #fff;
  text-align: center;
  max-width: 560px;
  margin: 0;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);
}

.ctrlv-abilities-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 24px;
  width: 100%;
  max-width: 1200px;
  margin-top: 12px;
}

/* Ability cards look like the game's copyable data blocks:
   white pixel outline, black body, faint binary fill */
.ctrlv-ability-card {
  position: relative;
  border: 2px solid #fff;
  outline: 2px solid rgba(0, 0, 0, 0.85);
  background: rgba(0, 0, 0, 0.82);
  padding: 20px;
  overflow: hidden;
}

.ctrlv-ability-card::before {
  content: "01101000 01100101 01101100 01110000 00100000 01101101 01100101 00100000 01101100 01100001 01101101 01110000 01111001 00100000 01101001 01110011 00100000 01100101 01110110 01101001 01101100";
  position: absolute;
  inset: 0;
  font-family: var(--ctrlv-font-terminal);
  font-size: 0.9rem;
  line-height: 1.3;
  color: var(--ctrlv-grid-green);
  opacity: 0.14;
  word-break: break-all;
  padding: 6px;
  pointer-events: none;
}

.ctrlv-ability-name {
  font-family: var(--ctrlv-font-pixel);
  font-size: 1.1rem;
  color: #fff;
  margin: 14px 0 8px;
}

.ctrlv-ability-desc {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.2rem;
  line-height: 1.4;
  color: #cfe3cf;
  margin: 0;
}

/* Keycap presses down on hover, like in-game */
.ctrlv-ability-card:hover .ctrlv-keycap--c { background-image: url("/assets/images/projects/CTRL-V/sprites/keycap-c-down.png"); }
.ctrlv-ability-card:hover .ctrlv-keycap--v { background-image: url("/assets/images/projects/CTRL-V/sprites/keycap-v-down.png"); }
.ctrlv-ability-card:hover .ctrlv-keycap--x { background-image: url("/assets/images/projects/CTRL-V/sprites/keycap-x-down.png"); }
.ctrlv-ability-card:hover .ctrlv-keycap--z { background-image: url("/assets/images/projects/CTRL-V/sprites/keycap-z-down.png"); }

.ctrlv-abilities-hint {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.2rem;
  color: var(--ctrlv-grid-green);
  background: rgba(0, 0, 0, 0.8);
  border: 1px solid var(--ctrlv-grid-green);
  padding: 10px 18px;
  margin: 8px 0 0;
  max-width: 640px;
  text-align: center;
}

.ctrlv-abilities-hint b {
  font-weight: normal;
  color: #fff;
}

/* Hide the interactive hint where there is no pointer/keyboard.
   Not `@media (hover: none)`: iPadOS Safari always matches that, even
   with a trackpad connected, so it's gated on ctrl-v_clipboard.js's
   live pointerType detection (ctrlv-pointer-ready) instead. */
body:not(.ctrlv-pointer-ready) .ctrlv-abilities-hint:not(.ctrlv-abilities-hint--touch) { display: none; }

/* Touch gets its own long-press-flavored hint, shown only once a real
   touch has been seen (ctrlv-touch-ready, set in ctrl-v_clipboard.js) */
.ctrlv-abilities-hint--touch { display: none; }
body.ctrlv-touch-ready .ctrlv-abilities-hint--touch { display: block; }
body.ctrlv-touch-ready .ctrlv-abilities-hint:not(.ctrlv-abilities-hint--touch) { display: none; }

/* ============================================================
   LEVEL GALLERY — inside the screen (GPU level)
   ============================================================ */

.ctrlv-gallery {
  position: relative;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  background-color: #0d0d12;
  background-image: url("/assets/images/projects/CTRL-V/backgrounds/gpu.webp");
  background-size: 480px;
  image-rendering: pixelated;
  padding: 80px 24px;
  display: flex;
  justify-content: center;
}

.ctrlv-gallery-decor {
  position: absolute;
  image-rendering: pixelated;
  pointer-events: auto;
  z-index: 0;
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.6));
}

@media (max-width: 760px) {
  .ctrlv-gallery-decor { display: none; }
}

.ctrlv-gallery-window {
  width: 100%;
  max-width: 1080px;
}

.ctrlv-gallery-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ctrlv-gallery-viewport {
  /* Sunken picture area, like an old image viewer */
  box-shadow:
    inset 1px 1px var(--ctrlv-chrome-dark),
    inset -1px -1px var(--ctrlv-chrome-lighter),
    inset 2px 2px var(--ctrlv-chrome-darker),
    inset -2px -2px var(--ctrlv-chrome-light);
  padding: 4px;
  background: #000;
}

.ctrlv-gallery-viewport img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  image-rendering: pixelated;
}

.ctrlv-gallery-controls {
  display: flex;
  align-items: center;
  gap: 14px;
}

.ctrlv-gallery-caption {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  text-align: center;
}

.ctrlv-gallery-title {
  font-family: var(--ctrlv-font-pixel);
  font-size: 0.95rem;
  color: var(--ctrlv-navy);
}

.ctrlv-gallery-desc {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.15rem;
  color: #222;
}

.ctrlv-gallery-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
}

.ctrlv-gallery-dot {
  width: 12px;
  height: 12px;
  border: none;
  padding: 0;
  background: var(--ctrlv-chrome);
  box-shadow:
    inset 1px 1px var(--ctrlv-chrome-dark),
    inset -1px -1px var(--ctrlv-chrome-lighter);
  cursor: pointer;
}

.ctrlv-gallery-dot.is-active {
  background: var(--ctrlv-banner-blue);
}

/* ============================================================
   PLAY — the Core
   ============================================================ */

.ctrlv-play {
  position: relative;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  background-color: var(--ctrlv-core-teal);
  background-image: url("/assets/images/projects/CTRL-V/backgrounds/core.png");
  background-size: cover;
  background-position: center;
  image-rendering: pixelated;
  padding: 80px 24px 96px;
  display: flex;
  justify-content: center;
}

.ctrlv-play-decor {
  position: absolute;
  image-rendering: pixelated;
  pointer-events: none;
}

/* The virus sprite sheet has 2 frames side by side — show one */
.ctrlv-play-decor--virus {
  width: 48px !important;
  height: 48px;
  object-fit: cover;
  object-position: left;
}

/* Copyable decorations need pointer events for the scanner hover */
.ctrlv-play-decor[data-copyable] {
  pointer-events: auto;
}

/* Shared crop for square-framed enemy sprite sheets — each sheet is a
   row of equal, square frames, so an aspect-ratio: 1/1 box + object-fit:
   cover + left-anchored position always lands on frame 1 */
.ctrlv-sprite-frame {
  aspect-ratio: 1 / 1;
  height: auto;
  object-fit: cover;
  object-position: left top;
}

/* deadpixel.png frame 0 is nearly all transparent (blends into the
   dark level backdrop in-game) — frame 3 of 4 reads as a shape here */
.ctrlv-sprite-frame--deadpixel {
  object-position: 66.7% top;
}

.ctrlv-play-window {
  width: 100%;
  max-width: 1040px;
}

.ctrlv-play-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ctrlv-play-frame {
  position: relative;
  aspect-ratio: 980 / 620;
  background: #000;
  box-shadow:
    inset 1px 1px var(--ctrlv-chrome-dark),
    inset -1px -1px var(--ctrlv-chrome-lighter),
    inset 2px 2px var(--ctrlv-chrome-darker),
    inset -2px -2px var(--ctrlv-chrome-light);
  padding: 4px;
  overflow: hidden;
}

.ctrlv-play-poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  image-rendering: pixelated;
  filter: brightness(0.55);
}

.ctrlv-play-run {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.05rem;
  padding: 14px 28px;
}

.ctrlv-play-run:active {
  padding: 15px 27px 13px 29px;
}

.ctrlv-play-frame iframe {
  position: absolute;
  inset: 4px;
  width: calc(100% - 8px);
  height: calc(100% - 8px);
  border: 0;
}

.ctrlv-play-note {
  font-family: var(--ctrlv-font-terminal);
  font-size: 1.2rem;
  color: #222;
  margin: 0;
  text-align: center;
}

.ctrlv-play-note a {
  color: var(--ctrlv-banner-blue);
}

/* ============================================================
   CLIPBOARD ABILITY — scanner highlight, HUD, paste layer
   ============================================================ */

/* Copyable elements: only the cursor hints at them — like the game,
   highlights appear exclusively while Ctrl/Cmd is held */
[data-copyable] {
  cursor: crosshair;
  /* A long-press opens our own touch toolbar (see ctrl-v_clipboard.js) —
     suppress iOS's native callout/selection menu so it doesn't compete */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

[data-copyable] img,
img[data-copyable] {
  -webkit-user-drag: none;
}

/* Cut elements stay in the layout as an invisible hole (no reflow) */
.ctrlv-cut {
  visibility: hidden;
}

/* Pasted clones live in a fixed overlay layer */
.ctrlv-paste-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 0;
  overflow: visible;
  z-index: 5;
  pointer-events: none;
}

.ctrlv-paste-layer .ctrlv-pasted {
  position: absolute;
  margin: 0;
  pointer-events: auto;
}

@keyframes ctrlv-paste-pop {
  0% { transform: scale(0.4); }
  60% { transform: scale(1.1); }
  100% { transform: scale(1); }
}

/* Pasted clones are holograms, not the real thing — the game re-skins
   them with a translucent, blue-tinted material (same look as the
   paste-preview ghost below) and dissolves them after a while */
.ctrlv-pasted {
  opacity: 0.82;
  filter: sepia(0.9) hue-rotate(175deg) saturate(2.4) brightness(1.05);
  transition: opacity 0.9s ease;
  animation: ctrlv-paste-pop 0.18s steps(3, end);
}

.ctrlv-pasted.ctrlv-pasted-fading {
  opacity: 0;
}

@keyframes ctrlv-dissolve {
  0% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.85); }
}

.ctrlv-dissolving {
  animation: ctrlv-dissolve 0.22s steps(4, end) forwards;
}

@media (prefers-reduced-motion: reduce) {
  .ctrlv-pasted { animation: none; transition-duration: 0.01s; }
  .ctrlv-dissolving { animation-duration: 0.01s; }
}

/* Ability HUD — fixed bottom-right, like the in-game unlocked keys */
.ctrlv-hud {
  position: fixed;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  display: flex;
  gap: 4px;
  z-index: 20;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

.ctrlv-hud.is-visible {
  opacity: 1;
}

.ctrlv-hud .ctrlv-keycap {
  width: 48px;
  height: 48px;
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.5));
}

/* See the .ctrlv-abilities-hint rule above for why this isn't a
   `@media (hover: none)` block. */
body:not(.ctrlv-pointer-ready) .ctrlv-hud { display: none; }

/* Touch toolbar — long-press equivalent of the HUD, positioned by JS
   next to the press point. Uses opacity (not display) so its size can
   be measured for placement before it's shown. */
.ctrlv-touch-toolbar {
  position: fixed;
  top: 0;
  left: 0;
  display: flex;
  gap: 4px;
  padding: 6px;
  background: var(--ctrlv-chrome);
  box-shadow:
    inset -1px -1px var(--ctrlv-chrome-darker),
    inset 1px 1px var(--ctrlv-chrome-lighter),
    inset -2px -2px var(--ctrlv-chrome-dark),
    inset 2px 2px var(--ctrlv-chrome-light),
    2px 4px 10px rgba(0, 0, 0, 0.4);
  z-index: 25;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.ctrlv-touch-toolbar.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.ctrlv-touch-toolbar .ctrlv-keycap {
  width: 52px;
  height: 52px;
  border: none;
  background-color: transparent;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
}

.ctrlv-touch-toolbar .ctrlv-keycap.is-disabled {
  opacity: 0.35;
  filter: grayscale(1);
}

.ctrlv-touch-toolbar .ctrlv-keycap:active:not(.is-disabled) {
  transform: scale(0.92);
}

/* ============================================================
   HELD-MODIFIER HIGHLIGHTS — like the game, ability previews
   only show while Ctrl/Cmd is held:
     green  = scanned element can be copied / cut
     blue   = paste preview ghost under the cursor
     yellow = what Undo would restore or remove
   ============================================================ */

/* Copy/Cut — green scan highlight, while held (desktop) or selected via
   long-press with the touch toolbar open */
body.ctrlv-mod-held [data-copyable].ctrlv-scan,
body.ctrlv-touch-scanning [data-copyable].ctrlv-scan {
  outline: 2px solid var(--ctrlv-grid-green);
  outline-offset: 2px;
  background-color: rgba(153, 229, 80, 0.22);
  box-shadow: 0 0 0 4px rgba(153, 229, 80, 0.25);
}

/* Sprites are transparent-background pixel art, so a rectangular
   outline doesn't hug their silhouette. In-game, CopyHighlightManager
   duplicates the sprite, scales it up and tints it green behind the
   original (AuraEffect.cs) — a stacked drop-shadow glow gives the same
   "tinted duplicate poking out from behind" look, following the
   sprite's actual pixels instead of its bounding box. */
body.ctrlv-mod-held img[data-copyable].ctrlv-scan,
body.ctrlv-touch-scanning img[data-copyable].ctrlv-scan {
  outline: none;
  background: none;
  box-shadow: none;
  filter:
    sepia(0.4) hue-rotate(50deg) saturate(2) brightness(1.15)
    drop-shadow(0 0 2px var(--ctrlv-grid-green))
    drop-shadow(0 0 5px var(--ctrlv-grid-green))
    drop-shadow(0 0 9px rgba(153, 229, 80, 0.75));
}

/* Paste preview — the clipboard element's own look, tinted blue,
   slightly scaled, following the cursor */
.ctrlv-paste-preview {
  position: absolute;
  margin: 0;
  opacity: 0.55;
  filter: sepia(0.9) hue-rotate(175deg) saturate(2.4) brightness(0.95);
  transform: scale(1.05);
  pointer-events: none;
  z-index: 6;
}

/* Undo (last action = cut) — yellow ghost where the element lived */
.ctrlv-undo-ghost {
  position: absolute;
  margin: 0;
  opacity: 0.5;
  filter: sepia(1) hue-rotate(5deg) saturate(3) brightness(1.1);
  transform: scale(1.03);
  pointer-events: none;
  z-index: 6;
}

/* Undo (last action = paste) — yellow ring around the removable clone */
.ctrlv-hl-undo {
  outline: 3px solid #ffd84a !important;
  outline-offset: 2px;
  box-shadow: 0 0 0 6px rgba(255, 216, 74, 0.3);
}

/* Sprites: same "tinted duplicate" glow as the scan highlight (see
   img[data-copyable].ctrlv-scan above), in undo's yellow — hugs the
   sprite's actual pixels instead of drawing a box around it */
img.ctrlv-hl-undo {
  outline: none !important;
  background: none !important;
  box-shadow: none !important;
  filter:
    sepia(1) hue-rotate(5deg) saturate(3) brightness(1.1)
    drop-shadow(0 0 2px #ffd84a)
    drop-shadow(0 0 5px #ffd84a)
    drop-shadow(0 0 9px rgba(255, 216, 74, 0.75)) !important;
}

/* ============================================================
   RAM HUD — the game's own sprites: ClipBoard lens + RamBar,
   plus the fullscreen RamOverload "cooling down" animation.
   Mechanics mirror RamSettings.asset (10 blocks, drain 1/s).
   ============================================================ */

.ctrlv-ram-hud {
  position: fixed;
  /* Sticky at the very top: the back button only lives in the hero
     (it scrolls away with the document), and the RAM HUD only turns
     visible once the hero is scrolled past, so the two never overlap.
     Kept small (not 0) so the wires poking up past the bar (see
     .ctrlv-ram-wire below) still land on-screen instead of getting
     clipped by the viewport — in-game the whole assembly sits flush
     against the top edge, wires disappearing off-frame. */
  top: 6px;
  left: 12px;
  display: flex;
  /* The bar sits near the TOP of the lens, not centered on it (see
     UI.prefab: RamIndicator's rect sits just below ClipboardPreview's
     top edge) — flex-start + the bar's own margin-top recreates that */
  align-items: flex-start;
  /* The lens (96px) is absolutely positioned and centered on this box,
     so the box must actually claim 96px of height — otherwise it
     overflows above the flex row's real (24px, bar-only) box and gets
     clipped by the viewport. */
  height: 96px;
  z-index: 20;
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

.ctrlv-ram-hud.is-visible {
  opacity: 1;
}

/* Cables threaded behind the RAM bar (WiresBackground group in-game),
   poking up above its top edge. Positions come straight from the
   game's own RectTransforms (UI.prefab: WiresBackground's Wire0/1/2,
   offset from RamIndicator's rect), converted with the same 0.75
   scale the bar/lens sprites already use, then anchored to our bar's
   actual left/top. Plain <img> tags added before the bar/lens in the
   DOM so they paint behind them and only their tips show. */
.ctrlv-ram-wire {
  position: absolute;
  image-rendering: pixelated;
  pointer-events: none;
}

.ctrlv-ram-wire--0 { width: 84px; left: 99px; top: -3px; }
.ctrlv-ram-wire--1 { width: 48px; left: 205px; top: -3px; }
.ctrlv-ram-wire--2 { width: 12px; left: 79px; top: -3px; }

/* Lens (ClipBoard.png: disc backdrop + ring frame) — drawn after the
   bar in the DOM so it overlaps the pipe's start, like in-game */
.ctrlv-ram-lens {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 96px;
  height: 96px;
}

.ctrlv-ram-lens::before {
  content: "";
  position: absolute;
  inset: 7%;
  background: url("/assets/images/projects/CTRL-V/sprites/lens-disc.png") center / contain no-repeat;
  image-rendering: pixelated;
}

.ctrlv-ram-lens::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url("/assets/images/projects/CTRL-V/sprites/lens-ring.png") center / contain no-repeat;
  image-rendering: pixelated;
}

.ctrlv-ram-lens-inner {
  position: absolute;
  inset: 15%;
  border-radius: 50%;
  overflow: hidden;
  z-index: 1;
}

/* Clipboard preview clone: absolutely centered, then scaled by JS —
   grid centering can't handle items larger than the lens circle */
.ctrlv-lens-clone {
  position: absolute;
  left: 50%;
  top: 50%;
  pointer-events: none;
}

/* RAM bar (RamBar.png: frame 296x32, strips 276x24) at 0.75 scale */
.ctrlv-ram-bar {
  position: relative;
  width: 222px;
  height: 24px;
  margin-left: 48px;   /* starts under the lens, pipe pokes out right */
  margin-top: 4px;     /* sits near the lens's top edge, not centered on it */
  image-rendering: pixelated;
}

.ctrlv-ram-fill-area {
  position: absolute;
  left: 3.4%;
  right: 3.4%;
  top: 12.5%;
  bottom: 12.5%;
  /* Dark backdrop strip + the light strip as the fill's shape mask */
  background: url("/assets/images/projects/CTRL-V/sprites/rambar-back.png") center / 100% 100% no-repeat;
  -webkit-mask-image: url("/assets/images/projects/CTRL-V/sprites/rambar-fill.png");
  -webkit-mask-size: 100% 100%;
  mask-image: url("/assets/images/projects/CTRL-V/sprites/rambar-fill.png");
  mask-size: 100% 100%;
  image-rendering: pixelated;
}

/* rambar-frame.png's 10 segment dividers are cut as transparent holes
   over an opaque frame — it has to paint AFTER (on top of) the fill
   strip or the dividers just disappear under it, leaving one solid
   blob instead of 10 separate cells */
.ctrlv-ram-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url("/assets/images/projects/CTRL-V/sprites/rambar-frame.png") center / 100% 100% no-repeat;
  image-rendering: pixelated;
  pointer-events: none;
}

.ctrlv-ram-fill {
  height: 100%;
  width: 0%;
  /* Color is driven by JS with the game's exact ramp */
  background-color: #00ff80;
  transition: width 0.15s steps(3, end), background-color 0.3s linear;
}

/* ---------- Overload lockout (RamOverload.png, 3-frame flicker) ---------- */

.ctrlv-ram-overlay {
  position: fixed;
  inset: 0;
  /* Above .ctrlv-cursor-buddy (z-index: 999) — the overload flash
     should cover the buddy sprite too, not sit behind it. */
  z-index: 1000;
  display: none;
  pointer-events: none;   /* blocks the abilities, not the site */
}

.ctrlv-ram-overlay.is-active {
  display: block;
}

.ctrlv-ram-overlay-frame {
  position: absolute;
  inset: 0;
  background-image: var(--ctrlv-overload-img);
  background-size: cover;
  background-position: center;
  image-rendering: pixelated;
  opacity: 0;
}

/* Mobile only: "cover" on a narrow/portrait screen mismatches the
   sprite's 480x270 aspect ratio badly — the browser has to scale the
   image way up to satisfy the taller viewport, then crops nearly all
   of the width away, slicing right through the triangle. Below this
   breakpoint: one image, scaled up large enough that the full 480px
   canvas clips off-screen left/right (that's just harmless dither
   texture), but sized off the triangle's own measured footprint
   (130x145px, dead-center in the frame) so the triangle itself always
   stays fully inside the viewport. Repeats vertically for the rest. */
@media (max-width: 700px) {
  .ctrlv-ram-overlay-frame {
    /* 480/130 * 100vw ≈ 369vw would render the triangle at exactly
       viewport width with zero margin — scale down a bit for breathing
       room, so it reads at ~80% of the screen's width. */
    background-size: 300vw auto;
    background-repeat: repeat-y;
    background-position: center center;
  }
}

.ctrlv-ram-overlay-frame--0 { --ctrlv-overload-img: url("/assets/images/projects/CTRL-V/sprites/ramoverload-0.png"); }
.ctrlv-ram-overlay-frame--1 { --ctrlv-overload-img: url("/assets/images/projects/CTRL-V/sprites/ramoverload-1.png"); }
.ctrlv-ram-overlay-frame--2 { --ctrlv-overload-img: url("/assets/images/projects/CTRL-V/sprites/ramoverload-2.png"); }

@keyframes ctrlv-overload-f0 {
  0%, 32% { opacity: 1; }
  33%, 100% { opacity: 0; }
}
@keyframes ctrlv-overload-f1 {
  0%, 32% { opacity: 0; }
  33%, 65% { opacity: 1; }
  66%, 100% { opacity: 0; }
}
@keyframes ctrlv-overload-f2 {
  0%, 65% { opacity: 0; }
  66%, 100% { opacity: 1; }
}

.is-active .ctrlv-ram-overlay-frame--0 { animation: ctrlv-overload-f0 0.6s steps(1, end) infinite; }
.is-active .ctrlv-ram-overlay-frame--1 { animation: ctrlv-overload-f1 0.6s steps(1, end) infinite; }
.is-active .ctrlv-ram-overlay-frame--2 { animation: ctrlv-overload-f2 0.6s steps(1, end) infinite; }

@media (prefers-reduced-motion: reduce) {
  .is-active .ctrlv-ram-overlay-frame { animation: none; }
  .is-active .ctrlv-ram-overlay-frame--0 { opacity: 1; }
}

/* See the .ctrlv-abilities-hint rule above for why this isn't a
   `@media (hover: none)` block. RAM costs apply on touch too (see the
   touch toolbar in ctrl-v_clipboard.js), so this stays visible for
   ctrlv-touch-ready as well as ctrlv-pointer-ready. */
body:not(.ctrlv-pointer-ready):not(.ctrlv-touch-ready) .ctrlv-ram-hud,
body:not(.ctrlv-pointer-ready):not(.ctrlv-touch-ready) .ctrlv-ram-overlay { display: none !important; }
