/* The screen — docs/DESIGN.md §1–§5, §7, §8, drawn in CSS.
 *
 * The web twin of `ios/Sources/Screen/`. Every rectangle is positioned from
 * `ScreenLayout` in JS, in content-relative coordinates straight out of §4.1's
 * table, so this file carries type, colour, motion and the two or three things
 * that are cheaper to say in CSS than in arithmetic. It types no layout number
 * that JS also types.
 *
 * Composition, bottom to top — the same stack the native build composited:
 *
 *   ground → grain → content (clock · middle · faces · said) → band → strip → touch
 *
 * The band is `#band` and the strip's layer is `#panel`, both owned by the
 * shell's index.html; §8.5 puts the strip above everything including the band.
 */

/* Author rules beat the UA stylesheet, so a `display:` below would otherwise
 * override `[hidden]` — which is how "Nothing new yet." ended up drawn over a
 * message that had arrived. Caught in a headless render, not in review. */
.friendo-screen [hidden],
.op-strip[hidden] { display: none !important; }

/* ── the Panel (§1.2) ─────────────────────────────────────────────────────── */

.friendo-screen {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font);
  /* He will never open Settings, and the composition has no room to grow: one
   * hardcoded type floor, no adaptive geometry (§3.1). This is the web's
   * `dynamicTypeSize(.large)`. */
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
  color-scheme: dark;
  /* The complete gesture vocabulary is a single tap. Nothing scrolls, nothing
   * zooms, nothing is selectable, nothing shows a callout. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

/* iOS symmetrises the horizontal safe-area insets in landscape, so the Panel is
 * the same rect whichever way round the phone is and the composition never has
 * to know where the island is. Vertically it is the FULL height: landscape has
 * no top inset and the 21 pt bottom inset exists only for a home indicator that
 * Guided Access suppresses. Height is the scarce axis; we do not donate 21 pt of
 * it to a bar that is not on screen. */
.friendo-screen .panel {
  position: absolute;
  top: 0;
  bottom: 0;
  left: env(safe-area-inset-left, 0px);
  right: env(safe-area-inset-right, 0px);
}

.friendo-screen .content {
  position: absolute;
}

/* Brand material at 0.035, not the brand's 0.05: this ground runs at maximum
 * brightness behind 34 pt+ glyphs and visible noise subtracts from effective
 * contrast, which is the whole budget here. Directly above `ground`, below all
 * content — so it never lands over a photograph. Deterministic tile: the same
 * seed produces the same pixels in every capture. */
.friendo-screen .grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-repeat: repeat;
  background-size: 96px 96px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='96' height='96' filter='url(%23g)'/%3E%3C/svg%3E");
}

/* The one touch surface. Above the faces, not beneath them: the stop target is
 * everywhere and the faces row must not punch holes in it. */
.friendo-screen .touch {
  position: absolute;
  inset: 0;
  z-index: 3;
  touch-action: none;
}

/* ── landscape, and the one honest line when it is not (§1.5) ─────────────── */

.friendo-screen .rotate { display: none; }

@media (orientation: portrait) {
  /* iOS Safari cannot lock orientation from a page, and faking it with a CSS
   * rotation breaks every touch coordinate. So: lay out for landscape, and when
   * the phone is portrait say the one thing an operator can act on. */
  .friendo-screen .panel,
  .friendo-screen .touch { display: none; }
  .friendo-screen .rotate {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 0 40px;
    text-align: center;
    font-size: var(--caption-size);
    line-height: var(--caption-line);
    font-weight: 600;
    letter-spacing: var(--caption-tracking);
    color: var(--ink);
  }
}

/* ── the clock column (§4.1, §10 row 9) ──────────────────────────────────── */

.friendo-screen .clock { position: absolute; }

.friendo-screen .day,
.friendo-screen .time {
  position: absolute;
  display: flex;
  align-items: center;
  white-space: nowrap;
}

/* The clock is the only piece of furniture in this app that speaks in the
 * brand's Rounded register. */
.friendo-screen .day {
  font-family: var(--font-rounded);
  font-size: var(--day-size);
  line-height: var(--day-line);
  font-weight: 500;
  color: var(--ink-soft);
}

