/* Components.
 *
 * Two rules hold throughout, both from screens.md "Responsive rules":
 *   - every multi-column grid is auto-fit minmax(), never fixed columns
 *   - every side-by-side row wraps, and its text column gets min-width: 0
 * Together they are why nothing clips at 360px. A fixed column count or a flex
 * child without min-width: 0 is what causes the overflow they prevent.
 */

/* --- App shell -------------------------------------------------------------- */

.shell {
  min-height: 100dvh;
}

.main {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: var(--s-6) var(--s-5);
  /* Clear of the tab bar, plus the home indicator on notched phones. */
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--s-8));
}

/* Bottom tab bar (mobile). */
.tabbar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 20;
  display: flex;
  background: var(--ink);
  padding-bottom: env(safe-area-inset-bottom);
}

.tabbar__item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: var(--tabbar-h);
  padding: var(--s-2) var(--s-1);
  text-decoration: none;
  color: var(--text-on-ink-muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
}

.tabbar__item[aria-current="page"] {
  color: var(--text-on-ink);
  background: var(--ink-raised);
  /* The active marker is a bar as well as a colour: colour alone is not a
   * sufficient signal. */
  box-shadow: inset 0 3px 0 var(--red);
}

.tabbar__icon {
  width: 22px;
  height: 22px;
}

.tabbar__badge {
  min-width: 18px;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: var(--blue);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  line-height: 18px;
  text-align: center;
}

/* Sidebar (desktop). Hidden on mobile; the tab bar takes over.
 *
 * display:none rather than visual hiding on purpose: both navs are in the DOM
 * at every width, and an offscreen-but-focusable duplicate would mean tabbing
 * through ten nav links to reach the page. display:none removes it from the
 * tab order and the accessibility tree together. */
.sidebar {
  display: none;
}

@media (min-width: 900px) {
  .tabbar {
    display: none;
  }

  .shell {
    display: grid;
    grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  }

  .sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--s-6);
    position: sticky;
    top: 0;
    height: 100dvh;
    /* Never compresses; scrolls internally. */
    width: var(--sidebar-w);
    flex: 0 0 var(--sidebar-w);
    overflow-y: auto;
    padding: var(--s-6) var(--s-4);
    background: var(--ink);
    color: var(--text-on-ink);
  }

  .sidebar__nav {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
  }

  .sidebar__item {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    min-height: var(--tap-min);
    padding: 0 var(--s-3);
    border-radius: var(--r-tile);
    text-decoration: none;
    color: var(--text-on-ink-muted);
    font-size: 14px;
    font-weight: 600;
  }

  .sidebar__item:hover {
    background: var(--ink-raised);
    color: var(--text-on-ink);
  }

  .sidebar__item[aria-current="page"] {
    background: var(--ink-raised);
    color: var(--text-on-ink);
    box-shadow: inset 3px 0 0 var(--red);
  }

  /* A button rather than a link (sign-out has to be a POST), otherwise
     identical to .sidebar__item -- reset the button-specific defaults a
     <button> carries that an <a> never did. */
  .sidebar__item--button {
    width: 100%;
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
  }

  .main {
    max-width: 1100px;
    padding: var(--s-10) var(--s-8);
    padding-bottom: var(--s-12);
  }
}

/* The app mark next to the wordmark. */
.brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  text-decoration: none;
}

.brand svg {
  flex-shrink: 0;
}

.brand__name {
  font-family: var(--font-display);
  font-size: 24px;
  color: var(--text-on-ink);
}

/* Offline status block in the sidebar. Not error-styled: offline is a supported
 * mode, not a failure. */
.sidebar__status {
  margin-top: auto;
  padding: var(--s-3);
  border-radius: var(--r-tile);
  background: var(--ink-raised);
  color: var(--text-on-ink-muted);
  font-size: 12px;
}

/* --- Cards ------------------------------------------------------------------ */

.card {
  padding: var(--card-pad);
  border-radius: var(--r-card-lg);
  background: var(--cream);
  border: 1px solid var(--border);
}

.card--ink {
  background: var(--ink);
  border-color: var(--ink-line);
  color: var(--text-on-ink);
}

