/* Marvin Advergames — retro site styles */

/* Self-hosted (not loaded from Google's CDN) so visitor IPs never reach
   fonts.gstatic.com — only the latin subset, which covers EN/IT copy. */
@font-face {
  font-family: "Press Start 2P";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/press-start-2p-latin.woff2") format("woff2");
}
@font-face {
  font-family: "VT323";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/vt323-latin.woff2") format("woff2");
}

:root {
  --bg: #f7f7f5;
  --bg-alt: #ececea;
  --ink: #18181b;
  --muted: #6b6b70;
  --card-bg: #ffffff;
  --yellow: #ffc72c;
  --blue: #2563eb;
  /* Was a blue-violet (#7c3aed) that read as near-identical to --blue on
     laptop screens with narrower gamuts. First shift (to #c026d3) fixed
     that but landed too close to --coral — same lightness/saturation as
     coral, differing only by hue, which is its own confusable pair. This
     darker plum-fuchsia clears both: further from blue in hue, and from
     coral in lightness (much darker) as well as hue. */
  --purple: #86198f;
  --mint: #34d399;
  --coral: #e11d48;
  /* Was only 29.6° from --blue on the hue wheel — the tightest gap in the
     whole palette, tighter even than the original blue/purple problem.
     Deepened toward petrol/teal so it separates from blue by lightness
     (23pt gap now, was 17) as well as hue, without drifting closer to
     --mint on the other side. */
  --cyan: #155e75;
  --gb-screen: #9bbc0f;
  --gb-pixel: #306230;
  --gb-pixel-dark: #0f380f;
  --shadow: 4px 4px 0 rgba(0, 0, 0, 0.35);
  --radius: 2px;
  --font-head: "Press Start 2P", monospace;
  --font-body: "VT323", monospace;
  --maxw: 1120px;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 20px;
  line-height: 1.5;
}

img { max-width: 100%; display: block; }

a { color: var(--blue); }
a:hover { color: var(--mint); }

h1, h2, h3, .pixel {
  font-family: var(--font-head);
  line-height: 1.4;
  letter-spacing: 0.5px;
}

h1 { font-size: clamp(1.4rem, 4vw, 2.4rem); }
h2 { font-size: clamp(1.1rem, 2.6vw, 1.6rem); }
h3 { font-size: 1rem; }

/* Titles cycle through the 4 palette colors dark/saturated enough to stay
   legible as running text (yellow/mint are reserved for fills & borders,
   not text) — sequential by section position so nearby sections never
   land on the same color, unlike a simple odd/even split. */
main > section:nth-of-type(4n+1) h2 { color: var(--blue); }
main > section:nth-of-type(4n+2) h2 { color: var(--purple); }
main > section:nth-of-type(4n+3) h2 { color: var(--coral); }
main > section:nth-of-type(4n+4) h2 { color: var(--cyan); }

.container {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* Header / nav */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 3px solid var(--ink);
}

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 0.9rem;
  padding-bottom: 0.9rem;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-head);
  font-size: 0.85rem;
  text-decoration: none;
  color: var(--ink);
}

.brand img { height: 44px; width: auto; }

.main-nav {
  display: flex;
  gap: 1.6rem;
  flex-wrap: wrap;
  font-size: 1.25rem;
}

.main-nav a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  padding-bottom: 4px;
}

/* Each nav item gets its own underline color from the palette — text stays
   ink for legibility, only the accent line changes, so every section reads
   as distinct without ever recoloring body/nav text into yellow or mint.
   5 items, 5 different colors — no repeats. */
.main-nav a:nth-child(1) { --nav-color: var(--yellow); }
.main-nav a:nth-child(2) { --nav-color: var(--blue); }
.main-nav a:nth-child(3) { --nav-color: var(--purple); }
.main-nav a:nth-child(4) { --nav-color: var(--mint); }
.main-nav a:nth-child(5) { --nav-color: var(--coral); }

.main-nav a:hover,
.main-nav a[aria-current="page"] {
  border-bottom-color: var(--nav-color);
  color: var(--ink);
}