.friendo-screen .time {
  font-family: var(--font-rounded);
  font-size: var(--time-size);
  line-height: var(--time-line);
  font-weight: 600;
  color: var(--ink);
  /* Tabular digits, so nothing reflows on a "1" at the minute rollover. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ── the middle (§5) ─────────────────────────────────────────────────────── */

.friendo-screen .middle { position: absolute; inset: 0; }

.friendo-screen .photo-frame {
  position: absolute;
  overflow: hidden;
  border-radius: var(--photo-radius);
  /* A dark photograph on a #0E0D0B ground otherwise dissolves at the edge.
   * No spinner, no shimmer while it loads: the band is the only progress
   * indicator in this product. */
  box-shadow: inset 0 0 0 var(--hairline-width) var(--hairline);
}

/* So a portrait photograph does not leave two black wings. Dark enough (35%)
 * that it never competes with the photo or the ground. */
.friendo-screen .photo-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(40px) brightness(0.35) saturate(0.8);
  transform: scale(1.2); /* so the blur has pixels at the edge, not transparency */
}

/* **Aspect-FIT, centred. Never aspect-fill.** A cropped grandchild's face is
 * the failure mode of this screen, and fit cannot crop. `object-fit: contain`
 * is `ScreenLayout.fitted()` in CSS. */
.friendo-screen .photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Never any text over the photograph or the backdrop, in any state, for any
 * reason: we control the contrast of a solid colour and nothing about a
 * photograph. */

.friendo-screen .words {
  position: absolute;
  display: flex;
  align-items: center;      /* vertically centred in the 152 pt frame */
  overflow: hidden;
}

/* ≤ 3 lines, leading-aligned. The clamp sits on an inner span because
 * `-webkit-box` cannot also centre its own block vertically. */
.friendo-screen .words-text {
  width: 100%;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  text-align: left;
  font-size: var(--message-size);
  line-height: var(--message-line);
  font-weight: 600;
  letter-spacing: var(--caption-tracking);
  color: var(--ink);
}

.friendo-screen .caption {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;   /* centred on the photo frame's centre x */
  white-space: nowrap;
  font-size: var(--caption-size);
  line-height: var(--caption-line);
  font-weight: 600;
  letter-spacing: var(--caption-tracking);
  color: var(--ink);
}

/* No frame, no stroke, no placeholder graphic: an empty bordered rectangle
 * reads as a broken image, which is worse than nothing. */
.friendo-screen .empty {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  font-size: var(--empty-size);
  line-height: var(--empty-line);
  font-weight: 600;
  color: var(--ink-soft);
}

/* "Nothing new yet." is 16 characters and never wraps. The gate's instruction
 * (§9) is longer than the 458 pt frame at 40 pt, and a line that runs out of
 * the frame is worse than a line that takes two of the 208 pt it has. Centred
 * both ways, so it stays in the same place either way. */
.friendo-screen .empty.two-line {
  white-space: normal;
  text-align: center;
  text-wrap: balance;
}

/* ── the faces row (§4) ──────────────────────────────────────────────────── */

.friendo-screen .faces { position: absolute; inset: 0; }

.friendo-screen .tile {
  position: absolute;
  /* The hit rect is the full tile and it is hit-tested in JS; the DOM node is
   * inert so a touch that lands on a face still reaches the stop surface. */
  pointer-events: none;
}

.friendo-screen .face {
  position: absolute;
  box-sizing: border-box;
  border-radius: 50%;
  border: var(--hairline-width) solid var(--hairline);
  background: var(--ground);
  /* Touch-down: `FPressStyle`'s in-curve. Two frames — the acknowledgement must
   * land with the finger. */
  transition: transform var(--press-out), left var(--morph), top var(--morph),
              width var(--morph), height var(--morph), border-color 200ms ease-out;
}

.friendo-screen .face.pressed {
  transform: scale(0.94);
  transition: transform var(--press-in);
}

/* Something waiting says so with a warm ring — the only pre-attentive cue on a
 * screen with no badges, and never the sole carrier, because the words sit
 * directly beneath it (§4.6, §11.6). */
.friendo-screen .face.has-waiting {
  border-width: 3px;
  border-color: var(--warm);
}

.friendo-screen .face-media {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
}

/* An initial on a warm-toned circle. The warm is at 0.14 — it stands in for a
 * PHOTOGRAPH, which is content, not chrome, so it does not spend one of the
 * three sanctioned uses of `warm` on the interface. A missing file degrades to
 * this and a real JPEG later just replaces it. */
.friendo-screen .face-initial {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(var(--warm-rgb), 0.14);
  color: var(--ink);
  font-weight: 600;
  font-size: calc(var(--face-d, 68px) * 0.42);
}

.friendo-screen .face-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Pre-cropped square with the face centred, so fill cannot crop a face. */
  object-fit: cover;
  opacity: 0;
}

.friendo-screen .face-media.has-photo .face-photo { opacity: 1; }
.friendo-screen .face-media.has-photo .face-initial { opacity: 0; }

