/* microphone.css — /testers/microphone/
   The first screen is one button. Everything else waits behind a permission
   the visitor has not granted yet, so nothing else belongs above the fold. */

.mic-hero {
  text-align: center;
  padding: 2rem 0 2.5rem;
  max-width: 620px;
  margin: 0 auto;
}
.mic-hero h1 { margin-bottom: .75rem; }
.mic-hero-line {
  color: var(--text-sec);
  font-size: 1rem;
  margin-bottom: 2rem;
}
.mic-hero-note {
  font-size: .8125rem;
  color: var(--text-muted);
  margin-top: 1rem;
}

.mic-start {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 260px;
  padding: 1.125rem 2.5rem;
  border: none;
  border-radius: var(--r-lg);
  background: var(--primary);
  color: #fff;
  font-family: var(--font);
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -.01em;
  cursor: pointer;
  box-shadow: var(--sh-md);
  transition: background .15s, transform .1s, box-shadow .15s;
}
.mic-start:hover { background: var(--primary-hover); }
.mic-start:active { transform: scale(.98); }
.mic-start:focus-visible { outline: 3px solid var(--primary); outline-offset: 3px; }
.mic-start:disabled { background: var(--text-muted); cursor: default; }

/* Hidden once the test runs: the device list and the Stop button already cover
   everything a restart would do. */
.mic-hero.started { padding-bottom: 1.75rem; }
.mic-hero.started .mic-start { display: none; }
.mic-hero.started .mic-hero-line { margin-bottom: 0; }

.mic-block { margin-bottom: 1.25rem; }

/* ===== PERMISSION REFUSED ===== */
.mic-denied {
  border-color: #fde68a;
  background: var(--warning-bg);
  margin-bottom: 1.25rem;
}
.mic-denied-title { font-size: 1.125rem; margin-bottom: .625rem; }
.mic-denied p { font-size: .9375rem; color: #92400e; line-height: 1.7; }
.mic-steps {
  margin: .875rem 0 .875rem 1.25rem;
  font-size: .9375rem;
  color: #92400e;
  line-height: 1.7;
}
.mic-steps li { margin-bottom: .35rem; }
.mic-denied-foot { margin-top: .75rem; font-weight: 600; }

/* ===== LEVEL METER ===== */
/* A grid rather than flex so the scale's row lines up with the bar alone,
   not with the bar plus the dB readout beside it. */
.level-wrap {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: .875rem;
  row-gap: .25rem;
  align-items: center;
  margin-bottom: 1rem;
}
.level-bar {
  position: relative;
  height: 26px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  overflow: hidden;
}
/* The gradient spans the whole bar and the level only uncovers it, instead of
   squeezing into the fill: that way a color always marks the same level, and
   a quiet voice stays green while only a loud one reaches amber and red. */
.level-fill {
  height: 100%;
  background: linear-gradient(90deg, #22c55e 0%, #22c55e 55%, #f59e0b 78%, #ef4444 95%);
  clip-path: inset(0 100% 0 0);
  transition: clip-path .05s linear;
}
.level-peak,
.level-max {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  /* Right edge on the value: at 0 dB a marker starting there would sit wholly
     outside the bar and be clipped, and 0 dB is when it matters most. */
  transform: translateX(-100%);
  transition: left .08s linear;
}
.level-peak { background: var(--text); opacity: .55; }
.level-max { background: var(--error); }
/* Fixed to the longest reading, "-89.9 dB": a width that follows the number
   would drag the bar's edge along with every change. */
.level-db {
  font-family: var(--mono);
  font-size: .875rem;
  width: 8ch;
  white-space: nowrap;
  text-align: right;
  color: var(--text-sec);
}

.level-scale {
  grid-column: 1;
  position: relative;
  height: 1.125rem;
  font-family: var(--mono);
  font-size: .6875rem;
  line-height: 1;
  color: var(--text-muted);
}
.level-scale span {
  position: absolute;
  top: 0;
  padding-top: 6px;
  transform: translateX(-50%);
  white-space: nowrap;
}
.level-scale span::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  width: 1px;
  height: 4px;
  background: var(--text-muted);
}
/* The end labels align inward so they never spill past the bar's edges. */
.level-scale span:first-child { transform: none; }
.level-scale span:first-child::before { left: 0; }
.level-scale span:last-child { transform: translateX(-100%); }
.level-scale span:last-child::before { left: auto; right: 0; }

.wave {
  display: block;
  width: 100%;
  height: 120px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  margin-bottom: 1rem;
}

/* ===== DIAGNOSIS ===== */
/* Every message shares one grid cell and only the active one is visible, so
   the box always has the height of the longest message at the current width
   and the cards below do not jump when the verdict changes. */
.mic-diag { display: grid; }
.mic-diag-msg {
  grid-area: 1 / 1;
  visibility: hidden;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .875rem;
}
.mic-diag-msg.active { visibility: visible; }

/* ===== RECORDING ===== */
.card-h2 { font-size: 1.125rem; margin-bottom: .5rem; }
.mic-rec-note { font-size: .9375rem; color: var(--text-sec); line-height: 1.7; margin-bottom: 1rem; }

/* The player takes the space beside the button and shares that column with
   the countdown. The two never show at once, so neither shifts the other and
   the player keeps the same width take after take. */
.rec-row {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: .875rem;
  margin-bottom: 1rem;
}
.rec-timer, .playback { grid-column: 2; grid-row: 1; }
.rec-timer { display: inline-flex; align-items: center; gap: .4rem; font-size: .875rem; color: var(--text-sec); }
.rec-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--error);
  animation: rec-pulse 1s ease-in-out infinite;
}
@keyframes rec-pulse { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .rec-dot { animation: none; } }

.btn.recording { background: var(--error); }
.btn.recording:hover { background: #dc2626; }

.playback { width: 100%; }

/* Without a take the player is out of sight but keeps its place, so the row
   keeps one height and the button does not jump when a take starts or ends. */
.mic-rec:not(.has-take) .playback { visibility: hidden; }

@media (max-width: 560px) {
  .mic-start { width: 100%; min-width: 0; font-size: 1.125rem; padding: 1rem 1.25rem; }
  .wave { height: 90px; }
  .level-db { font-size: .8125rem; }
  /* Too narrow for the player beside the button; it drops to a line of its own,
     and a reserved empty line would only cost a phone screen space. */
  .rec-row { display: flex; flex-wrap: wrap; }
  .playback { flex-basis: 100%; }
  .mic-rec:not(.has-take) .playback { display: none; }
  /* .btn keeps its label on one line, and this label is wider than a 320px
     screen: let it wrap instead of pushing past the edge. */
  .mic-stop { white-space: normal; text-wrap: balance; }
  /* Seven labels start running into each other on a phone; every 20 dB still
     reads as a scale, and the ticks keep the 10 dB steps. */
  .level-scale .minor { visibility: hidden; }
  .level-scale .minor::before { visibility: visible; }
}