.lang-switch {
  display: flex;
  gap: 0.5rem;
  font-family: var(--font-head);
  font-size: 0.8rem;
}

.lang-switch a {
  text-decoration: none;
  color: var(--muted);
  padding: 2px 6px;
  border: 2px solid transparent;
}

.lang-switch a[aria-current="true"] {
  color: var(--ink);
  border-color: var(--ink);
  background: var(--card-bg);
}

/* Hamburger nav toggle — hidden on desktop, where nav + lang-switch show
   inline as before. On mobile it collapses both into a dropdown, since a
   5-item nav plus EN/IT was wrapping onto 2-3 stacked rows and eating a
   huge chunk of the viewport before any real content appeared. */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 42px;
  height: 38px;
  border: 3px solid var(--ink);
  background: var(--card-bg);
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
}

.nav-toggle span {
  display: block;
  width: 20px;
  height: 3px;
  background: var(--ink);
}

/* On desktop this wrapper is invisible to layout — .main-nav and
   .lang-switch behave as if they were direct flex children of
   .site-header .container again, exactly like before the wrapper existed.
   The mobile media query below overrides this back to a real box so it can
   be shown/hidden as a dropdown. */
.site-nav { display: contents; }

@media (max-width: 760px) {
  .nav-toggle { display: flex; }

  .site-nav {
    display: none;
    flex-basis: 100%;
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
    padding-top: 1rem;
  }

  .site-nav.is-open { display: flex; }

  .main-nav { flex-direction: column; gap: 0.9rem; font-size: 1.1rem; }
}

/* Buttons */
.btn {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.85rem;
  text-decoration: none;
  color: var(--ink);
  background: var(--yellow);
  border: 3px solid var(--ink);
  padding: 0.9rem 1.2rem;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: transform 0.15s ease, background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.btn:hover {
  transform: scale(1.08);
  background: var(--blue);
  color: #fff;
  border-color: var(--ink);
}

.btn:active {
  transform: scale(0.97) translate(2px, 2px);
  box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.35);
}

.btn.btn-alt { background: var(--card-bg); color: var(--ink); }
.btn.btn-green { background: var(--mint); color: var(--ink); }

/* Explicit variant + :hover pairing needed: .btn.btn-alt/.btn.btn-green have
   the same specificity as .btn:hover and come later in the file, so without
   this they'd win over the plain hover rule above and the hover color
   change would silently not show on those two button variants. */
.btn.btn-alt:hover,
.btn.btn-green:hover {
  background: var(--blue);
  color: #fff;
  border-color: var(--ink);
}

/* Hero */
.hero {
  padding: 3.5rem 0 2.5rem;
}

.hero .container {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 2.5rem;
  align-items: center;
}

@media (max-width: 860px) {
  .hero .container { grid-template-columns: 1fr; }
}

.hero-solo .container {
  grid-template-columns: 1fr;
  max-width: 760px;
}

.hero-embed-section {
  padding: 1.5rem 0 2rem;
}

.embed-fullscreen {
  aspect-ratio: 1280 / 720;
  height: auto;
  margin: 0 auto;
}

/* On mobile the game itself scales responsively (Construct's own "scale
   outer" mode) rather than being locked to 16:9, so it can run portrait
   instead of letterboxing a small landscape rectangle. It still sits in
   the normal page frame (same container padding as everywhere else) and
   stops short of the full viewport height on purpose, so a sliver of the
   next section peeks through as a "there's more, scroll down" cue. */
@media (max-width: 760px) {
  .embed-fullscreen { aspect-ratio: 3 / 4; margin: 0 auto; }
}

.hero-tag {
  font-family: var(--font-head);
  color: var(--purple);
  font-size: 0.9rem;
  margin-bottom: 0.8rem;
  display: block;
}

.hero p.lead { font-size: 1.3rem; max-width: 46ch; margin-left: auto; margin-right: auto; }

.cta-row { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 1.5rem; }

/* Sections */
section { padding: 3rem 0; }
section h2 { margin-bottom: 1.2rem; }
.section-alt { background: var(--bg-alt); }

.grid {
  display: grid;
  gap: 1.5rem;
}