.card--ink h1,
.card--ink h2,
.card--ink h3 {
  color: var(--text-on-ink);
}

.card--warm {
  background: var(--sand-warm);
  border-color: var(--border-dashed);
}

.card--dashed {
  border: 2px dashed var(--border-dashed);
  background: transparent;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}

.stack--tight {
  gap: var(--s-3);
}

.row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

/* The text column in any side-by-side row. Without this a long Maa word makes
 * the flex item refuse to shrink and the row overflows the card. */
.row__text {
  flex: 1 1 0;
  min-width: 0;
}

/* --- Stat tiles ------------------------------------------------------------- */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: var(--s-3);
}

@media (min-width: 900px) {
  .stats {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  }
}

.tile {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-4);
  border-radius: var(--r-tile);
  background: var(--cream);
  border: 1px solid var(--border);
  color: var(--text-body);
  text-decoration: none;
  min-width: 0;
}

.tile__label {
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--text-muted);
}

.tile--blue {
  background: var(--blue);
  border-color: var(--blue);
  color: #fff;
}

.tile--green {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
}

.tile--ink {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--text-on-ink);
}

/* First-ever day: nothing has been achieved, so the due tile is neutral. Green
 * here would claim an accomplishment that has not happened. */
.tile--neutral {
  background: var(--sand);
  border-color: var(--border);
  color: var(--text-muted);
}

.tile--blue .tile__label,
.tile--green .tile__label,
.tile--ink .tile__label {
  color: rgb(255 255 255 / 0.82);
}

/* --- Buttons ---------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--tap-min);
  padding: var(--s-3) var(--s-5);
  border-radius: var(--r-pill);
  font-size: 15px;
  font-weight: 800;
  text-decoration: none;
  text-align: center;
  border: 1px solid transparent;
}

.btn--block {
  width: 100%;
}

.btn--ink {
  background: var(--ink);
  color: var(--text-on-ink);
}

.btn--ink:hover {
  background: var(--ink-raised);
}

.btn--ochre {
  background: var(--ochre);
  color: var(--ink);
}

.btn--ochre:hover {
  background: var(--ochre-hover);
}

.btn--green {
  background: var(--green);
  color: #fff;
}

.btn--green:hover {
  background: var(--green-hover);
}

.btn--blue {
  background: var(--blue);
  color: #fff;
}

.btn--blue:hover {
  background: var(--blue-hover);
}

.btn--quiet {
  background: var(--sand);
  color: var(--text-body);
  border-color: var(--border);
}

/* Looks like a link, but it is still something to hit on a phone, so it keeps
   the tap minimum the other buttons have. The underline sits on the text rather
   than the box, so the extra height is padding and does not look like a slab. */
.btn--link {
  min-height: var(--tap-min);
  padding: var(--s-2);
  background: none;
  color: var(--text-muted);
  font-weight: 600;
  text-decoration: underline;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--border);
  color: var(--text-muted);
  cursor: not-allowed;
}

/* Icon-only controls still meet the 44px minimum even when the glyph is 20px.
 * The reference's small play and + buttons are under it; this is the fix. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-pill);
  background: var(--blue);
  color: #fff;
}

.btn-icon--lg {
  width: 46px;
  height: 46px;
}

.btn-icon--quiet {
  background: var(--sand);
  color: var(--text-body);
}

/* Audio that has not been downloaded. Dimmed with a download glyph, never
 * hidden and never error-styled: it is not broken, it is not here yet. */
.btn-icon--uncached {
  background: var(--sand);
  color: var(--text-muted);
  opacity: 0.72;
}

.btn-icon svg {
  width: 20px;
  height: 20px;
}

/* --- Chips ------------------------------------------------------------------ */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* A chip is a label by default: 36px reads correctly beside a word and is not
   something anyone taps. */
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 0 var(--s-4);
  border-radius: var(--r-pill);
  background: var(--sand);
  border: 1px solid var(--border);
  color: var(--text-body);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}

/* A chip that is a link or a button is a target, so it takes the tap minimum.
   Scoped by element rather than by a modifier class: the filter chips are plain
   anchors, and a rule keyed off markup cannot be forgotten when a new one is
   added. */
a.chip,
button.chip {
  min-height: var(--tap-min);
}