/* Friendo's tile: the wave, not a face. Same silhouette, same slot — it is
 * someone you can reach, not a mode you switch into. */
.friendo-screen .friendo-wave {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.friendo-screen .friendo-wave path {
  stroke: var(--warm);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The band's own three passes at half opacity on the tile: 22 / 9 / core. */
.friendo-screen .friendo-wave .bloom-outer {
  filter: blur(var(--band-bloom-outer));
  opacity: calc(0.3 * var(--wave-alpha, 0.6) * 0.5);
  mix-blend-mode: plus-lighter;
}
.friendo-screen .friendo-wave .bloom-inner {
  filter: blur(var(--band-bloom-inner));
  opacity: calc(0.55 * var(--wave-alpha, 0.6) * 0.5);
  mix-blend-mode: plus-lighter;
}
.friendo-screen .friendo-wave .core { opacity: var(--wave-alpha, 0.6); }

/* Names are rendered at 34 pt ALWAYS and scaled to 0.8235 in standby, never
 * re-typeset: animating a font size re-lays-out every frame and the text
 * jitters, while scaling a rasterised glyph down is stable and stays crisp. */
.friendo-screen .name {
  position: absolute;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  font-size: var(--name-big-size);
  line-height: var(--name-big-line);
  font-weight: 600;
  letter-spacing: var(--name-tracking);
  color: var(--ink);
  transform-origin: 50% 50%;
  transition: top var(--morph), transform var(--morph);
}

.friendo-screen.is-standby .name { transform: scale(var(--name-standby-scale)); }
.friendo-screen.is-reaching .name { transform: none; }

.friendo-screen .waiting {
  position: absolute;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  font-size: var(--waiting-size);
  line-height: var(--waiting-line);
  font-weight: 500;
  color: var(--ink-soft);
}

/* ── the state change (§7.1) ─────────────────────────────────────────────── */
/* The delays differ by direction, and each direction's rule lives on the state
 * it is going TO — which is how "going in, the words follow the faces; coming
 * back, the photograph waits for the faces to land" falls out of CSS. */

.friendo-screen.is-standby .waiting { opacity: 0; transition: opacity 200ms ease-out; }
.friendo-screen.is-reaching .waiting { opacity: 1; transition: opacity 250ms ease-out 200ms; }

.friendo-screen.is-standby .middle {
  opacity: 1;
  transform: none;
  transition: opacity 300ms ease-out 200ms, transform 300ms ease-out 200ms;
}
.friendo-screen.is-reaching .middle {
  opacity: 0;
  transform: scale(0.96);
  transition: opacity 250ms ease-out, transform 250ms ease-out;
}

.friendo-screen.is-standby .clock { opacity: 1; transition: opacity 300ms ease-out 200ms; }
.friendo-screen.is-reaching .clock { opacity: 0; transition: opacity 200ms ease-out; }

/* ── the words across the bottom (§5.4, §3.4) ────────────────────────────── */

.friendo-screen .said {
  position: absolute;
  display: flex;
  /* Bottom-anchored to the content rect, centred horizontally — the largest
   * thing in the product and the only element still at the threshold of
   * legibility at a metre. Centred, because that is the easiest position to
   * find on a dark screen at distance. */
  align-items: flex-end;
  justify-content: center;
}

.friendo-screen.is-standby .said { opacity: 0; transition: opacity 200ms ease-out; }
.friendo-screen.is-reaching .said { opacity: 1; transition: opacity 250ms ease-out 150ms; }

/* ONE text node in two inline runs. Two block elements line-break independently
 * and the sentence tears. No `minimumScaleFactor` equivalent anywhere: text
 * that shrinks to fit hides a copy bug, and the budget is 56 characters,
 * enforced in the said machine upstream of this. */
.friendo-screen .said-line {
  margin: 0;
  width: 100%;
  text-align: center;
  font-size: var(--said-size);
  line-height: var(--said-line);
  font-weight: var(--said-weight);
  letter-spacing: var(--said-tracking);
  color: var(--ink);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.friendo-screen .said-line.fresh { animation: said-replace var(--said-replace); }

@keyframes said-replace { from { opacity: 0; } to { opacity: 1; } }

.friendo-screen .said-spoken { color: var(--ink); }

/* Not yet spoken: `ink` at 0.55, NOT the brand's 0.45 — 0.45 measures 3.9:1 and
 * fails AA. Words cross from here to full ink on the audio cursor, 120 ms. */
.friendo-screen .said-ahead {
  color: var(--ahead);
  transition: color var(--read-along);
}

/* ── the hidden panel (§8) ───────────────────────────────────────────────── */

.op-strip {
  position: fixed;
  z-index: 20;
  box-sizing: border-box;
  padding: 16px;
  /* Matte, not glass: glass over a flat fill has nothing to refract and renders
   * as a flat tinted rectangle. Matte is more honest, and 14.3:1. */
  background: var(--elevated);
  border: 1px solid var(--panel-stroke);
  border-radius: var(--sheet-radius);
  color: var(--ink);
  font-family: var(--font);
  pointer-events: auto;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  /* Slide from below + fade, `FMotion.snap`, 0.28 s. */
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--snap), transform var(--snap);
}

.op-strip.open { opacity: 1; transform: none; }

.op-row {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 100%;
}

.op-pill {
  flex: 0 0 auto;
  height: 56px;
  border: 0;
  border-radius: 999px;
  background: rgba(237, 232, 223, 0.12);
  color: var(--ink);
  font: 600 20px/1 var(--font);
  -webkit-appearance: none;
  appearance: none;
}

.op-slider { flex: 0 0 auto; height: 64px; }

.op-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  height: 20px;
  margin-bottom: 8px;
}

/* Lower case is the brand's section-header idiom. These three sit below the
 * 22 pt floor DELIBERATELY: they are read by us, at 30 cm, holding the phone on
 * purpose, and they live only inside this strip. */
.op-label {
  font-size: var(--op-label-size);
  font-weight: 600;
  color: var(--ink-soft);
}

.op-value {
  font-family: var(--font-mono);
  font-size: var(--op-value-size);
  color: var(--ink);
}

.op-track {
  position: relative;
  height: 36px;
  touch-action: none;
}

.op-track::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 16px;
  height: 4px;
  border-radius: 2px;
  background: rgba(237, 232, 223, 0.15);
}