.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }

@media (max-width: 860px) {
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
  .grid-3, .grid-2 { grid-template-columns: 1fr; }
}

.card {
  position: relative;
  background: var(--card-bg);
  border: 3px solid var(--ink);
  border-top-width: 6px;
  border-top-color: var(--card-accent, var(--mint));
  box-shadow: var(--shadow);
  padding: 1.2rem;
}

/* A lone card outside a grid (e.g. a closing CTA) isn't competing with
   siblings for differentiation — keep it to a single accent (matching its
   own button) instead of border/heading/button each pulling a different
   color. (.card.center h2 needs to out-specificity the section h2 cycle
   above, which is section+nth-of-type+h2 — two classes beats that.) */
.card.center { border-top-color: var(--mint); }
.card.center h2 { color: var(--ink); }

/* Each card in a grid gets its own accent from the palette (cycling through
   all 6) instead of a uniform edge — cards read as distinct/dynamic while
   staying inside the same small color system. A 6-item grid (Team,
   Portfolio) gets 6 unique colors with zero repeats. */
.grid .card:nth-child(6n+1) { --card-accent: var(--yellow); }
.grid .card:nth-child(6n+2) { --card-accent: var(--blue); }
.grid .card:nth-child(6n+3) { --card-accent: var(--purple); }
.grid .card:nth-child(6n+4) { --card-accent: var(--mint); }
.grid .card:nth-child(6n+5) { --card-accent: var(--coral); }
.grid .card:nth-child(6n+6) { --card-accent: var(--cyan); }

/* Team */
.avatar {
  width: 100%;
  aspect-ratio: 1;
  border: 3px solid var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-head);
  font-size: 1.6rem;
  color: #fff;
  margin-bottom: 0.9rem;
  image-rendering: pixelated;
}

.avatar-real {
  width: 100%;
  aspect-ratio: 1;
  border: 3px solid var(--ink);
  margin-bottom: 0.9rem;
  overflow: hidden;
  background: #cdf3e1;
}

.avatar-real img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  image-rendering: pixelated;
}

.team-card h3 { margin: 0 0 0.2rem; }

/* Role caption uses its own 4-color rotation (only the tones dark enough
   to read as text) independent of the card's border accent above it —
   the border can land on yellow/mint, which would be illegible as text. */
.team-card:nth-child(4n+1) .role { color: var(--blue); }
.team-card:nth-child(4n+2) .role { color: var(--purple); }
.team-card:nth-child(4n+3) .role { color: var(--coral); }
.team-card:nth-child(4n+4) .role { color: var(--cyan); }
.team-card .role { font-size: 0.95rem; margin-bottom: 0.5rem; display: block; }
.team-card .skills { font-size: 0.95rem; color: var(--muted); }

/* Portfolio */
.cartridge {
  border: 3px solid var(--ink);
  background: var(--ink);
  color: #fff;
  padding: 0.6rem 0.8rem;
  font-family: var(--font-head);
  font-size: 0.8rem;
  margin-bottom: 0.9rem;
}

.game-embed {
  border: 3px solid var(--ink);
  background: var(--ink);
  background-size: cover;
  background-position: center;
  overflow: hidden;
  margin-bottom: 0.9rem;
  position: relative;
}

/* Semi-transparent so the loaded, animating game shows through behind it —
   a "press start" overlay rather than a solid loading box. */
.game-embed-play {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgba(24, 24, 27, 0.55);
  color: var(--mint);
  font-family: var(--font-head);
  font-size: 0.8rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  line-height: 1.6;
  transition: background-color 0.2s ease;
}

.game-embed-play:hover { background: rgba(24, 24, 27, 0.7); }

.game-embed-play[data-state="loading"] { cursor: default; }
.game-embed-play[data-state="loading"] span { opacity: 0.7; }

.game-embed-play[hidden] { display: none; }

.game-embed-play span {
  display: inline-block;
  background: var(--yellow);
  color: var(--ink);
  border: 3px solid var(--ink);
  padding: 1.1rem 2rem;
  font-size: 1.1rem;
  box-shadow: var(--shadow);
  transition: transform 0.15s ease;
}

