/* FlashCards — shared app styles (mobile-first, Bootstrap 5 utility-first)
 * Palette/typography inspired by leavemealone.com: warm cream backgrounds,
 * a sand/amber primary, generous rounded corners and whitespace, a
 * friendly rounded display font for headings/buttons.
 */

/* Self-hosted Fredoka and Nunito Sans -------------------------------------
 *
 * These came from fonts.googleapis.com until now, which put two extra origins
 * on the critical path — googleapis for the stylesheet, gstatic for the files
 * themselves — costing a DNS lookup and a TLS handshake each, both blocking
 * first paint, on connections we neither control nor cache.
 *
 * Copied from what Google served with only the URLs changed: same families,
 * weights, styles, font-display and unicode-ranges, so the browser resolves
 * exactly the same face for exactly the same text as before.
 *
 * Three weights per family share one file each. These are variable fonts, and
 * Google points all three weight declarations at a single woff2 carrying the
 * whole weight axis; the browser fetches it once and instances 500/600/700
 * from it. A file per weight would triple the transfer for identical bytes.
 *
 * latin and latin-ext only. Google also publishes cyrillic, cyrillic-ext,
 * hebrew and vietnamese for these families; every deck, card, test and
 * question was scanned and not one character falls in any of them. The
 * fallback chain on --font-display/--font-body below covers anything added
 * later, which is already how the → and ← in the card content render — those
 * sit outside the latin range too (it carries ↑ and ↓, but not those two).
 *
 * Both families are under the SIL Open Font License 1.1, which permits
 * redistribution like this. See core/fonts/OFL.txt.
 *
 * Don't hand-edit the unicode-range values; refetch from the css2 URL in this
 * commit's message instead.
 */

/* Fredoka 500 — latin-ext */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/fredoka-latin-ext.d3c5911bfd9a.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Fredoka 500 — latin */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/fredoka-latin.9591efe1be62.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Fredoka 600 — latin-ext */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/fredoka-latin-ext.d3c5911bfd9a.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Fredoka 600 — latin */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/fredoka-latin.9591efe1be62.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Fredoka 700 — latin-ext */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/fredoka-latin-ext.d3c5911bfd9a.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Fredoka 700 — latin */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/fredoka-latin.9591efe1be62.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Nunito Sans 400 — latin-ext */
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/nunito-sans-latin-ext.9359f35055cf.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Nunito Sans 400 — latin */
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/nunito-sans-latin.e010923d59d4.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Nunito Sans 600 — latin-ext */
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/nunito-sans-latin-ext.9359f35055cf.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Nunito Sans 600 — latin */
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/nunito-sans-latin.e010923d59d4.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Nunito Sans 700 — latin-ext */
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/nunito-sans-latin-ext.9359f35055cf.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Nunito Sans 700 — latin */
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("../fonts/nunito-sans-latin.e010923d59d4.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --brand-cream: #fffaf7;
  --brand-cream-deep: #f7ede7;
  --brand-sand: #e3a93f;
  --brand-sand-dark: #c48a2b;
  --brand-sand-tint: #fbf0d9;
  --brand-gold: #d9be6b;
  --brand-gold-dark: #bd9c46;
  --brand-gold-tint: #faf1d9;
  --brand-sage: #8fb996;
  --brand-sage-dark: #6f9c78;
  --brand-terracotta: #e2694f;
  --brand-terracotta-dark: #c2503a;
  --brand-plum: #b98cae;
  --brand-ink: #241f1f;
  --brand-ink-soft: #746863;
  --brand-border: #efe2da;
  --radius-lg: 22px;
  --radius-md: 14px;
  --radius-pill: 999px;
  --shadow-soft: 0 10px 30px rgba(36, 31, 31, 0.08);
  --shadow-hover: 0 16px 36px rgba(36, 31, 31, 0.14);
  --font-display: "Fredoka", "Segoe UI", Roboto, sans-serif;
  --font-body: "Nunito Sans", "Segoe UI", Roboto, sans-serif;
}

body {
  background: var(--brand-cream);
  color: var(--brand-ink);
  font-family: var(--font-body);
}

h1, h2, h3, h4, h5, h6, .navbar-brand, .btn, .brand-display {
  font-family: var(--font-display);
  font-weight: 600;
}

a {
  color: var(--brand-sand-dark);
}

/* Buttons: generous rounded-pill, warm palette --------------------- */