.op-fill {
  position: absolute;
  left: 0;
  top: 16px;
  height: 4px;
  border-radius: 2px;
  background: rgba(237, 232, 223, 0.5);
}

.op-thumb {
  position: absolute;
  top: 4px;
  width: 28px;
  height: 28px;
  margin-left: -14px;
  border-radius: 50%;
  background: var(--ink);
}

/* The only sanctioned use of `inkFaint` in the app. */
.op-stamp {
  flex: 0 0 auto;
  /* 80 + 124 + 100 + 108 + 112 + 56 + (5 × 12) = 640 in 646 — six points of
   * slack, the same as §8.3's own total. An auto margin, not a spacer element:
   * a spacer would be a seventh child and cost a seventh gutter. */
  margin-left: auto;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--op-stamp-size);
  line-height: 14px;
  color: var(--ink-faint-operator-only);
}

/* The live readout, floated above the strip so §8.3's six-column arithmetic is
 * untouched. Mono, faint, operator-only, and it exists only while two fingers
 * are holding the panel open. */
.op-readout {
  position: fixed;
  z-index: 20;
  box-sizing: border-box;
  padding: 0 4px;
  height: 16px;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--op-stamp-size);
  line-height: 16px;
  color: var(--ink-faint-operator-only);
  pointer-events: none;
}
.op-readout[hidden] { display: none !important; }

@media (orientation: portrait) { .op-strip, .op-readout { display: none; } }

/* ── Reduce Motion (§7.4) ────────────────────────────────────────────────── */
/* Crossfade, 0.30 s. No travel, no scaling — the standby composition fades out
 * and the reaching composition fades in with the faces already at their final
 * size and position. Press feedback is KEPT: it is direct manipulation, not
 * vestibular motion. The band's listening response is never switched off for
 * anybody, and that rule lives in band.js. */

@media (prefers-reduced-motion: reduce) {
  .friendo-screen .face,
  .friendo-screen .name {
    transition: border-color 200ms ease-out;
  }
  .friendo-screen .face.pressed { transition: transform var(--press-in); }
  .friendo-screen.is-standby .middle,
  .friendo-screen.is-reaching .middle,
  .friendo-screen.is-standby .clock,
  .friendo-screen.is-reaching .clock,
  .friendo-screen.is-standby .said,
  .friendo-screen.is-reaching .said,
  .friendo-screen.is-standby .waiting,
  .friendo-screen.is-reaching .waiting {
    transition: opacity var(--crossfade);
  }
  .friendo-screen.is-reaching .middle { transform: none; }
  /* The whole sentence at α 1.00 immediately, with no cursor — handled in JS,
   * which reads the same media query. */
  .friendo-screen .said-ahead { transition: none; }
  .friendo-screen .said-line.fresh { animation: none; }
  .op-strip { transition: opacity var(--crossfade); transform: none; }
}