.chip[aria-pressed="true"],
.chip--active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--text-on-ink);
}

.chip--word {
  background: var(--feedback-wrong-bg);
  border-color: var(--red);
  color: var(--red-deep);
  font-weight: 700;
}

/* --- Progress --------------------------------------------------------------- */

.progress {
  flex: 1 1 0;
  min-width: 0;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--border);
  overflow: hidden;
}

.progress__fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--red);
}

.progress__fill--green {
  background: var(--green);
}

.progress--on-ink {
  background: var(--ink-line);
}

/* --- The path --------------------------------------------------------------- */

.path {
  display: flex;
  flex-direction: column;
}

.path__item {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
}

/* The connector is drawn on the node column so it lines up with the circle
 * centre regardless of how tall the label wraps. */
.path__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  align-self: stretch;
  flex: 0 0 auto;
}

.path__node {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-pill);
  background: var(--sand);
  border: 3px dashed var(--border-dashed);
  color: var(--text-muted);
  font-weight: 800;
  font-size: 14px;
  flex: 0 0 auto;
}

@media (min-width: 900px) {
  .path__node {
    width: 48px;
    height: 48px;
  }
}

.path__node--done {
  background: var(--green);
  border: 3px solid var(--green);
  color: #fff;
}

.path__node--today {
  background: var(--red);
  border: 3px solid var(--red);
  color: #fff;
}

.path__connector {
  flex: 1 0 20px;
  width: 3px;
  min-height: 20px;
  background: var(--border-dashed);
}

.path__connector--done {
  background: var(--green);
}

.path__label {
  flex: 1 1 0;
  min-width: 0;
  padding: var(--s-2) 0 var(--s-6);
}

.path__label--locked {
  color: var(--text-muted);
}

/* --- Answer options ---------------------------------------------------------- */

.options {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-3);
}

@media (min-width: 900px) {
  .options {
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  }
}

.option {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  min-height: 60px;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-tile);
  background: var(--cream);
  border: 2px solid var(--border);
  text-align: left;
}

.option__text {
  flex: 1 1 0;
  min-width: 0;
}

.option__maa {
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
}

.option__gloss {
  font-size: 13px;
  color: var(--text-muted);
}

/* The mark slot is reserved at all times so selecting an answer does not
 * reflow the row. */
.option__mark {
  flex: 0 0 24px;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.option[aria-checked="true"],
.option--selected {
  background: var(--feedback-selected-bg);
  border-color: var(--ochre);
}

/* Correct and wrong are signalled three ways at once — border, fill and a ✓/✕
 * mark in the slot. Colour alone would fail for colour-blind users. */
.option--correct {
  background: var(--feedback-correct-bg);
  border-color: var(--green);
}

.option--wrong {
  background: var(--feedback-wrong-bg);
  border-color: var(--red);
}

/* --- Flashcard --------------------------------------------------------------- */

.flashcard {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-4);
  width: 100%;
  min-height: 290px;
  padding: var(--s-6);
  border-radius: var(--r-flashcard);
  background: var(--ink);
  color: var(--text-on-ink);
  text-align: center;
}

@media (min-width: 900px) {
  .flashcard {
    min-height: 380px;
  }
}

/* Sits inside .btn--quiet, which is sand, not ink -- so it takes the light-surface
   muted colour. The on-ink one measured 1.76:1 here. */
.flashcard__hint {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.flashcard__rule {
  width: 60%;
  height: 1px;
  background: var(--ink-line-strong);
}

.flashcard__answer {
  color: var(--text-on-ink);
}

/* Grade row. Again and Hard carry their own light backgrounds; Good is ink. */
.grades {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: var(--s-2);
}

.btn--again {
  background: var(--grade-again-bg);
  color: var(--grade-again-fg);
}

.btn--hard {
  background: var(--grade-hard-bg);
  color: var(--grade-hard-fg);
}

/* --- Review queue rail (desktop) --------------------------------------------- */

.queue-rail {
  display: none;
}

@media (min-width: 900px) {
  .review-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 280px;
    gap: var(--s-6);
    align-items: start;
  }

  .queue-rail {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    max-height: 70dvh;
    overflow-y: auto;
  }

  .queue-rail__item {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    padding: var(--s-2) var(--s-3);
    border-radius: var(--r-tile);
    min-width: 0;
  }

  .queue-rail__dot {
    flex: 0 0 8px;
    width: 8px;
    height: 8px;
    border-radius: var(--r-pill);
    background: var(--border-dashed);
  }

  .queue-rail__dot--current {
    background: var(--red);
  }

  .queue-rail__dot--done {
    background: var(--green);
  }
}