.btn {
  min-height: 44px;
  border-radius: var(--radius-pill);
  font-weight: 600;
  padding-left: 1.25rem;
  padding-right: 1.25rem;
}

.btn-sm {
  min-height: 34px;
  padding-left: 0.9rem;
  padding-right: 0.9rem;
}

/* The "description" trigger beside a deck's/test's name on its start page.
 * It's a <button> so it can open the modal, which means it picks up .btn's
 * display font at 600 above — inside an <h1> that reads as a second
 * heading rather than the aside it is. Body font, normal weight. */
.description-link {
  font-family: var(--font-body);
  font-weight: 400;
}

.btn-primary {
  --bs-btn-color: var(--brand-ink);
  --bs-btn-bg: var(--brand-sand);
  --bs-btn-border-color: var(--brand-sand);
  --bs-btn-hover-color: var(--brand-ink);
  --bs-btn-hover-bg: var(--brand-sand-dark);
  --bs-btn-hover-border-color: var(--brand-sand-dark);
  --bs-btn-active-color: var(--brand-ink);
  --bs-btn-active-bg: var(--brand-sand-dark);
  --bs-btn-active-border-color: var(--brand-sand-dark);
  --bs-btn-disabled-bg: var(--brand-sand);
  --bs-btn-disabled-border-color: var(--brand-sand);
  --bs-btn-focus-shadow-rgb: 227, 169, 63;
}

.btn-outline-primary {
  --bs-btn-color: var(--brand-sand-dark);
  --bs-btn-border-color: var(--brand-sand);
  --bs-btn-hover-color: var(--brand-ink);
  --bs-btn-hover-bg: var(--brand-sand);
  --bs-btn-hover-border-color: var(--brand-sand);
  --bs-btn-active-color: var(--brand-ink);
  --bs-btn-active-bg: var(--brand-sand);
  --bs-btn-active-border-color: var(--brand-sand);
  --bs-btn-focus-shadow-rgb: 227, 169, 63;
  /* Bootstrap's own disabled tokens for this variant are its blue
     (#0d6efd), and they're not covered by any of the overrides above — so
     without these two a disabled outline button drops out of the palette
     entirely and renders blue. */
  --bs-btn-disabled-color: var(--brand-sand-dark);
  --bs-btn-disabled-border-color: var(--brand-sand);
}

/* Targeted practice ("practise what you keep getting wrong"). Its own colour
   rather than a Bootstrap variant: it sits beside the amber "start studying"
   button as an equal third option, so it needs to read as a distinct action
   without competing for primary. Sage is the palette's other call-to-action
   colour, and ink-on-sage keeps the contrast where white-on-sage wouldn't.
   Every state is spelled out, disabled included — the point above about
   Bootstrap's blue applies to any variant left half-overridden. */
.btn-practice {
  --bs-btn-color: var(--brand-ink);
  --bs-btn-bg: var(--brand-sage);
  --bs-btn-border-color: var(--brand-sage);
  --bs-btn-hover-color: var(--brand-ink);
  --bs-btn-hover-bg: var(--brand-sage-dark);
  --bs-btn-hover-border-color: var(--brand-sage-dark);
  --bs-btn-active-color: var(--brand-ink);
  --bs-btn-active-bg: var(--brand-sage-dark);
  --bs-btn-active-border-color: var(--brand-sage-dark);
  --bs-btn-disabled-color: var(--brand-ink);
  --bs-btn-disabled-bg: var(--brand-sage);
  --bs-btn-disabled-border-color: var(--brand-sage);
  --bs-btn-focus-shadow-rgb: 143, 185, 150;
}

.btn-success {
  --bs-btn-bg: var(--brand-sage);
  --bs-btn-border-color: var(--brand-sage);
  --bs-btn-hover-bg: var(--brand-sage-dark);
  --bs-btn-hover-border-color: var(--brand-sage-dark);
  --bs-btn-active-bg: var(--brand-sage-dark);
  --bs-btn-active-border-color: var(--brand-sage-dark);
  --bs-btn-focus-shadow-rgb: 143, 185, 150;
}

