/* Interface for a listener who is 88 and nearly blind.

   The design job here is not decoration. Everything on screen exists to be
   read at arm's length by failing eyes, or to tell a sighted helper what the
   machine is doing. One element carries the whole interface -- the microphone
   -- and everything around it stays quiet so that it reads first. */

:root {
  --bg:        #0F1318;
  --amber:     #D9A441;   /* active state, and only active state */
  --text:      #F2EFE9;   /* warm off-white: pure white glares on dark at size */
  --muted:     #8C9099;   /* only for text that may be missed without loss */
  --ring:      rgba(217, 164, 65, 0.25);

  /* Type scale. Larger than the brief's floor throughout: 24px is a minimum
     for a general audience, not a target for this one. */
  --size-state: 42px;
  --size-reply: 34px;
  --size-heard: 24px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  /* The system stack is the deliberate choice, not the lazy one: on iPad it
     resolves to SF Pro, whose open apertures and large x-height are what make
     it legible at distance -- and it needs no download, so the page renders
     instantly on a home network. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  font-size: var(--size-reply);
  line-height: 1.4;
  /* Text selection and callouts get in the way when the whole page is a
     button: a long press should not offer to copy anything. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

#stage {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 40px;
  padding: max(32px, env(safe-area-inset-top)) 32px
           max(32px, env(safe-area-inset-bottom));
  text-align: center;
  cursor: pointer;
}

/* Keyboard focus stays visible. The space bar is one of the three ways in, and
   a helper using a keyboard needs to see where they are. */
#stage:focus-visible {
  outline: 4px solid var(--amber);
  outline-offset: -8px;
}

/* --- microphone --------------------------------------------------------- */

.mic {
  position: relative;
  width: 220px;
  height: 220px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #171C23;
  border: 3px solid #2A313B;
  transition: border-color 140ms ease, background 140ms ease;
}

.mic-glyph {
  width: 96px;
  height: 96px;
  fill: var(--muted);
  color: var(--muted);
  transition: fill 140ms ease, color 140ms ease;
}

/* The expanding ring is the only motion on the page, and it exists to answer
   an action: it appears when the microphone opens and stops when it closes.
   Nothing else moves, so this reads as information rather than decoration. */
.mic-ring {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 3px solid var(--amber);
  opacity: 0;
}

body.listening .mic {
  background: #1E1710;
  border-color: var(--amber);
}
body.listening .mic-glyph { fill: var(--amber); color: var(--amber); }
body.listening .mic-ring { animation: pulse 1.5s ease-out infinite; }

@keyframes pulse {
  0%   { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0;   transform: scale(1.35); }
}

/* Thinking has no animation on purpose. A spinner tells someone who cannot see
   it nothing, and the state line already says so in words. */
body.thinking .mic { border-color: #4A5260; }
body.thinking .mic-glyph { fill: #4A5260; color: #4A5260; }

body.speaking .mic-glyph { fill: var(--amber); color: var(--amber); }

/* Waiting for a yes or a no. Amber like listening, because the microphone is
   in fact open -- but steady rather than pulsing, so a helper can tell at a
   glance that this turn is a question being answered and not a new one.
   State is never carried by motion alone here, for the same reason. */
body.waiting .mic {
  background: #1E1710;
  border-color: var(--amber);
}
body.waiting .mic-glyph { fill: var(--amber); color: var(--amber); }
body.waiting .mic-ring { opacity: 0.9; }
body.waiting .state { color: var(--amber); }

/* Waiting for a yes or a no. Amber like listening, because the microphone is
   in fact open -- but steady rather than pulsing, so a helper can see at a
   glance that this turn is a question being answered and not a new one. */
body.waiting .mic {
  background: #1E1710;
  border-color: var(--amber);
}
body.waiting .mic-glyph { fill: var(--amber); color: var(--amber); }
body.waiting .mic-ring { opacity: 0.9; }
body.waiting .state { color: var(--amber); }

/* --- text --------------------------------------------------------------- */

.state {
  margin: 0;
  font-size: var(--size-state);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
}

body.listening .state { color: var(--amber); }

.reply {
  margin: 0;
  font-size: var(--size-reply);
  font-weight: 400;
  /* Under 80 characters per line, which at this size is roughly 18em. */
  max-width: 18em;
  color: var(--text);

  /* Scrolls rather than overflowing. On an iPad a long answer ran off the
     bottom of the screen and stopped mid-sentence, with nothing to show that
     more existed -- a sighted helper reading over the listener's shoulder had
     no way to see the rest.
     
     The height cap is in lines rather than pixels, so it holds across the type
     sizes below. Overscroll is contained so a flick inside the text does not
     drag the whole page. */
  max-height: 9em;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* A narrow, dim scrollbar: present enough to signal that there is more, quiet
   enough not to compete with the microphone for attention. */
.reply::-webkit-scrollbar { width: 6px; }
.reply::-webkit-scrollbar-thumb {
  background: #2A313B;
  border-radius: 3px;
}

/* What was heard, shown smaller. It matters when an answer looks wrong -- a
   helper can see whether the machine misheard or misunderstood -- but it is
   not the answer, so it does not compete with it. */
.heard {
  margin: 0;
  font-size: var(--size-heard);
  color: var(--muted);
  max-width: 22em;
  font-style: italic;
}
.heard:empty { display: none; }
.reply:empty { display: none; }

.playing {
  margin: 0;
  font-size: var(--size-heard);
  color: var(--amber);
}

/* --- reduced motion ------------------------------------------------------ */

/* With motion suppressed the ring still has to signal a live microphone, so it
   holds steady rather than disappearing. State must never be carried by
   animation alone. */
@media (prefers-reduced-motion: reduce) {
  body.listening .mic-ring {
    animation: none;
    opacity: 0.9;
  }
}

/* --- small screens ------------------------------------------------------- */

@media (max-height: 700px) {
  :root {
    --size-state: 34px;
    --size-reply: 28px;
  }
  .mic { width: 180px; height: 180px; }
  .mic-glyph { width: 78px; height: 78px; }
  #stage { gap: 28px; }
}