.game-embed-play:not([data-state="loading"]):hover span { transform: scale(1.06); }

.game-embed-fullscreen {
  position: absolute;
  right: 0.6rem;
  bottom: 0.6rem;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0;
  background: rgba(24, 24, 27, 0.6);
  color: var(--mint);
  border: 2px solid var(--ink);
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.15s ease;
  z-index: 2;
}

.game-embed-fullscreen:hover { background: rgba(24, 24, 27, 0.85); transform: scale(1.08); }
.game-embed-fullscreen[hidden] { display: none; }

.cover-frame {
  display: block;
  aspect-ratio: 3 / 2;
  border: 3px solid var(--ink);
  overflow: hidden;
  margin-bottom: 0.9rem;
  background: var(--ink);
}

.cover-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.15s ease;
}

.cover-frame:hover img { transform: scale(1.03); }

.tag {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.75rem;
  background: var(--yellow);
  border: 2px solid var(--ink);
  padding: 3px 6px;
  margin-bottom: 0.6rem;
}

.client-tag { color: var(--muted); font-size: 0.95rem; margin-bottom: 0.5rem; }

/* Little cartridge mockups tucked onto matching portfolio cards, like a
   physical box-art peeking out of the corner. */
.cart-badge {
  position: absolute;
  top: -16px;
  right: -14px;
  width: 84px;
  border: 3px solid var(--ink);
  box-shadow: var(--shadow);
  transform: rotate(7deg);
  transition: transform 0.2s ease;
  z-index: 2;
}

.card:nth-child(even) .cart-badge { transform: rotate(-6deg); }
.card:hover .cart-badge { transform: rotate(0deg) scale(1.08); }

@media (max-width: 600px) {
  .cart-badge { width: 60px; top: -10px; right: -8px; }
}

/* Decorative scatter strip of other Marvin cartridges/titles, purely for
   flavour — not tied to a specific case study. */
.cart-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-end;
  gap: 1.8rem;
  margin: 2.4rem 0;
}

.cart-strip a {
  display: block;
}

.cart-strip img {
  width: 88px;
  border: 3px solid var(--ink);
  box-shadow: var(--shadow);
  transition: transform 0.2s ease;
}

.cart-strip a:nth-child(odd) img { transform: rotate(-6deg); }
.cart-strip a:nth-child(even) img { transform: rotate(5deg); }
.cart-strip a:hover img { transform: rotate(0deg) scale(1.1); }

/* Footer */
.site-footer {
  border-top: 3px solid var(--ink);
  padding: 2.5rem 0;
  background: var(--bg-alt);
}

.site-footer .container {
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}