.btn-danger {
  --bs-btn-bg: var(--brand-terracotta);
  --bs-btn-border-color: var(--brand-terracotta);
  --bs-btn-hover-bg: var(--brand-terracotta-dark);
  --bs-btn-hover-border-color: var(--brand-terracotta-dark);
  --bs-btn-active-bg: var(--brand-terracotta-dark);
  --bs-btn-active-border-color: var(--brand-terracotta-dark);
  --bs-btn-focus-shadow-rgb: 226, 105, 79;
}

.btn-outline-secondary {
  --bs-btn-color: var(--brand-ink-soft);
  --bs-btn-border-color: var(--brand-border);
  --bs-btn-hover-bg: var(--brand-cream-deep);
  --bs-btn-hover-color: var(--brand-ink);
  --bs-btn-hover-border-color: var(--brand-border);
}

/* Nav ---------------------------------------------------------------- */

.brand-nav {
  background: var(--brand-cream);
  border-bottom: 1px solid var(--brand-border);
}

.brand-nav-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.navbar-toggler {
  border-color: var(--brand-border);
  padding: 0.35rem 0.6rem;
}

.navbar-toggler:focus {
  box-shadow: 0 0 0 0.2rem rgba(227, 169, 63, 0.35);
}

/* Above the collapse breakpoint, nav-links and nav-right share the
 * remaining width next to the brand, spread to opposite ends. Below it,
 * Bootstrap's collapse plumbing (navbar-expand-md) shows/hides this whole
 * block as one dropdown panel instead of everything fighting for room on
 * a single row. */
.navbar-collapse {
  gap: 1.5rem;
}

@media (min-width: 768px) {
  .navbar-collapse {
    display: flex !important;
    flex: 1 1 auto;
    align-items: center;
    justify-content: space-between;
  }
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

/* The active-state underline hangs off the bottom edge — 0.3rem of padding
 * plus the 2px border — with nothing balancing it above, which lifted the
 * labels off the row's centre line: the brand and the lang/account pills are
 * centred, so the links sat a few pixels high next to them. Reserving the
 * same space above puts each label back in the middle of its own box, and
 * the whole row shares one centre line.
 *
 * Bootstrap's own .nav-link padding is no help here: it's written in terms
 * of --bs-nav-link-padding-*, which Bootstrap only defines on .nav and
 * .navbar-nav. This row is a plain .nav-links flex container, so those vars
 * are undefined, the shorthand is invalid at computed-value time, and the
 * padding resolves to 0. Hence spelling both sides out. */
.nav-link {
  color: var(--brand-ink-soft);
  font-weight: 600;
  text-decoration: none;
  padding-top: calc(0.3rem + 2px);
  padding-bottom: 0.3rem;
  border-bottom: 2px solid transparent;
}

.nav-link:hover,
.nav-link:focus {
  color: var(--brand-ink);
}

.nav-link.active {
  color: var(--brand-sand-dark);
  border-bottom-color: var(--brand-sand);
}

.nav-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.profile-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  max-width: 16rem;
  background: none;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-pill);
  padding: 0.3rem 0.7rem 0.3rem 0.55rem;
  font-size: 1.1rem;
  cursor: pointer;
}

.profile-trigger .nav-user-email {
  color: var(--brand-ink-soft);
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-trigger:hover,
.profile-trigger:focus {
  background: var(--brand-cream-deep);
}

/* Auth pages (sign in / beta signup) --------------------------------- */

.auth-page {
  display: flex;
  justify-content: center;
}

.auth-card {
  width: 100%;
  max-width: 420px;
}

.lang-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: none;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-pill);
  padding: 0.35rem 0.75rem;
  cursor: pointer;
}

.lang-trigger:hover,
.lang-trigger:focus {
  background: var(--brand-cream-deep);
}

.lang-trigger .lang-flag {
  font-size: 1.15rem;
  line-height: 1;
}

.lang-trigger .lang-caret {
  font-size: 0.7rem;
  color: var(--brand-ink-soft);
}

/* Language modal ----------------------------------------------------- */

.modal-content {
  border: none;
  border-radius: var(--radius-lg);
}

.lang-option {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.75rem 1rem;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  background: #fff;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 1rem;
  color: var(--brand-ink);
  text-align: left;
}

.lang-option:hover {
  background: var(--brand-cream-deep);
}

.lang-option.active {
  border-color: var(--brand-sand);
  background: var(--brand-sand-tint);
}

.lang-option .lang-flag {
  font-size: 1.3rem;
  line-height: 1;
}

.lang-option .lang-check {
  margin-left: auto;
  color: var(--brand-sand-dark);
  font-weight: 700;
}

