/* Copyright (C) 2026 Aron Sommer. See LICENSE file for full license details. */

/* One blue and one red, always with white text on them in both themes. */
:root {
  color-scheme: light dark;
  --bg: light-dark(#f3f6fb, #131314);
  --surface: light-dark(#ffffff, #1e1f20);
  --tonal: light-dark(#dde4ee, #2d2f31);
  --line: light-dark(#e3e8ef, #3c4043);
  --text: light-dark(#1f1f1f, #e3e3e3);
  --text-2: light-dark(#5f6368, #b4b8bc);
  --accent: #1e90ff;
  --on-accent: #ffffff;
  --bad: #dc143c;
  --on-bad: #ffffff;
  --bubble-teacher: var(--surface);
  --bubble-user: light-dark(#dcecff, #1b2c40);
  --gap-tight: 4px; /* a label and its field, the corner buttons */
  --gap-small: 8px; /* the buttons, the teacher image and the title, the bubbles and the lines by them */
  --gap: 12px; /* the usual one: the header, the settings rows */
  --control: 34px; /* the height of buttons and one-line fields */
  --shadow:
    0 1px 2px light-dark(rgb(60 64 67 / 0.08), transparent),
    0 1px 3px light-dark(rgb(60 64 67 / 0.06), transparent);
  font-family: "Figtree", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* No flash over a tapped button or link on touch screens. */
  -webkit-tap-highlight-color: transparent;
  /* No zoom on a double tap. */
  touch-action: manipulation;
}

* {
  box-sizing: border-box;
}

html {
  height: 100%;
}

/* The padding keeps everything out from under the notch and the home bar. */
body {
  margin: 0;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom)
    env(safe-area-inset-left);
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
}

/* The title and the buttons at the left, the icons at the right. */
header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;
  gap: var(--gap);
  padding: var(--gap-small) 16px;
  border-bottom: 1px solid var(--line);
}

h1,
h2 {
  margin: 0;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.5px;
}

/* The two words stack beside the teacher image. */
h1 {
  display: grid;
  grid-template-columns: auto min-content;
  justify-content: start;
  align-items: center;
  column-gap: var(--gap-small);
  line-height: 1.1;
}

/* One line taller than the corner icons. The ratio holds the width until the image has loaded. */
h1 img {
  height: calc(var(--control) + 1lh);
  aspect-ratio: 1;
}

a {
  color: inherit;
}

#settings {
  width: 480px;
  padding: 0;
  border: 0;
  border-radius: 28px;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
}

#settings::backdrop {
  background: rgb(0 0 0 / 0.35);
}

/* The padding is the form's, so only a click on the backdrop lands on the dialog itself. */
#settings form {
  display: grid;
  gap: var(--gap);
  padding: 24px;
}

h2 {
  text-align: center;
}

/* The link and the version on two close lines. */
#settings p {
  display: grid;
  justify-items: start;
  gap: var(--gap-tight);
  margin: 0;
  font-size: 14px;
  color: var(--text-2);
}

#settings button {
  justify-self: center;
}

label {
  display: grid;
  gap: var(--gap-tight);
  font-size: 14px;
  color: var(--text-2);
}

/* The key's label at the left, the link to get one at the right. */
label span {
  display: flex;
  justify-content: space-between;
  gap: var(--gap);
}

input,
select,
textarea,
button {
  font: inherit;
  font-size: 15px;
}

/* One height, so a row is equally tall whichever controls it holds. */
#key,
#typed,
select,
button {
  height: var(--control);
}

/* Masked like a password field, without being one for password managers. */
#key {
  -webkit-text-security: disc;
}

/* Empty: the placeholder is a single space. */
#key:placeholder-shown {
  border-color: var(--bad);
}

#key,
#typed,
select,
#notes {
  width: 100%;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
}

#notes {
  height: 8lh;
  padding-block: 8px;
  resize: vertical;
}

button {
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  cursor: pointer;
}

/* The buttons and the checkboxes stay in one row. */
.buttons {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--gap-small);
}

/* The two checkboxes, one above the other, are together as tall as the buttons. */
.checks {
  display: grid;
  grid-auto-rows: 1fr;
  height: var(--control);
  white-space: nowrap;
}

.checks label {
  display: flex;
  align-items: center;
}

/* Drawn here, as browsers may change the accent colour of their own checkboxes. */
.checks input {
  appearance: none;
  display: grid;
  place-content: center;
  width: 14px;
  height: 14px;
  margin: 0;
  border: 1px solid var(--text-2);
  border-radius: 3px;
  background: var(--surface);
}

.checks input:checked {
  border-color: var(--accent);
  background: var(--accent);
}

/* The tick: two sides of a box, turned. */
.checks input:checked::before {
  content: "";
  width: 8px;
  height: 4px;
  margin-top: -2px;
  border: solid var(--on-accent);
  border-width: 0 0 2px 2px;
  rotate: -45deg;
}

.checks label:has(:disabled) {
  opacity: 0.45;
}

/* Without a key, the button that adds one shows in place of these. */
.no-key .buttons,
.no-key #status,
body:not(.no-key) #addKey {
  display: none;
}

button.bad {
  background: var(--bad);
  color: var(--on-bad);
}

/* Start/Stop, Clear and Done are equally wide. */
#toggle,
button.plain {
  width: 64px;
  padding: 0;
}

/* Only the label of the current state shows. */
#toggle:not(.bad) span:last-child,
#toggle.bad span:first-child {
  display: none;
}