.social-list {
  list-style: none;
  padding: 0;
  margin: 0.6rem 0 0;
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.social-list a { text-decoration: underline; }

.small-print { color: var(--muted); font-size: 0.9rem; margin-top: 1.5rem; }

/* Footer eco note — small badge, same treatment as the portfolio genre
   tags (mint fill, ink border/text — mint is too light to use as text
   color directly, so it stays fill-only here too). */
.eco-badge {
  display: inline-block;
  font-family: var(--font-head);
  font-size: 0.75rem;
  background: var(--mint);
  color: var(--ink);
  border: 2px solid var(--ink);
  padding: 4px 8px;
  margin-top: 0.8rem;
}

/* Video reel */
.video-frame {
  border: 3px solid var(--ink);
  box-shadow: var(--shadow);
  background: #000;
  max-width: 760px;
  margin: 0 auto;
  overflow: hidden;
}

.video-frame video { width: 100%; display: block; }

/* Retro dialog box (matches the in-game "CIAO!" popup chrome) */
.dialog-box {
  border: 3px solid var(--ink);
  box-shadow: var(--shadow);
  background: #fff;
  margin-bottom: 1.2rem;
}

.dialog-box-title {
  background: var(--ink);
  color: #fff;
  font-family: var(--font-head);
  font-size: 0.8rem;
  padding: 0.5rem 0.7rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Coral is reserved for a single accent per section instead of coloring
   every popup title bar the same — see index.html for which ones. */
.dialog-box-title.accent-purple { background: var(--purple); }
.dialog-box-title.accent-blue { background: var(--blue); }
.dialog-box-title.accent-coral { background: var(--coral); }
.dialog-box-title.accent-cyan { background: var(--cyan); }
/* Yellow and mint are too light for white text to pass contrast, so this
   one flips to dark text/close-button instead — same move already used
   for the widget's own GB-screen title bar. */
.dialog-box-title.accent-yellow { background: var(--yellow); color: var(--ink); }
.dialog-box-title.accent-yellow .dialog-box-close { border-color: var(--ink); color: var(--ink); }

.dialog-box-title .dialog-box-close {
  border: 1px solid #fff;
  width: 14px;
  height: 14px;
  line-height: 12px;
  text-align: center;
  font-size: 0.6rem;
}

.dialog-box-body { padding: 1rem 1.1rem; font-size: 1.15rem; }
.dialog-box-body .who { display: block; margin-top: 0.4rem; color: var(--muted); font-size: 0.95rem; }

/* Scrolling ticker of short, real reactions pulled from social comments —
   reads like a retro arcade high-score marquee. Pure CSS loop: the comment
   list is duplicated once in the HTML so scrolling exactly -50% loops
   seamlessly. Pauses on hover so it's actually readable if you want to. */
.comment-ticker {
  overflow: hidden;
  border: 3px solid var(--ink);
  background: var(--card-bg);
  box-shadow: var(--shadow);
  padding: 1rem 0;
  margin: 1.5rem 0;
}

.comment-ticker-track {
  display: flex;
  width: max-content;
  gap: 1rem;
  animation: ticker-scroll 45s linear infinite;
}

.comment-ticker:hover .comment-ticker-track { animation-play-state: paused; }

.comment-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  white-space: nowrap;
  font-size: 1.1rem;
  padding: 0.5rem 1rem;
  border: 2px solid var(--ink);
  background: var(--yellow);
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}

.comment-chip:hover { text-decoration: underline; }

.comment-chip .who { font-size: 0.85rem; opacity: 0.75; }

.comment-chip:nth-child(6n+2) { background: var(--mint); }
.comment-chip:nth-child(6n+3) { background: var(--blue); color: #fff; }
.comment-chip:nth-child(6n+4) { background: var(--coral); color: #fff; }
.comment-chip:nth-child(6n+5) { background: var(--purple); color: #fff; }
.comment-chip:nth-child(6n+6) { background: var(--cyan); color: #fff; }

@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Subtle CRT scanline overlay for cover art / portraits */
.cover-frame, .avatar-real { position: relative; }
.cover-frame::after, .avatar-real::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.08),
    rgba(0, 0, 0, 0.08) 1px,
    transparent 1px,
    transparent 3px
  );
  pointer-events: none;
}

/* Utility */
.center { text-align: center; }
.mt-0 { margin-top: 0; }
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

/* Animated Marvin mascot (9-frame idle sprite — stands and blinks rather
   than walking in place, which looked odd on a decorative element that
   never actually moves). */
.mascot-sprite {
  width: 112px;
  aspect-ratio: 112 / 124;
  display: inline-block;
  background-image: url("../img/marvin-idle-sprite.png");
  background-repeat: no-repeat;
  background-size: 900% 100%;
  image-rendering: pixelated;
  animation: marvin-mascot-idle 1.5s steps(9, jump-none) infinite;
}

.mascot-sprite.sm { width: 62px; }
.mascot-sprite.lg { width: 154px; }

@keyframes marvin-mascot-idle {
  from { background-position-x: 0%; }
  to { background-position-x: 100%; }
}

/* Little "dance" sequence for the Home CTA mascot only (assets/js/
   marvin-mascot.js): idle, then a jump, then a few steps right/left back
   to center, on a loop. .mascot-dance carries the position transition;
   .dance-walk / .dance-jump swap the sprite sheet for those phases. */
.mascot-dance {
  position: absolute;
  left: 0;
  bottom: 0;
  /* Only position slides smoothly — the left/right facing flip (via the
     separate `scale` property, not `transform`) snaps instantly, like a
     classic sprite flip, instead of visibly rotating through itself. */
  transition-property: translate;
}

/* Sized to match .mascot-dance exactly so the bubble's `left: 100%`
   anchors to Marvin's own right edge, not the much wider .card around
   him — .mascot-dance can't carry role="img" AND contain the bubble
   itself, since AT ignores the text content of anything inside a
   role="img" subtree, so this sibling wrapper holds both instead.
   Height is pinned to the taller jump/land frame (128px) rather than
   left to shrink-to-fit: .dance-jump's own aspect-ratio is 4px taller
   than idle's, and letting the wrap's height follow that made the
   whole card visibly grow and shrink by those 4px every time the jump
   phase played. .mascot-dance is bottom-anchored inside so his feet
   stay on the ground line either way. */
.mascot-dance-wrap {
  position: relative;
  width: 112px;
  height: 128px;
  margin: 0 auto 1rem;
}

/* Minimal ground line so his feet read as standing on something. */
.mascot-dance::after {
  content: "";
  position: absolute;
  left: 18px;
  right: 18px;
  bottom: 4px;
  height: 2px;
  background: var(--ink);
  opacity: 0.15;
}

/* "Which one's the real Marvin" bubble — a scripted, one-time exchange
   with the corner widget (assets/js/marvin-mascot.js + marvin-widget.js).
   Deliberately plain (white/black) rather than the widget's GB-screen
   toast look, so the two read as visually distinct characters. */
.mascot-dance-bubble {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: 14px;
  width: 170px;
  padding: 0.6rem 0.75rem;
  background: #fff;
  border: 3px solid var(--ink);
  box-shadow: var(--shadow);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.3;
  text-align: left;
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.mascot-dance-bubble.is-visible { opacity: 1; }

@media (max-width: 600px) {
  .mascot-dance-wrap {
    width: auto;
  }
  .mascot-dance-bubble {
    position: static;
    transform: none;
    margin: 0.6rem auto 0;
    left: auto;
    top: auto;
  }
}

/* Claudio breaks the fourth wall to end the clone-fight — a single,
   center-screen line styled distinctly from both Marvins' own speech
   (neither GB-screen nor plain-white) so it reads as "the real world"
   stepping in. Click anywhere to dismiss early, otherwise auto-closes. */
.marvin-interrupt {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(24, 24, 27, 0.7);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  cursor: pointer;
}

.marvin-interrupt.is-visible {
  opacity: 1;
  pointer-events: auto;
}

.marvin-interrupt-box {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  max-width: 420px;
  background: var(--ink);
  border: 3px solid var(--yellow);
  box-shadow: var(--shadow);
  padding: 1.1rem 1.3rem;
  color: #fff;
  font-size: 1rem;
  line-height: 1.4;
  text-align: left;
}

.marvin-interrupt-portrait {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  border: 2px solid var(--yellow);
  image-rendering: pixelated;
}

.marvin-interrupt-name {
  display: block;
  font-family: var(--font-head);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: var(--yellow);
  margin-bottom: 0.35rem;
}

.mascot-dance.dance-walk {
  background-image: url("../img/marvin-walk-sprite.png");
  background-size: 800% 100%;
  animation: marvin-mascot-walk 0.5s steps(8, jump-none) infinite;
}

.mascot-dance.dance-jump {
  aspect-ratio: 112 / 128;
  background-image: url("../img/marvin-jump-sprite.png");
  background-size: 1100% 100%;
  animation: marvin-mascot-jump 1.1s steps(11, jump-none) 1;
}

@keyframes marvin-mascot-walk {
  from { background-position-x: 0%; }
  to { background-position-x: 100%; }
}

@keyframes marvin-mascot-jump {
  from { background-position-x: 0%; }
  to { background-position-x: 100%; }
}

/* Typewriter effect (used on short "// TAG" style lines, and on every
   H1/H2). Hidden until assets/js/animations.js actually starts typing it
   — otherwise the full text renders normally first (it's real HTML), then
   gets wiped and retyped the moment it scrolls into view, which reads as
   an unwanted "flash then rebuild." Same hide-then-reveal approach as
   .reveal below. */
[data-typewriter] {
  opacity: 0;
}

.type-cursor {
  color: var(--mint);
  animation: caret-blink 0.9s steps(1) infinite;
}

@keyframes caret-blink { 50% { opacity: 0; } }

/* Scroll reveal — slide + fade in from alternating sides */
.reveal {
  opacity: 0;
  transform: translateX(-32px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.reveal-right { transform: translateX(32px); }
.reveal.in-view { opacity: 1; transform: translateX(0); }

/* Note: the mascot idle, typewriter and scroll-reveal animations run
   unconditionally (not gated behind prefers-reduced-motion) so they're
   always visible — see assets/js/animations.js. */

/* Marvin corner widget — a living character, not a static click-toy.
   Styled as a real Game Boy-style handheld screen: pale-green LCD with
   dark-green duotone art, dark housing, twin accent stripe under the
   title bar. Idle / walk / talk use Marvin's real sprite frames from the
   original game project, recolored into the two-tone GB green — no
   invented poses. He rests at the bottom-right or bottom-left corner
   (never in between, so he's never sitting over page text) and
   occasionally walks across to the other side — assets/js/marvin-widget.js
   drives the transform, always ending up facing back in toward the page. */
.marvin-widget {
  position: fixed;
  left: 0;
  bottom: 1.1rem;
  z-index: 60;
  transition-property: transform;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.6rem;
  transform: translateX(0);
  /* Fixed to match .marvin-widget-bubble's width so opening/closing the
     panel never changes the container's own size — that used to shift
     the whole widget (positioned via JS-measured width) left or right
     depending on which child happened to be visible. */
  width: 200px;
}

/* At the left corner, everything hugs the container's left edge instead
   of the right — otherwise the panel/toast would grow off-screen past
   the left edge of the viewport. */
.marvin-widget.at-left {
  align-items: flex-start;
}

.marvin-widget-bubble {
  width: 200px;
  margin-bottom: 0;
  background: var(--ink);
  overflow: hidden;
}

.marvin-widget-bubble[hidden] { display: none; }

.marvin-widget-bubble .dialog-box-title {
  background: var(--gb-screen);
  color: var(--gb-pixel-dark);
}

.marvin-widget-bubble .dialog-box-title .dialog-box-close {
  border-color: var(--gb-pixel-dark);
  color: var(--gb-pixel-dark);
}

.marvin-widget-bubble .dialog-box-body {
  position: relative;
  background: var(--gb-screen);
  color: var(--gb-pixel-dark);
  text-shadow: none;
}

/* On the GB-screen background the typing caret should match the dark-green
   text there, not the site-wide mint used for the light-background
   typewriter headings. */
.marvin-toast .type-cursor,
.marvin-widget-bubble .type-cursor {
  color: var(--gb-pixel-dark);
}

/* CRT glass: scanlines + a soft vignette toward the corners, tinted with
   the screen's own dark green rather than neutral black. */
.marvin-widget-bubble .dialog-box-body::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at center, transparent 55%, rgba(15, 56, 15, 0.35) 100%),
    repeating-linear-gradient(to bottom, rgba(15, 56, 15, 0.15) 0, rgba(15, 56, 15, 0.15) 1px, transparent 1px, transparent 3px);
  pointer-events: none;
}

.marvin-widget-avatar {
  position: relative;
  display: block;
  line-height: 0;
  overflow: hidden;
  border: 3px solid var(--ink);
  background: var(--gb-screen);
  padding: 10px 12px 4px;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform 0.15s ease;
}

.marvin-widget-avatar:hover { transform: scale(1.08); }
.marvin-widget-avatar:active { transform: scale(0.97); }

/* Ground line — grounds Marvin on the little screen instead of floating. */
.marvin-widget-avatar::before {
  content: "";
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 5px;
  height: 2px;
  background: var(--gb-pixel-dark);
  opacity: 0.55;
}

/* CRT glass overlay, same treatment as the bubble screen. */
.marvin-widget-avatar::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at center, transparent 50%, rgba(15, 56, 15, 0.4) 100%),
    repeating-linear-gradient(to bottom, rgba(15, 56, 15, 0.15) 0, rgba(15, 56, 15, 0.15) 1px, transparent 1px, transparent 3px);
  pointer-events: none;
}

/* Idle by default: Marvin's real idle-animation frames, gently looping.
   Facing (the `scale` property, set from JS) and any small in-frame
   fidget position (the `translate` property) are kept independent of
   each other on purpose — only `translate` is transitioned, so a facing
   flip snaps instantly like a classic sprite flip instead of visibly
   rotating through itself. The frame (.marvin-widget-avatar) itself never
   moves for a fidget — only the sprite shifts inside it. */
.marvin-widget-avatar .mascot-sprite {
  aspect-ratio: 112 / 128;
  background-image: url("../img/marvin-idle-gb-sprite.png");
  background-size: 900% 100%;
  animation: marvin-idle-frames 1.5s steps(9, jump-none) infinite;
  transition-property: translate;
}

/* Walking: Marvin's real walk-cycle frames, used both for the corner-to-
   corner shuttle and for the small in-frame fidget shuffle. Facing (set
   from JS via the sprite's `scale`) matches direction of travel while
   this plays, then gets re-set for "facing inward" once at rest again. */
.marvin-widget-avatar.is-walking .mascot-sprite {
  background-image: url("../img/marvin-walk-gb-sprite.png");
  background-size: 800% 100%;
  animation: marvin-walk-frames 0.7s steps(8, jump-none) infinite;
}

/* Talking state (screen open): Marvin's real jump + land frames, paired
   with a matching hop/shimmy so he visibly jumps rather than just
   swapping pose in place. */
.marvin-widget-avatar.is-talking .mascot-sprite {
  background-image: url("../img/marvin-talk-gb-sprite.png");
  background-size: 1100% 100%;
  animation: marvin-talk-frames 1.1s steps(11, jump-none) infinite, marvin-talk-hop 1.1s ease-in-out infinite;
}

@keyframes marvin-idle-frames {
  from { background-position-x: 0%; }
  to { background-position-x: 100%; }
}

@keyframes marvin-talk-frames {
  from { background-position-x: 0%; }
  to { background-position-x: 100%; }
}

@keyframes marvin-walk-frames {
  from { background-position-x: 0%; }
  to { background-position-x: 100%; }
}

@keyframes marvin-talk-hop {
  0% { transform: translate(0, 0); }
  30% { transform: translate(-2px, -5px); }
  55% { transform: translate(2px, -4px); }
  80% { transform: translate(-1px, 0); }
  100% { transform: translate(0, 0); }
}

/* Compact auto-dismissing toast — spontaneous comments (welcome, jokes,
   tips, trivia, idle nudges, section remarks). Deliberately smaller and
   plainer than the click-triggered panel: no title bar, short text only,
   fades in/out on its own via .is-visible rather than being toggled with
   [hidden], so the dismissal transition can actually play. */
.marvin-toast {
  position: absolute;
  right: 0;
  bottom: 105px;
  max-width: 175px;
  padding: 0.5rem 0.65rem;
  background: var(--gb-screen);
  border: 2px solid var(--ink);
  box-shadow: var(--shadow);
  color: var(--gb-pixel-dark);
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.25;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.3s ease, transform 0.3s ease;
  pointer-events: none;
}

/* At the left corner the toast anchors from the left instead, so it grows
   rightward into the page rather than off the left edge of the viewport. */
.marvin-widget.at-left .marvin-toast {
  right: auto;
  left: 0;
}

.marvin-toast.is-visible { opacity: 1; transform: translateY(0); }

@media (max-width: 600px) {
  .marvin-widget { bottom: 0.8rem; width: 190px; }
  .marvin-widget-bubble { width: 190px; }
  .marvin-widget-avatar .mascot-sprite { width: 48px; }
  .marvin-toast { max-width: 145px; font-size: 0.85rem; bottom: 84px; }
}