.brand-logo {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  background: var(--brand-sand);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
}

.navbar-brand {
  color: var(--brand-ink) !important;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1.25rem;
}

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

.card {
  border: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
}

.group-heading {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand-ink-soft);
}

.group-heading::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--brand-gold);
  flex-shrink: 0;
}

/* Last-session progress (deck/test cards) ----------------------------- */

.last-session-cell {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.progress-pie {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* Fully filled, in a different color from the in-progress conic-gradient
 * pie, so a completed deck/test reads as "done" rather than "100% through
 * an active session" at a glance. */
.progress-pie--done {
  background: var(--brand-sage);
}

/* Deck grid -------------------------------------------------------- */

.deck-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.25rem;
}

/* Homepage hub (Memory Flashcards / Tests) reuses the deck-grid/deck-card
 * look with just two big options, so it should collapse empty tracks
 * instead of stretching across the full row width. */
.hub-grid {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  max-width: 640px;
  margin: 0 auto;
}

.hub-grid .deck-avatar {
  width: 56px;
  height: 56px;
  font-size: 1.75rem;
}

.deck-card {
  text-decoration: none;
  color: inherit;
}

.deck-card .card {
  height: 100%;
  transition: box-shadow 0.15s ease-in-out, transform 0.15s ease-in-out;
}

.deck-card:hover .card,
.deck-card:focus .card {
  box-shadow: var(--shadow-hover);
  transform: translateY(-2px);
}

.deck-avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 700;
  font-family: var(--font-display);
}

/* Study view -------------------------------------------------------- */

/* The card/question swapped in place by core/js/session-swap.js. It takes a
 * tabindex purely so the script can move focus here after a swap, the way a
 * navigation would — that's not the user tabbing to it, so it gets no focus
 * ring. Everything focusable inside keeps its own. */
#session-live:focus {
  outline: none;
}

.study-card {
  max-width: 640px;
  margin: 0 auto;
}

.study-answer[hidden] {
  display: none !important;
}

.study-answer {
  border-color: var(--brand-border) !important;
}

/* Test view (multiple-choice feedback) ------------------------------- */

/* Before answering, these are plain .btn elements, which default to the
 * rounded display font — but the answered state's options (.answer-option
 * below) render in the body font instead. Matching it here means the text
 * doesn't visibly change typeface the moment you pick an answer. */
.answer-choice {
  font-family: var(--font-body);
  font-weight: 400;
}

.answer-option {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 1rem;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
}

/* Holds the option number and its text together as the row's left half, so
 * space-between still pushes the ✓/✗ to the far right. */
.answer-option-text {
  display: flex;
  align-items: baseline;
}

.answer-option.correct {
  border-color: var(--brand-sage);
  background: var(--brand-sage);
  color: #fff;
  font-weight: 600;
}

.answer-option.incorrect {
  border-color: var(--brand-terracotta);
  background: var(--brand-terracotta);
  color: #fff;
  font-weight: 600;
}

/* Keyboard shortcut hints -------------------------------------------- */

/* The small "1" / "( → )" labels on buttons a key press can activate.
 * Hidden by default and shown only where the browser reports a fine
 * pointer with hover — laptops and desktops — so phones and tablets don't
 * advertise keys they have no keyboard for. The handlers themselves
 * (study/js/keyboard.js, tests/js/keyboard.js) stay unconditional: with no
 * keyboard attached they simply never fire, and a tablet that does have one
 * keeps working. Inheriting the colour keeps the hint legible on the filled
 * grading buttons and through the answer buttons' hover state alike. */
.key-hint {
  display: none;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  color: inherit;
  opacity: 0.65;
}

@media (hover: hover) and (pointer: fine) {
  .key-hint {
    display: inline-block;
  }
}

/* Leading option number, on both the answer buttons and the answered
 * state's feedback rows — a floor on the width so the option text starts at
 * the same place down the whole column, however wide the numbers get. */
.answer-choice .key-hint,
.answer-option .key-hint {
  min-width: 2.1em;
  margin-right: 0.4rem;
}

.progress {
  border-radius: var(--radius-pill);
  background-color: var(--brand-cream-deep);
}

.progress-bar {
  background-color: var(--brand-sand);
}

.alert {
  border: none;
  border-radius: var(--radius-md);
  background: var(--brand-gold-tint);
  color: var(--brand-ink);
}

