/* =========================================================================
   GG chrome · viewport frame
   1600×900 desktop landscape · token-only · no literal hex.
   ========================================================================= */

@import url('https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Space+Grotesk:wght@400;500;600;700&display=swap');

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: var(--rd-font-body);
  background: var(--rd-dark);
  color: var(--rd-cream);
  font-size: 14px;
  line-height: 1.5;
}

.viewport {
  /* WEEK45: scale-as-one-unit. The viewport becomes a flex-centered window
     filler; the composition lives inside a fixed-size 1600×900 .stage that
     is transform-scaled by --fit. Mockup-60 proportions stay intact at any
     window size; iPad / mobile (M1.5) will swap in alternate stages at
     breakpoints, reusing the same pattern.

     --fit is the unitless scale factor written by chrome/fit.js on load +
     resize (transform: scale() requires a number, not a length — a CSS
     min(calc(100vw/1600), calc(100svh/900)) yields px and scale() rejects
     it). The :1 default keeps the design size visible if the script
     hasn't run yet. */
  --fit: 1;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100vw;
  height: 100vh;
  height: 100svh;
  background: var(--rd-dark);
  color: var(--rd-cream);
  overflow: hidden;
}
/* Fullscreen state — controls.js toggles this class. Inherits base .viewport dims today; add overrides here if fullscreen should differ (overflow/padding). */
.viewport.is-fullscreen {
  width: 100vw;
  height: 100svh;
}

/* WEEK45: the scale stage. Fixed at the mockup-60 design size (1600×900) so
   every child lays out in design pixels; transform: scale(var(--fit)) shrinks
   the whole composition together to fit the window. flex-shrink:0 keeps the
   stage at 1600×900 in layout (without it the flex parent would shrink it
   below design size and scale would have nothing to operate on).

   display:flex + center so the .vp-grid composition (constrained to board
   height in desktop.css) is vertically centered inside the 900px stage.
   modals + #rotate-prompt stay OUTSIDE .stage — position:fixed anchors to
   a transformed ancestor, not the viewport. */
.stage {
  position: relative;
  width: 1600px;
  height: 900px;
  flex-shrink: 0;
  transform: scale(var(--fit));
  transform-origin: center center;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ===== Rotation prompt (WEEK42 · mockup 76) =================================
   CSS-only landscape-enforcement overlay. Hidden by default; shown via the
   @media (orientation: portrait) rule below. Token-only colours. Sits above
   modals (z 300 > backdrops 200) so even a stuck modal can't hide it on a
   portrait reload. No JS, no engine — chrome-engine-contract §6 (chrome-
   local, never round-trips to the engine).
============================================================================= */
#rotate-prompt {
  position: fixed;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--rd-dark);
  z-index: 300;
}
#rotate-prompt .rp-card {
  background: var(--rd-glass-bg);
  border: 1px solid var(--rd-glass-border);
  border-radius: 10px;
  padding: 28px 32px;
  text-align: center;
  color: var(--rd-cream);
  font-family: var(--rd-font-serif);
  max-width: 420px;
}
#rotate-prompt .rp-logo {
  width: min(300px, 72vw);
  height: auto;
  display: block;
  margin: 0 auto 18px;
}
#rotate-prompt .rp-title {
  font-style: italic;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.3px;
  margin-bottom: 6px;
}
#rotate-prompt .rp-body {
  font-style: italic;
  font-size: 14px;
  color: var(--rd-cream-dim);
}

@media (orientation: portrait) {
  #rotate-prompt { display: flex; }
}

/* =========================================================================
   WEEK64 §4 T3 — shared clock-pulse keyframe (all tiers)
   ---------------------------------------------------------------------------
   Subtle 1s opacity pulse on the active.warning clock block (bank ≤ 20s).
   Lives here (viewport.css) so desktop / iPad / phone / phone-se can all
   reference it without duplication. Opacity-only = GPU-cheap (compositor
   thread) so the 1s loop is essentially free; the prefers-reduced-motion
   guard below switches it off for users who request reduced motion.
========================================================================= */
@keyframes ggClockPulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.72; }
}
@media (prefers-reduced-motion: reduce) {
  .pc-timer.active.warning {
    animation: none !important;
  }
}

/* =========================================================================
   WEEK66 §4e FIX-E4 — chat-button BLOCK keyframes (shared)
   ---------------------------------------------------------------------------
   chrome/chat.js signalBlocked(el) adds `.chat-blocked` to the clicked
   send/Chat control for ~380ms when the player tries to send while their
   balloon is still on screen. CSS owns the visible feedback: a quick
   translateX wobble (ggBlockShake) on the button + a brief 🚫 glyph that
   fades in/out on a ::after (ggBlockGlyph). The scoped style rules
   (which controls receive the effect on which tier, border colour, glyph
   size) live in phone.css and ipad.css under their respective
   [data-layout] selectors — desktop never enters the balloon path so
   no desktop selector references these keyframes.

   Lives here because phone.css forbids @media/@supports blocks under
   the tier-isolation guard, and the prefers-reduced-motion override
   needs a media query. Keyframes are global so the at-rule sits next
   to ggClockPulse above (both shared across tiers).
========================================================================= */
@keyframes ggBlockShake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-3px); }
  40% { transform: translateX(3px); }
  60% { transform: translateX(-2px); }
  80% { transform: translateX(2px); }
}
@keyframes ggBlockGlyph {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
  35%  { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.0); }
}
@media (prefers-reduced-motion: reduce) {
  .chat-blocked {
    animation: none !important;
  }
  .chat-blocked::after {
    animation: none !important;
    opacity: 1;
  }
}

/* =========================================================================
   WEEK67 · Settings popup · Pip count toggle (shared across all tiers)
   ---------------------------------------------------------------------------
   The Settings row "Pip count" toggles a `.pip-off` class on `.viewport`
   from chrome/controls.js. This rule hides every `.pc-pip` block (desktop
   right-col, iPad bottom-strip, phone strip) with one selector so the
   chrome doesn't need a tier branch. Default is ON — the class is only
   set when localStorage['progammon-set-pip'] === 'off'.
========================================================================= */
.viewport.pip-off .pc-pip {
  display: none;
}

/* =========================================================================
   WEEK74 §4 T3 · Spectate read-only foundation (shared across all tiers)
   ---------------------------------------------------------------------------
   chrome/spectate.js adds `.spectating` to the .viewport root when the
   chrome-local `spectate-active` flag (chrome/state.js) is on. The rules
   below suppress the on-board action affordances (ROLL/DOUBLE/END/UNDO
   on `.board-btn`, the autoroll fab on `.mb-autoroll`, and the resign
   .mb-btn.danger in both the desktop .menu-bar and the iPad .fab-menu /
   phone .ph-strip stacks) and freeze the canvas so the board is view-only.
   Profile / log / settings stay reachable; this is the read-only render
   seam only — the stripped-chrome Analyse UI is a later sprint.

   Default OFF in state.js → `.spectating` is never added → normal play
   is byte-identical. The engine never learns about spectate (file-75 §9).
========================================================================= */
.viewport.spectating .board-btn,
.viewport.spectating .mb-autoroll,
.viewport.spectating .mb-btn.danger {
  display: none;
}
.viewport.spectating #cv {
  pointer-events: none;
}