/* --- Day gate ---------------------------------------------------------------- */

.gate {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  background: var(--red);
}

/* Returning after days away is blue, not red. A different state should not
 * look like the ordinary one. */
.gate--away {
  background: var(--blue);
}

.gate__field {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s-5);
  padding: var(--s-8) var(--s-5);
  color: var(--gate-body);
}

.gate__rule {
  width: 72px;
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--ochre);
}

.gate__brand {
  margin-bottom: var(--s-4);
  width: 200px;
}

.gate__brand svg {
  width: 100%;
  height: auto;
}

@media (min-width: 900px) {
  .gate__brand {
    width: 240px;
  }
}

.gate__sheet {
  padding: var(--s-6) var(--s-5);
  padding-bottom: calc(var(--s-6) + env(safe-area-inset-bottom));
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  background: var(--cream);
}

.gate__dots {
  display: flex;
  gap: 6px;
}

.gate__dot {
  width: 7px;
  height: 7px;
  border-radius: var(--r-pill);
  background: rgb(255 255 255 / 0.32);
}

.gate__dot--done {
  background: var(--ochre);
}

@media (min-width: 900px) {
  .gate {
    flex-direction: row;
    align-items: stretch;
  }

  .gate__field {
    max-width: 900px;
    padding: var(--s-12);
  }

  .gate__sheet {
    flex: 0 1 42vw;
    min-width: 320px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    border-radius: 0;
    padding: var(--s-12) var(--s-8);
  }
}

/* --- Week chart --------------------------------------------------------------- */

.week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--s-2);
  align-items: end;
  height: 96px;
}

.week__bar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.week__fill {
  width: 100%;
  border-radius: var(--r-pill);
  background: var(--ink);
  /* An empty day is a 5px stub rather than nothing, so the day still reads as
   * present on the chart. */
  min-height: 5px;
}

.week__fill--today {
  background: var(--red);
}

.week__day {
  font-size: 10px;
  font-weight: 700;
  color: var(--text-muted);
}

/* --- Mastery rail -------------------------------------------------------------- */

.mastery {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.mastery__row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

.mastery__label {
  flex: 0 0 84px;
  font-size: 12px;
  font-weight: 700;
}

.mastery__count {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* --- Milestones ----------------------------------------------------------------- */

.milestone {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border);
}

.milestone__dot {
  flex: 0 0 10px;
  width: 10px;
  height: 10px;
  margin-top: 6px;
  border-radius: var(--r-pill);
  background: var(--border-dashed);
}

.milestone__dot--done {
  background: var(--green);
}

.milestone__dot--active {
  background: var(--ochre);
}

/* --- Feedback region ------------------------------------------------------------ */

.feedback {
  padding: var(--s-4);
  border-radius: var(--r-tile);
  background: var(--sand-warm);
  border: 1px solid var(--border);
}

.feedback--correct {
  background: var(--feedback-correct-bg);
  border-color: var(--green);
}

.feedback--wrong {
  background: var(--feedback-wrong-bg);
  border-color: var(--red);
}

/* --- Banners --------------------------------------------------------------------- */

.banner {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-tile);
  background: var(--sand-warm);
  border: 1px solid var(--border-dashed);
  font-size: 13px;
  color: var(--text-body);
}

/* --- Forms ----------------------------------------------------------------------- */

.field {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-tile);
  background: var(--cream);
  border: 1px solid var(--border);
  color: var(--ink);
}

.field::placeholder {
  color: var(--text-muted);
}

.filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s-2);
}

/* --- Waveform (family voices) ------------------------------------------------------ */

.waveform {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 24px;
}

.waveform__bar {
  width: 3px;
  border-radius: var(--r-pill);
  background: var(--red);
  opacity: 0.55;
}