/* Footer -------------------------------------------------------- */

.brand-footer {
  background: var(--brand-cream-deep);
  border-top: 1px solid var(--brand-border);
  color: var(--brand-ink-soft);
}

.brand-footer a {
  color: var(--brand-sand-dark);
  font-weight: 600;
  text-decoration: none;
}

.brand-footer a:hover {
  text-decoration: underline;
}

/* Below this width the brand + nav links + lang/account controls no
 * longer fit on one row — navbar-expand-md (see base.html) collapses
 * nav-links/nav-right into one hamburger-toggled dropdown panel instead
 * of everything squeezing onto one line; this just stacks what's inside
 * that panel into two centered, vertically-grouped lists. */
@media (max-width: 767.98px) {
  .brand-nav-row {
    padding-block: 0.5rem;
  }

  .navbar-collapse {
    width: 100%;
  }

  .nav-links,
  .nav-right {
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 0;
  }

  .nav-right {
    border-top: 1px solid var(--brand-border);
  }
}

/* FAQs ----------------------------------------------------------------- */

.faq-page {
  max-width: 720px;
  margin: 0 auto;
}

.faq-section {
  margin-bottom: 2rem;
}

.faq-section-heading {
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand-ink-soft);
  margin-bottom: 0.75rem;
}

.faq-item {
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  background: #fff;
  padding: 0.5rem 1rem;
  margin-bottom: 0.6rem;
}

.faq-question {
  cursor: pointer;
  font-weight: 600;
  padding: 0.5rem 0;
  list-style: none;
}

.faq-question::-webkit-details-marker {
  display: none;
}

.faq-question::before {
  content: "+";
  display: inline-block;
  width: 1.1em;
  color: var(--brand-sand-dark);
  font-weight: 700;
}

.faq-item[open] .faq-question::before {
  content: "–";
}

.faq-answer {
  padding: 0 0 0.5rem 1.1em;
  color: var(--brand-ink-soft);
}

.faq-answer p:last-child {
  margin-bottom: 0;
}

/* Study calendar ----------------------------------------------------- */

.calendar-page {
  max-width: 960px;
  margin: 0 auto;
}

.calendar-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

.range-switcher {
  flex-wrap: wrap;
}

.calendar-nav {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.calendar-nav .btn {
  min-height: 34px;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
}

.calendar-range-label {
  font-weight: 600;
  color: var(--brand-ink-soft);
  min-width: 11rem;
  text-align: center;
}

/* Deck / test list rows (flashcards & tests home) --------------------- */

.review-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.review-item {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  background: #fff;
  padding: 0.6rem 0.9rem;
}

/* Left-hand column on the home rows: the name (with count) stacked over the
 * "last studied" line, so the Start/Continue pill can sit at the far right. */
.review-item-main {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.review-item:hover {
  border-color: var(--brand-sand);
  background: var(--brand-sand-tint);
}

.review-item-name {
  font-weight: 600;
  text-decoration: none;
  color: var(--brand-ink);
  overflow-wrap: anywhere;
}

/* Stretch the name link over the whole row so clicking anywhere on the
 * item opens the deck/test. Only real links (a) get the overlay — a muted
 * row (nothing to study) uses a plain span and stays non-clickable. */
a.review-item-name::after {
  content: "";
  position: absolute;
  inset: 0;
}

.review-item:hover a.review-item-name {
  text-decoration: underline;
}

.review-item-count {
  color: var(--brand-ink-soft);
  font-weight: 400;
}

.review-item-when {
  white-space: nowrap;
}

/* Start / Continue pill on the deck & test home rows. Purely a visual label
 * — the whole row is the click target (the stretched name link above). */
.review-item-action {
  flex: none;
  white-space: nowrap;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.2rem 0.75rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--brand-sand);
  color: var(--brand-ink);
}

.review-item-action--continue {
  background: var(--brand-sand);
}

.review-item--muted:hover {
  border-color: var(--brand-border);
  background: #fff;
}

@media (max-width: 480px) {
  .review-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.2rem;
  }

  .review-item-when {
    white-space: normal;
  }
}

/* Public/private switch on the deck & test listings -------------------- */

.visibility-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  max-width: 520px;
  margin: 0 auto 2rem;
}

.visibility-option {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 0.75rem 1rem;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  background: #fff;
  color: var(--brand-ink);
  font-family: var(--font-display);
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  line-height: 1.2;
}

