/* system/apps/live-touch-room/styles.css
 *
 * Sept 22 2026. See app.js's own header for the coordinate-space
 * design (logical box-space vs. screen pixels) this all renders on
 * top of, and README.md for what the app is for.
 */

.live-touch-room {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* Fixed height so .ltr-box's 50% below is 50% of something real,
     not an intrinsic/content-driven number. First-pass choice --
     worth checking on a real device against what "50% of the app's
     current render" should actually mean once apps can be partial-
     screen (see app.js's header). */
  height: 70vh;
}

.ltr-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--slate);
}

.ltr-buttons {
  display: flex;
  gap: 8px;
}

.ltr-status {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--slate);
}

.ltr-hint {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--slate);
}

/* The bounding box: width of the app's own render area, 50% of its
   height -- measured from THIS element's own getBoundingClientRect()
   in app.js, never window.innerWidth/innerHeight, so the same math
   keeps working if an app is ever rendered partial-screen. */
.ltr-box {
  position: relative;
  width: 100%;
  height: 50%;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper, #f4f2ee);
  touch-action: none; /* we own every gesture in here ourselves */
}

/* The "camera" -- everything pan/zoom actually transforms. Its
   NATURAL (zoom=1) size is defined to equal .ltr-box's own on-screen
   size (see app.js's measureCameraNaturalSize()); ghosts and viewport
   boxes are deliberately NOT children of this element (see app.js's
   logicalToScreenLocal() comment) so their own physical size never
   scales with zoom, only their position does. */
.ltr-camera {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transform-origin: center center;
  will-change: transform;
}
.ltr-gesture .ltr-camera {
  /* Same "only promote to a composited layer during an active
     gesture" reasoning as WordFlex's own .scrabble-board-gesture --
     keeps the grid crisp once a pinch/pan settles instead of staying
     softly rasterized at the gesture's last zoom level. */
  will-change: transform;
}

/* Test grid: 4 across, 6 down (Ron's own numbers) -- a short, wide box
   containing a tall, thin grid on purpose, specifically so the
   zoom/pan/off-screen tests have an aspect-ratio mismatch to prove
   themselves against. Scaled down via aspect-ratio + max-width/height
   rather than any JS math -- centered and letterboxed by the camera's
   own flex centering. */
.ltr-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(6, 1fr);
  aspect-ratio: 4 / 6;
  max-width: 100%;
  max-height: 100%;
  width: 100%;
}
.ltr-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: var(--fs-lg, 1.25rem);
  font-weight: 600;
  color: var(--ink);
}
.ltr-cell:nth-child(odd) { background: #ffffff; }
.ltr-cell:nth-child(even) { background: #d9d5cc; }

/* ---------------- ghosts ---------------- */

/* Three-quarter-inch (Ron, Sept 22 2026 -- was half an inch in the
   first version), per Ron: "the human finger doesn't change size... it
   should always represent a human touch." CSS's own inch (1in = 96
   reference px, display-adjusted by the browser) gets this close
   enough to true-to-life on any device without our own DPI math.
   Positioned via left/top in app.js (logicalToScreenLocal()), never
   by living inside the zoomed .ltr-camera -- that's what keeps this
   size fixed regardless of current zoom. */
.ltr-ghost {
  position: absolute;
  width: 0.75in;
  height: 0.75in;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: color-mix(in srgb, var(--ltr-color, #3d7fd9) 55%, transparent);
  border: 2px solid var(--ltr-color, #3d7fd9);
  pointer-events: none;
  z-index: 5;
}
.ltr-ghost-own {
  /* So the fuzzy glow below is ink-colored too, not the blue default. */
  --ltr-color: var(--ink);
  background: color-mix(in srgb, var(--ink) 35%, transparent);
  border: 2px solid var(--ink);
  z-index: 6;
}

/* Fuzzy mode: same dot, softened into a glow instead of a hard edge.
   Trail mode uses the fuzzy ghost too (Ron, Sept 22 2026: the trail is
   drawn as fuzzy ghosts, "both for the gesture location and all along
   the tail" -- see app.js's fuzzySprite(), which approximates this
   same look on canvas). */
.ltr-mode-fuzzy .ltr-ghost,
.ltr-mode-trail .ltr-ghost {
  border: none;
  filter: blur(4px);
  box-shadow: 0 0 18px 6px color-mix(in srgb, var(--ltr-color, #3d7fd9) 70%, transparent);
}

/* Off-screen indicator: a small wedge pointing the TRUE direction,
   drawn at the clamped edge position -- Ron's third test scenario
   ("draw the ghost at the edge... with something that indicates the
   ghost is actually farther off screen in that direction"). --ltr-
   angle is set in app.js's placeGhost(). */
.ltr-ghost.is-clamped::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 7px 0 7px 12px;
  border-color: transparent transparent transparent var(--ltr-color, #3d7fd9);
  transform: translate(-3px, -7px) rotate(var(--ltr-angle, 0rad));
  transform-origin: 3px 7px;
}

/* Trail mode: per session, a run of fuzzy ghosts interpolated between
   the 5Hz sample points, shrinking and fading from the head to the
   oldest end (Sept 22 2026, Ron's own algorithm -- see app.js's
   TRAIL_SUBDIVISIONS), drawn every animation frame onto this one
   canvas by app.js's drawTrail(). Sized to the box in device
   pixels by app.js; this rule only places it. Below the ghosts
   (z-index 5) so a ghost sits on top of its own tail's head. */
.ltr-trail-canvas {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
  z-index: 4;
}
/* Circle/fuzzy modes don't draw tails at all (app.js skips the work
   when ghostMode !== 'trail'); hiding the canvas too is a defensive
   fallback against a stale last frame. */
.ltr-mode-circle .ltr-trail-canvas,
.ltr-mode-fuzzy .ltr-trail-canvas {
  display: none;
}

/* ---------------- viewport boxes ---------------- */

/* What part of the shared surface someone else is currently looking
   at -- persisted server-side (see room.ts), not a live trace, so it
   only moves when THEY pan/zoom, never continuously. Border width is
   deliberately fixed, same reasoning as the ghost's physical size --
   a "camera frame" shouldn't visually thicken just because someone
   else zoomed in. */
.ltr-viewport-box {
  position: absolute;
  border: 2px solid var(--ltr-color, #3d7fd9);
  border-radius: 4px;
  background: color-mix(in srgb, var(--ltr-color, #3d7fd9) 8%, transparent);
  pointer-events: none;
  z-index: 3;
}