/* The microphone and the keyboard side by side, the one in use filled. */
#mode {
  display: flex;
  gap: 2px;
  padding: 3px;
  background: var(--tonal);
  color: inherit;
}

#mode span {
  display: grid;
  place-items: center;
  width: 28px;
  border-radius: 999px;
}

body:not(.typing) #mode span:first-child,
.typing #mode span:last-child {
  background: var(--accent);
  color: var(--on-accent);
}

button.plain {
  background: var(--tonal);
  color: inherit;
  font-weight: 500;
}

/* The three icons in a row, Install below them. */
.corner {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: var(--gap-tight);
}

/* Shifted by the empty space around a drawing, so the last one ends at the right edge. */
.corner .material-symbols-outlined {
  width: var(--control);
  padding: 0;
  background: none;
  color: inherit;
  translate: 8px;
}

/* At the right, and low enough to end above the bottom of the title. Filled with the colour of the
   icons, its label has the colour of the page. */
#install-pwa-link {
  grid-row: 2;
  grid-column: 1 / -1;
  justify-self: end;
  height: 20px;
  padding: 0 10px;
  background: var(--text);
  color: var(--bg);
  font-size: 13px;
  font-weight: 500;
}

button:disabled {
  opacity: 0.45;
  cursor: default;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

main {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-small);
  padding: var(--gap-small) max(16px, 50% - 340px);
}

/* The turns lay out as children of main. */
#log {
  display: contents;
}

/* A turn: the teacher's message and the learner's messages to it that have left the footer. */
section {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--gap-small);
}

section:empty {
  display: none;
}

/* The last turn fills the chat, so scrolled to the end it starts at the top. */
section:nth-last-child(1 of :not(:empty)) {
  min-height: 100%;
  /* Covers the shadow of the bubble above, which would show below the header. */
  box-shadow: 0 calc(-1 * var(--gap-small)) var(--bg);
}

/* The learner's last message, at a fixed height for two rows of words with meanings. While
   typing, the field takes part of the height, so the footer is as tall in both modes. */
footer {
  display: flex;
  flex-direction: column;
  height: 133px;
  border-top: 1px solid var(--line);
}

#said {
  flex: 1;
  overflow-y: auto;
  padding: var(--gap-small) max(16px, 50% - 340px);
}

/* The message only shows once the chat has one, the footer only then or while typing. */
main:has(#log:empty) + footer #said,
body:not(.typing) main:has(#log:empty) + footer,
body:not(.typing) #typing {
  display: none;
}

/* While typing, the message sits just above the field. */
.typing #said {
  display: flex;
  flex-direction: column;
}

.typing #said > :first-child {
  margin-top: auto;
}

/* The field takes the row, the button stays round. */
#typing {
  display: flex;
  gap: var(--gap-small);
  padding: 0 max(16px, 50% - 340px) var(--gap-small);
}

/* Without the message, the footer is only the field, which then keeps its own gap above. */
main:has(#log:empty) + footer {
  height: auto;
}

main:has(#log:empty) + footer #typing {
  padding-top: var(--gap-small);
}

/* Above an on-screen keyboard there is little room, so the header goes. */
.keyboard header {
  display: none;
}

/* The keyboard covers the home bar. */
body.keyboard {
  padding-bottom: 0;
}

/* Scrolling past their ends does not move the page under the keyboard. */
.keyboard main,
.keyboard #said {
  overscroll-behavior: contain;
}

#send {
  flex: none;
  width: var(--control);
  padding: 0;
}

/* At a fixed distance below the header, so the field below in typing mode does not move it. */
#intro {
  display: grid;
  justify-items: center;
  gap: var(--gap);
  max-width: 320px;
  margin: 25dvh auto 0;
  text-align: center;
  color: var(--text-2);
}

#hint {
  margin: 0;
}

/* With a key, it only shows while the chat has no message and no status. */
body:not(.no-key):has(#log:not(:empty), #status:not(:empty)) #intro {
  display: none;
}

/* The last row of the header. Its line is kept while empty, so the chat never jumps. */
#status {
  grid-column: 1 / -1;
  min-height: 1lh;
  margin: 0;
  font-size: 14px;
  color: var(--text-2);
}

article {
  padding: 8px 16px;
  border-radius: 16px;
  background: var(--bubble-teacher);
  box-shadow: var(--shadow);
}

article.user {
  background: var(--bubble-user);
}

article p {
  margin: 0;
}

.text,
.words {
  font-size: 18px;
  line-height: 1.4;
}

.interim {
  color: var(--text-2);
}

.natural {
  margin-top: var(--gap-tight);
  font-size: 14px;
  line-height: 1.2;
  color: var(--text-2);
  /* The API's quota error ends in a URL wider than a phone's bubble. */
  overflow-wrap: anywhere;
}

.natural:empty {
  display: none;
}

/* The words of all sentences wrap as one list. */
.words > span {
  display: contents;
}

.words {
  display: none;
  flex-wrap: wrap;
  gap: var(--gap-small) var(--gap);
}

/* The meaning below a word is spaced like the translation line below the text. */
.word {
  display: grid;
  justify-items: center;
  row-gap: var(--gap-tight);
}

.word small {
  font-size: 14px;
  line-height: 1.2;
  color: var(--text-2);
}

.word-mode .glossed .words {
  display: flex;
}

/* Without translation, the lines that were translated before hide too. */
.no-translation .natural {
  display: none;
}

/* Word mode hides the translation line unless a translation in it failed. */
.word-mode .glossed .text,
.word-mode .natural:not(:has(.failed)) {
  display: none;
}