.visibility-option:hover,
.visibility-option:focus {
  border-color: var(--brand-sand);
  background: var(--brand-sand-tint);
  color: var(--brand-ink);
}

.visibility-option.active {
  border-color: var(--brand-sand);
  background: var(--brand-sand);
  box-shadow: var(--shadow-soft);
}

@media (max-width: 380px) {
  .visibility-option {
    min-height: 48px;
    padding: 0.6rem 0.5rem;
    font-size: 0.95rem;
  }
}

/* Manage section (own decks/tests) ------------------------------------ */

.manage-page {
  max-width: 860px;
  margin: 0 auto;
}

/* The "what this page is for" copy at the top of each section landing. */
.manage-intro {
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  background: var(--brand-sand-tint);
  padding: 1rem 1.15rem;
  margin-bottom: 1.75rem;
}

.manage-header,
.manage-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.manage-header {
  margin-bottom: 1.5rem;
}

.manage-toolbar {
  margin-bottom: 0.9rem;
}

.manage-header-actions,
.manage-toolbar-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

/* The search box takes the row's spare width; its Add button stays put. */
.manage-toolbar .live-search-input {
  flex: 1 1 16rem;
  min-width: 0;
  max-width: 26rem;
}

.live-search.is-searching .live-search-input {
  background-image: linear-gradient(90deg, transparent, var(--brand-sand-tint), transparent);
  background-size: 40% 100%;
  background-repeat: no-repeat;
  animation: live-search-sweep 0.9s linear infinite;
}

@keyframes live-search-sweep {
  from { background-position: -40% 0; }
  to { background-position: 140% 0; }
}

/* Manage rows reuse .review-item, stretched link included, so clicking
 * anywhere on a group or deck row opens it — the same feel as the public
 * lists. Rows that carry their own buttons (cards/questions, which have no
 * page of their own to put them on) opt out: an overlay covering the whole
 * row would swallow the clicks meant for those buttons. */
.manage-item--actions a.review-item-name::after {
  content: none;
}

.manage-item .manage-item-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  flex: none;
}

.manage-item .review-item-when {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Also on the statistics rows, which list questions the same way. */
.manage-item .question-number,
.stat-row .question-number {
  color: var(--brand-ink-soft);
}

/* --- Card reordering (apps/manage/js/manage.js, section 5) -------------- */

/* .review-item is justify-content: space-between, which reads as "one thing
 * left, one thing right" for the two-child rows everywhere else. The handle
 * makes three, and the middle child then floats in whatever space is left —
 * the question text ends up looking centred. Growing it puts the text back
 * against the handle and the buttons back against the right edge. */
.manage-item--sortable .review-item-main {
  flex: 1 1 auto;
}

/* The handle is a real button so it can be tabbed to and driven with the
 * arrow keys; it just doesn't look like one. touch-action keeps a drag from
 * scrolling the page out from under itself on a phone. */
.card-drag-handle {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  flex: none;
  border: 0;
  background: none;
  padding: 0.1rem 0.2rem;
  color: var(--brand-ink-soft);
  cursor: grab;
  touch-action: none;
}

.card-drag-handle:focus-visible {
  outline: 2px solid var(--brand-sand);
  border-radius: var(--radius-md);
}

.card-drag-grip {
  font-size: 1.1rem;
  line-height: 1;
}

.card-number {
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
}

.manage-item--sortable.is-dragging {
  opacity: 0.6;
  border-color: var(--brand-sand);
  background: var(--brand-sand-tint);
}

.card-sortable.is-reordering .card-drag-handle {
  cursor: grabbing;
}

/* Rows shift around under the pointer mid-drag; without this the hover
 * highlight follows it and every row it passes lights up. */
.card-sortable.is-reordering .review-item:hover {
  border-color: var(--brand-border);
  background: #fff;
}

.manage-reorder-hint {
  margin-bottom: 0.6rem;
}

.manage-reorder-status {
  margin-bottom: 0.4rem;
  min-height: 1.2em;
  color: var(--brand-ink-soft);
}

.manage-reorder-status.is-error {
  color: var(--brand-terracotta-dark);
}

.manage-form {
  max-width: 560px;
}

/* Card/question editors need the room — a WYSIWYG toolbar at 560px wraps. */
.manage-form--wide {
  max-width: 100%;
}

/* The arrow strip above each WYSIWYG field (apps/manage/js/manage.js). */
.rt-arrows {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.4rem;
}

.rt-arrows-label {
  font-size: 0.8rem;
  color: var(--brand-ink-soft);
}

.rt-arrows button {
  min-width: 2rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  background: #fff;
  font-size: 1rem;
  line-height: 1.4;
  cursor: pointer;
}

.rt-arrows button:hover,
.rt-arrows button:focus {
  border-color: var(--brand-sand);
  background: var(--brand-sand-tint);
}

/* Answer options editor (one radio group across all rows) --------------- */

.answer-editor {
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  padding: 1rem;
}

.answer-editor legend {
  float: none;
  width: auto;
  margin-bottom: 0;
  font-size: 1rem;
}

.answer-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.answer-row-correct {
  display: inline-flex;
  align-items: center;
  margin: 0;
}

.answer-row .form-control {
  flex: 1 1 auto;
  min-width: 0;
}

.answer-row-remove {
  flex: none;
  min-height: 34px;
  line-height: 1;
}

/* Rich text rendered from cards (study view + manage previews) and from a
 * deck's/test's description (.richtext-body, in the description modal) ---- */

/* Tables are the one thing in a card that can be wider than its column, so
 * they scroll inside their own figure instead of widening the page. */
.study-question figure.table,
.study-answer figure.table,
.richtext-body figure.table {
  max-width: 100%;
  overflow-x: auto;
  margin: 1rem 0;
}

.study-question table,
.study-answer table,
.richtext-body table {
  border-collapse: collapse;
  width: 100%;
}

.study-question th,
.study-question td,
.study-answer th,
.study-answer td,
.richtext-body th,
.richtext-body td {
  border: 1px solid var(--brand-border);
  padding: 0.4rem 0.6rem;
  text-align: left;
  vertical-align: top;
}

.study-question th,
.study-answer th,
.richtext-body th {
  background: var(--brand-cream-deep);
  font-weight: 600;
}

/* The editor ends every description with a paragraph, whose bottom margin
 * would otherwise sit between the last line and the modal's footer rule. */
.richtext-body > :last-child {
  margin-bottom: 0;
}

/* Study agenda: one row per active day -------------------------------- */

.agenda-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.agenda-day {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  background: #fff;
  padding: 0.9rem 1rem;
}

.agenda-day.is-today {
  border-color: var(--brand-sand);
  background: var(--brand-sand-tint);
}

.agenda-date {
  flex: 0 0 5.5rem;
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}

.agenda-weekday {
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--brand-ink-soft);
}

.agenda-daynum {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 600;
}

.agenda-monthyear {
  font-size: 0.78rem;
  color: var(--brand-ink-soft);
}

.agenda-today-badge {
  align-self: flex-start;
  margin-top: 0.25rem;
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius-md);
  background: var(--brand-sand);
  color: var(--brand-ink);
}

.agenda-activity {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 0.4rem;
}

.calendar-chip {
  font-size: 0.8rem;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-md);
  background: var(--brand-cream-deep);
  color: var(--brand-ink);
  overflow-wrap: anywhere;
}

.calendar-chip-test {
  background: var(--brand-gold-tint);
}

/* "3 times" sits on its own line under the deck/test name, so the chip
 * still reads as one name first and a count second. */
.calendar-chip-times {
  display: block;
  font-size: 0.72rem;
  color: var(--brand-ink-soft);
}

@media (max-width: 480px) {
  .agenda-day {
    flex-direction: column;
    gap: 0.6rem;
  }

  .agenda-date {
    flex-basis: auto;
    flex-direction: row;
    align-items: baseline;
    gap: 0.4rem;
  }
}

@media (max-width: 576px) {
  .study-grading-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 0.75rem;
    background: var(--brand-cream);
    border-top: 1px solid var(--brand-border);
    z-index: 10;
  }

  main {
    padding-bottom: 5rem;
  }

  .navbar-brand {
    font-size: 1rem;
  }

  .navbar-brand .brand-logo {
    width: 30px;
    height: 30px;
    font-size: 0.95rem;
  }
}

/* Settings page + PWA ----------------------------------------------- */

.settings-page {
  max-width: 640px;
  margin: 0 auto;
}

/* The failure-statistics page is a list of deck/test rows rather than a form,
   so it gets the wider measure the deck listings use. */
.stats-page {
  max-width: 760px;
  margin: 0 auto;
}

/* A deck/test row's accuracy: "✓ correct 75%  ✗ wrong 25%".
 *
 * The tick and cross carry the colour; the words and percentages stay in ink.
 * Sage and terracotta at this size wouldn't clear 4.5:1 against white, and the
 * icons don't need to — they're aria-hidden decoration beside the words
 * "correct" and "wrong", which are what actually say which number is which. */
.stats-correct,
.stats-wrong {
  color: var(--brand-ink);
  white-space: nowrap;
}

.stats-correct {
  margin-right: 0.6rem;
}

.stats-icon {
  font-weight: 700;
}

.stats-correct .stats-icon {
  color: var(--brand-sage-dark);
}

.stats-wrong .stats-icon {
  color: var(--brand-terracotta-dark);
}

/* Rows for content the user has never answered anything in. */
.stats-count--none {
  font-weight: 400;
  color: var(--brand-ink-soft);
}

/* "answered 5 times", ahead of the two percentages it's the denominator for. */
.stats-count {
  margin-right: 0.6rem;
  white-space: nowrap;
}

/* A card/question row on a deck's or test's statistics page. Nothing here is
 * a link — the row is a reading of one card, not a way in to it — so it drops
 * .review-item's hover highlight, which would promise a click target. */
.stat-row:hover {
  border-color: var(--brand-border);
  background: #fff;
}

/* Account menu: a list of plain links, not a stack of buttons. Six outlined
 * buttons read as six competing actions; these are just places to go, and the
 * rules below do the grouping that the buttons' boxes were doing.
 *
 * Sign out is a submit button (it has to POST), so the class resets the
 * button chrome — background, border, font — to make it sit level with the
 * anchors either side of it. */
.profile-menu-link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.45rem 0;
  border: 0;
  background: none;
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  color: var(--brand-ink);
  cursor: pointer;
}

/* A fixed column for the emoji so the labels line up down the menu whatever
 * width each one renders at — they vary by platform, and one wide glyph would
 * otherwise indent its own row. Decoration only: the markup marks them
 * aria-hidden and the label carries the meaning. */
.profile-menu-icon {
  flex: none;
  width: 1.5rem;
  font-size: 1.05rem;
  line-height: 1;
  text-align: center;
}

.profile-menu-link:hover,
.profile-menu-link:focus-visible {
  color: var(--brand-sand-dark);
  text-decoration: underline;
}

/* Rules between the account menu's three groups. Bootstrap's <hr> default is
 * a faint currentColor line; this uses the same border colour as everything
 * else in the modal so the grouping reads as deliberate rather than as a
 * gap. */
.profile-menu-rule {
  margin: 0.6rem 0;
  border: 0;
  border-top: 1px solid var(--brand-border);
  opacity: 1;
}

/* Sort switch: two links styled as one segmented control. */
.stat-order {
  display: inline-flex;
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.stat-order-option {
  padding: 0.25rem 0.85rem;
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--brand-ink-soft);
  white-space: nowrap;
}

.stat-order-option:hover {
  background: var(--brand-cream-deep);
  color: var(--brand-ink);
}

.stat-order-option.active {
  background: var(--brand-sand);
  color: var(--brand-ink);
}

/* One reminder rule: the on/off switch and the "after N days" box that
   belongs to it. The box stays usable while the rule is off (see
   apps/accounts/forms.py) — it's just visually stepped back so the switch
   reads as the thing in charge. */
.notify-rule {
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-md);
  padding: 1rem;
}

.notify-rule .form-check-input:checked {
  background-color: var(--brand-sand);
  border-color: var(--brand-sand);
}

.notify-rule-days {
  padding-left: 2.5rem;
}

.notify-rule-days .stale-days-input {
  max-width: 8rem;
}

/* Same treatment as the reminder day boxes: a percentage is at most three
   digits, so a full-width input just looks like a mistake. */
.fail-percent-input {
  max-width: 8rem;
}

@media (max-width: 480px) {
  .notify-rule-days {
    padding-left: 0;
  }
}

/* Installing the app from inside the installed app is nonsense. JS hides
   these too (core/js/pwa.js), but this applies before it has parsed and
   without depending on it at all. */
@media (display-mode: standalone) {
  .pwa-install-only {
    display: none !important;
  }
}
