/* Color Constants */
:root {
  --color-primary: #b5562f; /* Rust: hover/emphasis, still darker than secondary */
  --color-secondary: #cf7a40; /* Terracotta: the fox orange, aged like old paper */
  --color-background: #f1e7d2; /* Parchment, warm enough that the paper cards stand out */
  --color-surface: #faf7f0; /* Lighter paper for cards, rows and inputs (no harsh white) */
  --color-surface-muted: #efe7d3; /* Oatmeal: list rows and disabled fills */
  --color-ink: #2c221e; /* Espresso ink for text and outlines */
  --color-rule: #c8b99c; /* Thin dividers */
  --color-paper: #fffdf7; /* Index-card stock, a shade brighter than the page */
  --color-coffee: #4a3528; /* Card outlines and their flat offset shadow */
  --color-ink-soft: #5c4a42; /* Secondary text: descriptions, captions */
  /* Index-card stock, shared by every card on the site. Final Round re-points these. */
  --card-paper: var(--color-paper);
  --card-margin-line: #d98a8a; /* The pink line under an index card's heading */
  --card-rule: #c9dbe6; /* Faint blue ruled lines */
  --card-stamp: #c0503a; /* Hanko red */
  /* Selection washes, all shades of the fox orange, lightest to deepest */
  --select-hover: #fbeee2; /* Faint apricot: previews a tick under the cursor */
  --select-word: #f6dcc6; /* Apricot: a ticked word's line */
  --select-category: #eab99a; /* Burnt orange: the header of a fully selected category */
  /* Early-web hard shadows: a crisp offset with no blur */
  --shadow: 2px 2px 0 rgb(44 34 30 / 0.35);
  --shadow-raised: 4px 4px 0 rgb(44 34 30 / 0.35);
}
/* Body */
body {
  background-color: var(--color-background);
  color: var(--color-ink);
  font-family: "Klee One", cursive;
}

/* Icons: inline SVGs drawn from each page's sprite. They size like text (1em tall,
   width from the viewBox) and take the surrounding text color, as the old icon font did. */
.icon {
  display: inline-block;
  height: 1em;
  width: auto;
  overflow: visible;
  vertical-align: -0.125em;
  fill: currentColor;
}

/* Holds the <symbol>s only; it never renders itself */
.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* Bootstrap */
.container-fluid {
  max-width: 1140px;
}

/* Headers */
h1,
h2,
h3,
h4,
h5,
h6 {
  line-height: 1;
}

h1 {
  font-size: clamp(56px, 12vw, 96px); /* 96px on desktop, shrinks so titles don't overflow phones */
  font-weight: 800;
  padding: 0;
  margin: 0;
}

h2 {
  font-size: 64px;
  font-weight: 800;
}

h3 {
  font-size: 48px;
  font-weight: 600;
}

h4 {
  font-size: 36px;
  font-weight: 600;
}

h5 {
  font-size: 24px;
  font-weight: 400;
}

h6 {
  font-size: 18px;
  font-weight: 400;
}

/* Header Formatting */
.title-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

.title-nav-item {
  padding: 0 15px; /* Padding Between Nav Items */
  font-size: 18px;
  font-weight: 600;
  border-bottom: 2px solid transparent;
  transition:
    color 0.2s,
    border-color 0.2s;
}

/* Beats the global a:hover { color: inherit } */
.title-nav-item:hover,
.title-nav-item:focus-visible {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

/* Page nav below the title on every page except quiz and grade: underlined text links
   with an icon and the same small lift as .button so they read as clickable. The
   "Kitsu Cards" link above a page title uses .title-nav-item, a plain text link. */
.title-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 2px 4px;
  margin: 0 11px;
  font-size: 18px;
  font-weight: 600;
  border-bottom: 2px solid var(--color-secondary);
  transition:
    color 0.2s,
    border-color 0.2s,
    transform 0.2s;
}

.title-nav-link .icon {
  font-size: 0.85em;
  color: var(--color-primary);
}

/* Small trailing hint that the link leaves the site */
.title-nav-link .title-nav-external {
  font-size: 0.6em;
  opacity: 0.7;
}

/* Beats the global a:hover { color: inherit } */
.title-nav-link:hover,
.title-nav-link:focus-visible {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
  transform: translateY(-2px);
}

.title-nav-link:active {
  transform: translateY(0);
}

.title-nav-link:focus {
  outline: none;
}

.title-nav-link:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 4px;
  border-radius: 4px;
}

.title-selection {
  text-align: center;
  margin-bottom: 0.2em;
}
.title-selection-deck {
  text-align: center;
  margin-bottom: 1em;
}

/* Page divider: separates the header block from the main body. It is the page's own
   index-card heading line (the pink margin line), and its margins set the gap on
   both sides. */
.page-divider {
  width: min(100% - 2rem, 1110px);
  margin: 2rem auto;
  border: 0;
  border-top: 2px solid var(--card-margin-line);
}

/* Tags */
a,
a:focus,
a:hover {
  text-decoration: none;
  color: inherit;
}

/* Details */
details > summary::-webkit-details-marker {
  font-size: 1.5em;
  transition: transform 0.3s;
}

details[open] > summary::-webkit-details-marker {
  transform: rotate(90deg);
}

/* Buttons */

/* Index-card tabs: paper with a terracotta spine, resting on a flat coffee shadow.
   Same lift/press language as the deck cards on the home page. */
.button {
  background-color: var(--color-paper);
  border: 2px solid var(--color-coffee);
  border-left: 10px solid var(--color-secondary);
  color: var(--color-ink);
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 4px 2px;
  cursor: pointer;
  border-radius: 3px;
  box-shadow: 3px 3px 0 var(--color-coffee);
  transition:
    background-color 0.15s,
    transform 0.15s,
    box-shadow 0.15s;
}

/* Beats the global a:hover { color: inherit } so <a class="button"> matches <button class="button"> */
.button:hover,
.button:focus {
  color: var(--color-ink);
}

.button:hover {
  background-color: var(--color-surface);
  border-left-color: var(--color-primary);
  transform: translate(-1px, -1px);
  box-shadow: 4px 4px 0 var(--color-coffee);
}

/* Presses down onto its shadow */
.button:active {
  transform: translate(2px, 2px);
  box-shadow: 1px 1px 0 var(--color-coffee);
}

.button:focus {
  outline: none;
}

.button:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}

/* Full-width buttons: drop the side margin so they don't overflow their column */
.button.btn-block {
  margin-left: 0;
  margin-right: 0;
}

.button:disabled,
.button:disabled:hover {
  background-color: var(--color-surface-muted);
  border-left-color: var(--color-rule);
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* Secondary action: a plain card with no spine, so the main action stands out */
.button.button-alt {
  border-left-width: 2px;
}

/* Lists */
ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

li {
  display: flex;
  align-items: center;
  margin: 10px;
}

span {
  font-weight: bold;
}

/* Cards: the same spined index card as the category and tutorial cards */
.card {
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-left: 8px solid var(--color-secondary);
  border-radius: 3px;
  color: var(--color-ink);
  font-weight: 900;
  box-shadow: 3px 3px 0 var(--color-coffee);
  transition:
    transform 0.15s,
    box-shadow 0.15s;
  min-height: 155px;
}

/* Lifts toward you like a sticker: the card moves up-left as the shadow grows,
   so its "footprint" stays put and never overlaps neighbouring cards */
.card:hover {
  transform: translate(-1px, -1px);
  box-shadow: 4px 4px 0 var(--color-coffee);
}

/* ...and presses down onto its shadow when clicked */
.card-link:active .card {
  transform: translate(2px, 2px);
  box-shadow: 1px 1px 0 var(--color-coffee);
}

/* Stacks the title over the ruled area, which stretches to the bottom of the card */
.card .card-body {
  display: flex;
  flex-direction: column;
}

/* Index-card heading: the pink margin line runs edge to edge under the title.
   The negative margins cancel the card-body padding so the line meets the spine. */
.card .card-title {
  font-weight: 600;
  color: var(--color-ink);
  margin: 0 -1.25rem;
  padding: 0 1.25rem 0.5rem;
  border-bottom: 2px solid var(--card-margin-line);
}

/* Ruled area: faint blue lines every 24px, edge to edge, filling the rest of the card.
   The 24px line-height matches the rule spacing, so each line of the description
   sits on a rule, just like the flashcard. */
.card .card-text {
  flex: 1;
  margin: 0 -1.25rem;
  padding: 0 1.25rem;
  line-height: 24px;
  color: var(--color-ink-soft);
  background: repeating-linear-gradient(transparent 0 23px, var(--card-rule) 23px 24px);
}

/* Index: the link wraps the card, so it must fill the column for h-100 to work */
.card-link {
  display: block;
  height: 100%;
  border-radius: 3px;
}

.card-link:focus {
  outline: none;
}

.card-link:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 3px;
}

.stretched-link::after {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
}

.disabled {
  background-color: gray;
  pointer-events: none;
  opacity: 0.5;
  color: #ffffff;
}

/* "Coming Soon" decks: recessed blank cards, flat and dashed but still readable */
.card.disabled {
  background-color: var(--color-surface-muted);
  border: 1px dashed var(--color-rule);
  box-shadow: none;
  opacity: 1;
}

/* Blank cards keep their heading line and rules, faded into the oatmeal fill */
.card.disabled .card-title {
  border-bottom: 1px dashed var(--color-rule);
}

.card.disabled .card-text {
  background-image: repeating-linear-gradient(
    transparent 0 23px,
    color-mix(in srgb, var(--card-rule), var(--color-surface-muted) 50%) 23px 24px
  );
}

.card.disabled .card-title,
.card.disabled .card-text {
  color: #8a7768; /* Muted, but ~4:1 on the oatmeal fill */
}

.footer {
  padding: 1rem 0 2rem;
  font-size: 14px;
  opacity: 0.8;
}

/* Underlined so the handle is visibly a link, even in a quiet footer */
.footer-link {
  font-weight: 600;
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.footer-link:hover,
.footer-link:focus-visible {
  color: var(--color-secondary);
  text-decoration: underline;
}

.footer-copyright {
  font-size: 12px;
}

/* Word List: masonry columns, cards flow at their natural height */
.vocabulary-container {
  column-count: 3;
  column-gap: 20px;
}
@media (max-width: 991.98px) {
  .vocabulary-container {
    column-count: 2;
  }
}
@media (max-width: 768px) {
  .vocabulary-container {
    column-count: 1;
  }
}

.category-card {
  break-inside: avoid; /* Never split a category across two columns */
  margin-bottom: 20px;
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-left: 8px solid var(--color-secondary);
  border-radius: 3px;
  overflow: hidden;
  box-shadow: 3px 3px 0 var(--color-coffee);
  transition: box-shadow 0.15s;
}

/* Shadow only, no lift: the card is full of checkboxes, so it shouldn't move under the cursor */
.category-card:hover {
  box-shadow: 4px 4px 0 var(--color-coffee);
}

/* A category with any ticked words darkens its spine to rust, so it stands out from
   untouched cards. Its rows show which words; the spine shows the card is in use. */
.category-card:has(.category-checkbox:checked, .category-checkbox:indeterminate) {
  border-left-color: var(--color-primary);
}

/* A fully selected category also takes a burnt-orange header, a deeper shade than the
   apricot on its rows, so "whole category" reads differently from "these words" */
.category-card:has(.category-checkbox:checked) .category-header {
  background-color: var(--select-category);
}

/* The whole header is a label, so clicking the title toggles the category */
.category-header {
  transition: background-color 0.15s;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0;
  padding: 0.75rem 1rem;
  color: var(--color-ink);
  /* Index-card heading: the pink margin line sits under it */
  border-bottom: 2px solid var(--card-margin-line);
  font-size: 20px;
  cursor: pointer;
}

.category-name {
  flex: 1;
}

.category-count {
  font-size: 14px;
  color: var(--color-ink-soft);
}

.category-header input[type="checkbox"] {
  background-color: var(--color-surface);
}

.category-header input[type="checkbox"]:checked,
.category-header input[type="checkbox"]:indeterminate {
  background-color: var(--color-secondary);
}

.drop-down-icon {
  margin-right: 0.5em;
}

/* Word rows always sit inside an index card (category card, grade card, grade demo),
   so each row is one ruled line of that card */
.word {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0;
  padding: 0.6rem 1rem;
  break-inside: avoid;
  border-bottom: 1px solid var(--card-rule);
  background-color: transparent;
  transition: background-color 0.15s;
}

.word:last-child {
  border-bottom: 0;
}

label.word {
  cursor: pointer;
}

/* A faint apricot wash previews what ticking the word does */
label.word:hover {
  background-color: var(--select-hover);
}

/* Ticked words take an apricot wash, so they're easy to scan */
.word:has(.item-checkbox:checked),
label.word:has(.item-checkbox:checked):hover {
  background-color: var(--select-word);
}

/* Blank index cards shown while the word list loads (static, nothing loops). Rows are
   ruled lines sized like a word row, and the header holds a pale bar where the category
   name goes. The rows aren't .word, since selection.js treats every .word as a real word. */
.category-card-blank {
  pointer-events: none;
}

.category-card-blank .category-header {
  cursor: default;
}

.blank-row {
  min-height: 2.75rem;
  border-bottom: 1px solid var(--card-rule);
}

.blank-row:last-child {
  border-bottom: 0;
}

.blank-line {
  display: block;
  width: 45%;
  height: 0.6em;
  margin: 0.45em 0;
  background-color: var(--color-surface-muted);
  border-radius: 2px;
}

/* The real word list fades in over the blank cards instead of popping in */
.vocabulary-container.is-filled {
  animation: word-list-in 0.2s ease-out;
}

@keyframes word-list-in {
  from {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .vocabulary-container.is-filled {
    animation: none;
  }
}

/* Selection page: Continue + live counter stay pinned while scrolling the grid */
.selection-footer {
  position: sticky;
  bottom: 0;
  z-index: 10;
  margin-bottom: 0;
  padding: 0.75rem 0;
  background-color: var(--color-background);
  border-top: 2px solid var(--color-secondary);
}

.selected-count {
  font-size: 20px;
}

.selection-footer {
  flex-wrap: wrap;
}

/* --- Pill toggles: Flip Lock (quiz + selection), Direction + Script (quiz) --- */
.pill-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  cursor: pointer;
  user-select: none;
}

.pill-toggle-text {
  font-size: 16px;
}

/* Toggles whose text changes (Direction, Script) stack both labels in one grid cell
   and hide the inactive one, so the pill is always as wide as its longer label and
   the row doesn't jump when you flip it */
.toggle-label {
  display: inline-grid;
}

.toggle-label > span {
  grid-area: 1 / 1;
}

.toggle-label-on,
input:checked + .pill-toggle-text .toggle-label-off {
  visibility: hidden;
}

input:checked + .pill-toggle-text .toggle-label-on {
  visibility: visible;
}

/* Quiz: Direction / Script / Flip Lock in one wrapping row above Next */
.quiz-toggles {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.25rem;
}

/* Quiz and tutorial demo: Flip on the left and Next on the right, sharing one row */
.quiz-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
}

.quiz-actions .button {
  margin: 4px 0;
}

/* Quiz: Kass with her speech bubble above her. The bubble comes after her in the markup,
   so column-reverse stacks it on top. */
.quiz-speaker {
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 22px;
}

.quiz-mascot {
  max-width: 100%;
  max-height: 50vh;
  filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.3));
}

/* A smaller take on the speech bubble */
.quiz-bubble {
  width: 100%;
  padding: 0.75rem 1rem;
}

.quiz-bubble-text {
  font-size: 18px;
  margin: 0;
}

/* Tail under the bubble, pointing down at Kass's head. The two classes outrank the
   shared .speech-bubble tail, including its phone layout. */
.speech-bubble.quiz-bubble::before {
  top: auto;
  bottom: -12px;
  left: 50%;
  transform: translateX(-50%) rotate(-45deg);
}

/* Phones: Kass shrinks and stands to the left, with the bubble beside her face and the
   tail pointing left at her */
@media (max-width: 767.98px) {
  .quiz-speaker {
    flex-direction: row;
    align-items: flex-start;
    gap: 18px;
  }

  .quiz-mascot {
    flex-shrink: 0;
    width: 32%;
    max-width: 130px;
    max-height: none;
  }

  .quiz-bubble {
    flex: 1;
    margin-top: 1rem;
  }

  .speech-bubble.quiz-bubble::before {
    top: 50%;
    bottom: auto;
    left: -12px;
    transform: translateY(-50%) rotate(45deg);
  }
}

/* Re-shapes the shared custom checkbox into a pill switch; ::before becomes the knob */
.pill-toggle input[type="checkbox"] {
  width: 46px;
  height: 26px;
  border-radius: 999px;
  place-content: center start;
  padding: 0 3px;
  background-color: var(--color-surface);
}

.pill-toggle input[type="checkbox"]::before {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  clip-path: none;
  background-color: var(--color-secondary);
  transform: translateX(0);
}

.pill-toggle input[type="checkbox"]:checked {
  background-color: var(--color-primary);
}

.pill-toggle input[type="checkbox"]:checked::before {
  background-color: var(--color-background);
  transform: translateX(20px);
}

.pill-toggle input[type="checkbox"]:hover {
  background-color: var(--color-background);
}

.pill-toggle input[type="checkbox"]:checked:hover {
  background-color: var(--color-secondary);
}

/* Grade page: the word list is one spined index card, matching the category cards.
   The card styling waits for rows, so a "no active deck" message stays plain text. */
.grade-card:has(.word),
.grade-demo {
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-left: 8px solid var(--color-secondary);
  border-radius: 3px;
  overflow: hidden;
  box-shadow: 3px 3px 0 var(--color-coffee);
}

/* Grade rows: the same ruled .word lines as selection, just roomier */
.grade-word {
  padding: 0.75rem 1rem;
}

.grade-word.is-learned {
  opacity: 0.6;
}

/* Flipped during the quiz: locked out of earning a point this round, so the line is
   greyed out and never takes a selection wash */
.grade-word.is-flipped,
.grade-word.is-flipped:hover {
  opacity: 0.5;
  cursor: not-allowed;
  background-color: var(--color-surface-muted);
}

.flipped-tag {
  flex-shrink: 0;
  margin-right: 0.75rem;
  font-size: 14px;
  color: var(--color-primary);
}

.learned-check {
  color: var(--color-primary);
  font-size: 22px;
}

/* "2/3" correct-in-a-row badge on grade rows */
.streak-count {
  flex-shrink: 0;
  margin-right: 0.75rem;
  padding: 2px 8px;
  font-size: 14px;
  color: var(--color-background);
  background-color: var(--color-secondary);
  border-radius: 999px;
}

/* --- Final Round (quiz page) --- */
.final-round-banner {
  padding: 0.6rem 1rem;
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  color: var(--color-background);
  background-color: var(--color-primary);
  border: 2px solid var(--color-ink);
  border-radius: 10px;
  animation: final-round-pulse 1.6s ease-in-out infinite;
}

@keyframes final-round-pulse {
  50% {
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-primary), transparent 60%);
  }
}

/* Tints the cards by re-pointing the variables the paper and flap already read,
   so none of the 3D flip / deck rules below are touched */
.final-round .flashcard-container {
  --card-paper: #fbe7d3;
  --card-margin-line: var(--color-secondary);
}

@media (prefers-reduced-motion: reduce) {
  .final-round-banner {
    animation: none;
  }
}

.continue-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin: 2em 0 3em;
}

/* Grade page mascot: stays in view while scrolling a long word list */
.grade-mascot {
  position: sticky;
  top: 2rem;
}

.grade-mascot img {
  max-height: 45vh;
  filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.3));
}

.grade-score {
  font-size: 24px;
  margin-top: 0.5em;
}

@media (max-width: 767.98px) {
  .grade-mascot {
    position: static;
  }
  .grade-mascot img {
    max-height: 25vh;
  }
}

/* --- Results Overlay (grade page) --- */
.no-scroll {
  overflow: hidden;
}

.results-overlay {
  position: fixed;
  inset: 0;
  z-index: 1050;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  overflow-y: auto;
  background: radial-gradient(
    circle at center,
    var(--color-secondary),
    var(--color-primary)
  );
  animation: results-fade 0.4s ease-out;
}

/* display:flex above would otherwise override the hidden attribute */
.results-overlay[hidden] {
  display: none;
}

.results-panel {
  width: 100%;
  max-width: 520px;
  padding: 2rem;
  text-align: center;
  background-color: var(--color-background);
  border: 2px solid var(--color-ink);
  border-radius: 16px;
  box-shadow: var(--shadow-raised);
  animation: results-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.results-mascot {
  max-height: 35vh;
  margin-top: -1rem;
  filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.3));
  animation: results-bounce 1.2s ease-in-out 0.5s 2;
}

.results-title {
  font-size: 48px;
  color: var(--color-primary);
  margin: 0.5rem 0 0.25rem;
}

.results-text {
  font-size: 20px;
  margin-bottom: 1.5rem;
}

.results-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
}

.results-actions .button {
  margin: 0;
}

@keyframes results-fade {
  from {
    opacity: 0;
  }
}

@keyframes results-pop {
  from {
    opacity: 0;
    transform: scale(0.85) translateY(20px);
  }
}

@keyframes results-bounce {
  0%,
  100% {
    transform: translateY(0);
  }
  30% {
    transform: translateY(-14px);
  }
  50% {
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .results-overlay,
  .results-panel,
  .results-mascot {
    animation: none;
  }
}

/* --- Tutorial page --- */
.tutorial-main {
  max-width: 960px;
  padding-bottom: 3rem;
}

.tutorial-hero {
  margin: 2.5rem 0 1rem;
}

.tutorial-hero-mascot {
  max-height: 320px;
  filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.3));
}

/* Speech bubble pointing back at Kass: a plain paper card (no spine) with a tail. Kass sits
   to the left on desktop and above when the columns stack, so the tail follows her. */
.speech-bubble {
  position: relative;
  padding: 1.5rem;
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-radius: 3px;
  box-shadow: 3px 3px 0 var(--color-coffee);
}

.speech-bubble::before {
  content: "";
  position: absolute;
  top: 50%;
  left: -12px;
  width: 20px;
  height: 20px;
  background-color: var(--card-paper);
  border-left: 2px solid var(--color-coffee);
  border-bottom: 2px solid var(--color-coffee);
  transform: translateY(-50%) rotate(45deg);
}

@media (max-width: 767.98px) {
  .speech-bubble::before {
    top: -12px;
    left: 50%;
    transform: translateX(-50%) rotate(135deg);
  }
}

.tutorial-bubble-text {
  font-size: 20px;
  margin-bottom: 1rem;
}

.tutorial-loop {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.tutorial-loop-step {
  margin: 0;
  gap: 0.4rem;
  padding: 0.35rem 0.8rem 0.35rem 0.35rem;
  font-weight: 600;
  /* Little paper tabs, square-cornered like the cards */
  background-color: var(--card-paper);
  border: 1px solid var(--color-rule);
  border-radius: 3px;
}

.tutorial-loop-num {
  display: inline-grid;
  place-content: center;
  width: 26px;
  height: 26px;
  font-size: 14px;
  color: var(--color-paper);
  background-color: var(--card-stamp);
  border-radius: 2px; /* A small hanko-style seal holding the step number */
}

.tutorial-section {
  margin-top: 3rem;
  scroll-margin-top: 1.5rem;
}

/* Concept card: the same spined index card as the category cards. No hover lift,
   since the demos inside are interactive. */
.tutorial-card {
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-left: 8px solid var(--color-secondary);
  border-radius: 3px;
  overflow: hidden;
  box-shadow: 3px 3px 0 var(--color-coffee);
}

/* Index-card heading: ink title over the pink margin line */
.tutorial-card-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  color: var(--color-ink);
  border-bottom: 2px solid var(--card-margin-line);
}

/* Square hanko stamp holding the section icon, in the same red as the step numbers
   (paper on hanko red clears 4.5:1, which paper on terracotta doesn't) */
.tutorial-card-icon {
  display: grid;
  place-content: center;
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  font-size: 20px;
  color: var(--color-paper);
  background-color: var(--card-stamp);
  border: 2px solid var(--color-coffee);
  border-radius: 3px;
}

.tutorial-card-title {
  margin: 0;
  font-size: 26px;
}

/* Ruled area: faint blue lines every 24px with a 24px line-height, so every line of
   text sits on a rule. The rules start at the content box, so they line up with the
   text below the top padding and still run through the padding edge to edge. */
.tutorial-card-body {
  padding: 12px 1.25rem 24px;
  line-height: 24px;
  background: repeating-linear-gradient(transparent 0 23px, var(--card-rule) 23px 24px);
  background-origin: content-box;
}

/* The tagline leaves one blank ruled line before the points */
.tutorial-tagline {
  font-size: 18px;
  font-weight: 600;
  margin: 0 0 24px;
}

/* No vertical margin, so wrapped points stay on the rules. Baseline alignment keeps
   each icon beside the first line of a long point. */
.tutorial-points li {
  align-items: baseline;
  gap: 0.75rem;
  margin: 0;
}

.tutorial-points .icon {
  flex-shrink: 0;
  width: 20px;
  color: var(--color-primary);
}

/* Demo tray beside each card: a flat oatmeal patch of desk that the demo cards rest on,
   with no shadow so the cards on it stay the raised layer */
.tutorial-demo {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  min-height: 220px;
  padding: 1.5rem;
  color: var(--color-ink-soft);
  background-color: var(--color-surface-muted);
  border: 1px solid var(--color-rule);
  border-radius: 3px;
}

/* Live flip demo: the real flashcard, sized down for the tutorial slot */
.demo-flashcard {
  width: 100%;
  max-width: 360px;
  height: 200px;
}

.demo-card-text {
  font-size: 2rem;
  line-height: 1.2;
  word-break: keep-all;
  overflow-wrap: break-word;
  width: 100%;
}

.demo-status {
  margin: 0.5rem 0 0;
  /* Always reserve two lines, so longer messages don't resize the demo panel */
  min-height: 3em;
  line-height: 1.5;
  text-align: center;
  font-weight: 600;
}

.demo-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem 1.25rem;
}

.demo-controls .button {
  margin: 0;
  padding: 8px 18px;
}

/* Flip and Next share a row under the toggle, as wide as the demo card */
.demo-actions {
  width: 100%;
  max-width: 360px;
}

/* Lets the demo card fly out past the page edge on Next without a scrollbar flash */
.tutorial-page {
  overflow-x: clip;
}

.tutorial-demo-label {
  font-size: 16px;
}

/* Mastery mock: three words at different streaks, one per ruled line of a spined
   index card like the grade demo below it */
.streak-demo {
  width: 100%;
  max-width: 300px;
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-left: 8px solid var(--color-secondary);
  border-radius: 3px;
  box-shadow: 3px 3px 0 var(--color-coffee);
}

.streak-demo-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--card-rule);
  color: var(--color-ink);
}

.streak-demo-row:last-child {
  border-bottom: 0;
}

.streak-pips {
  display: flex;
  gap: 6px;
}

/* Each correct round earns a hanko stamp; empty slots are faint outlines */
.streak-pips i {
  width: 14px;
  height: 14px;
  border: 1px solid var(--color-rule);
  border-radius: 2px;
}

.streak-pips i.is-on {
  border-color: var(--card-stamp);
  background-color: var(--card-stamp);
}

/* The banner mock stays still: nothing on the How to Use page moves on its own */
.tutorial-demo .final-round-banner {
  animation: none;
}

/* Self-grading mock: two real grade rows, one ticked and one flipped, on a grade card */
.grade-demo {
  width: 100%;
  max-width: 360px;
  color: var(--color-ink);
}

/* Bar text sits beside the button, so it's smaller than a section heading */
.tutorial-cta-title {
  font-size: 24px;
}

/* --- Feedback page --- */
.feedback-main {
  max-width: 960px;
  padding-bottom: 3rem;
}

.feedback-mascot {
  max-height: 320px;
  filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.3));
}

/* Same index card as the tutorial cards */
.feedback-card {
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-left: 8px solid var(--color-secondary);
  border-radius: 3px;
  overflow: hidden;
  box-shadow: 3px 3px 0 var(--color-coffee);
}

.feedback-card-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  color: var(--color-ink);
  border-bottom: 2px solid var(--card-margin-line);
}

.feedback-form,
.feedback-thanks {
  padding: 1.25rem;
}

.feedback-label {
  font-weight: 600;
  font-size: 18px;
}

.feedback-form .form-control {
  font-family: inherit;
  border: 2px solid var(--color-ink);
  border-radius: 10px;
}

.feedback-form .form-control:focus {
  border-color: var(--color-ink);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary), transparent 60%);
}

/* Bootstrap 4 fixes the select height, which clips Klee One + our 2px border */
.feedback-form select.form-control {
  height: auto;
  min-height: calc(1.5em + 1rem + 4px);
  padding: 0.5rem 0.75rem;
  line-height: 1.5;
}

.feedback-form textarea.form-control {
  resize: vertical;
}

.feedback-hint {
  display: block;
  margin-top: 0.25rem;
  text-align: right;
  opacity: 0.7;
}

.feedback-error {
  color: var(--color-primary);
  font-weight: 600;
}

.feedback-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.75rem;
}

.feedback-actions .button {
  margin: 0;
}

.feedback-thanks {
  text-align: center;
}

.feedback-thanks .tutorial-card-title {
  color: var(--color-primary);
  margin-bottom: 0.5rem;
}

/* Letter layout: "Re:" sits on one line with the category written on a blank after it */
.letter-re {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

/* The category select is a fill-in blank: no box, just a ruled line to write on */
.feedback-form .letter-blank {
  flex: 1;
  border: 0;
  border-bottom: 1px solid var(--card-rule);
  border-radius: 0;
  background-color: transparent;
  padding-left: 0.25rem;
}

/* The message is written straight onto blue ruled lines. The 24px line-height matches
   the rule spacing so each line of text sits on a rule, and "local" scrolls the rules
   with the text once the letter grows past the box. */
.feedback-form .letter-body {
  padding: 0 0.25rem 0 0.5rem;
  font-size: 18px;
  line-height: 24px;
  border: 0;
  border-left: 2px solid transparent;
  border-radius: 0;
  background: repeating-linear-gradient(transparent 0 23px, var(--card-rule) 23px 24px) local;
  transition: border-color 0.15s;
}

/* Focus shows as a rust margin line beside the writing, rather than a box around it */
.feedback-form .letter-blank:focus,
.feedback-form .letter-body:focus {
  background-color: transparent;
  box-shadow: none;
}

.feedback-form .letter-blank:focus {
  border-bottom: 2px solid var(--color-primary);
}

.feedback-form .letter-body:focus {
  border-left-color: var(--color-primary);
}

.letter-salutation {
  font-size: 20px;
}

/* Sign-off sits under the message on the right, like the end of a letter */
.letter-signoff {
  margin: 0.5rem 0 0;
  text-align: right;
  font-size: 18px;
  color: var(--color-ink-soft);
}

/* Sending: the letter folds in half, then flies off to the right like it's being posted.
   main clips it so the flight never adds a horizontal scrollbar. */
.feedback-main {
  overflow-x: clip;
}

.feedback-card.is-sending {
  transform-origin: top center;
  animation: letter-send 0.7s ease-in forwards;
  pointer-events: none;
}

@keyframes letter-send {
  40% {
    transform: scaleY(0.5);
  }
  100% {
    transform: translate(120%, -30%) rotate(8deg) scaleY(0.5);
    opacity: 0;
  }
}

/* The thank-you note drops in after the letter has gone */
.feedback-card.is-arrived {
  animation: letter-arrive 0.3s ease-out;
}

@keyframes letter-arrive {
  from {
    transform: translateY(8px);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .feedback-card.is-sending,
  .feedback-card.is-arrived {
    animation: none;
  }
}

/* --- Kass mascot: tap to bounce (app.js) --- */
.kass {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Uses the standalone translate/scale properties, so it stacks on top of any
   existing transform (quiz's scale(1.1), the results-screen bounce) instead of replacing it */
.kass.is-bouncing {
  animation: kass-bounce 0.45s ease-out;
}

@keyframes kass-bounce {
  0% {
    translate: 0 0;
    scale: 1 1;
  }
  20% {
    translate: 0 0;
    scale: 1.04 0.96;
  }
  50% {
    translate: 0 -12px;
    scale: 0.98 1.03;
  }
  80% {
    translate: 0 0;
    scale: 1.02 0.98;
  }
  100% {
    translate: 0 0;
    scale: 1 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .kass.is-bouncing {
    animation: none;
  }
}

/* Inputs */

/* Custom checkbox: same ink outline + radius language as .button */
input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  flex-shrink: 0;
  display: grid;
  place-content: center;
  width: 22px;
  height: 22px;
  margin: 0;
  background-color: var(--color-surface);
  border: 2px solid var(--color-ink);
  border-radius: 6px;
  cursor: pointer;
  transition: background-color 0.15s;
}

input[type="checkbox"]::before {
  content: "";
  width: 12px;
  height: 12px;
  background-color: var(--color-background);
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
  transform: scale(0);
  transition: transform 0.15s ease-in-out;
}

input[type="checkbox"]:checked {
  background-color: var(--color-primary);
}

input[type="checkbox"]:checked::before {
  transform: scale(1);
}

/* Partially selected category: a dash instead of a tick */
input[type="checkbox"]:indeterminate {
  background-color: var(--color-primary);
}

input[type="checkbox"]:indeterminate::before {
  clip-path: inset(38% 5%);
  transform: scale(1);
}

input[type="checkbox"]:hover {
  background-color: var(--color-background);
}

input[type="checkbox"]:checked:hover {
  background-color: var(--color-secondary);
}

input[type="checkbox"]:disabled,
input[type="checkbox"]:disabled:hover {
  background-color: var(--color-surface-muted);
  border-style: dashed;
  cursor: not-allowed;
}

input[type="checkbox"]:focus {
  outline: none;
}

input[type="checkbox"]:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}

.english,
.hiragana,
.japanese,
.romaji {
  flex: 1;
  padding-right: 30px;
  text-align: left;
}

/* --- 3D Flip Engine --- */

/* One animatable value drives both the paper cut and the flap size */
@property --peel {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

.flashcard-body {
  position: relative;
  width: 100%;
  height: 100%;
  transition: transform 0.6s cubic-bezier(0.4, 0.2, 0.2, 1);
  transform-style: preserve-3d;
  cursor: pointer;
}

.flashcard-body.flipped {
  transform: rotateY(180deg);
}

/* Layer 1 (3D only): no filter, clip-path or shadow here, so nothing flattens the flip */
.flashcard-face {
  position: absolute;
  inset: 0;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

.flashcard-front {
  transform: rotateY(0deg) translateZ(1px);
}

/* The two 180deg rotations cancel out, so the back face's local bottom-right IS the visual bottom-right */
.flashcard-back {
  transform: rotateY(180deg) translateZ(1px);
}

/* Layer 2 (flat 2D): drop-shadow traces the cut paper AND the flap, so no square shadow corner */
.flashcard-surface {
  position: relative;
  width: 100%;
  height: 100%;
  color: var(--color-ink);
  font-weight: 900;
  /* Flat offset shadow; still a filter here so it traces the cut paper and the flap */
  filter: drop-shadow(3px 3px 0 var(--color-coffee));
  transition: --peel 0.3s ease-out;
}

.flashcard-body:hover .flashcard-surface {
  --peel: 45px;
}

/* Quiz and tutorial demo: only the Flip button flips, so the card itself isn't clickable
   and doesn't peel under the cursor. Hovering or focusing the Flip button peels the
   front card instead. */
.quiz-page .flashcard-body,
.tutorial-demo .flashcard-body {
  cursor: default;
}

.quiz-page .flashcard-body:hover .flashcard-surface,
.tutorial-demo .flashcard-body:hover .flashcard-surface {
  --peel: 0px;
}

.quiz-page:has(#flipButton:enabled:is(:hover, :focus-visible))
  .deck-card.is-active
  .flashcard-surface,
.tutorial-demo:has(#demoFlip:enabled:is(:hover, :focus-visible))
  .deck-card.is-active
  .flashcard-surface {
  --peel: 45px;
}

/* Layer 3: the paper. The clip-path is on this pseudo-element, not on anything carrying the filter.
   Both states have 5 points, so the cut animates instead of snapping. */
.flashcard-surface::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Ruled index card, all gradients (no images): pink margin line near the top
     and faint blue rules below it */
  background:
    linear-gradient(var(--card-margin-line), var(--card-margin-line)) no-repeat 0 34px / 100% 1.5px,
    repeating-linear-gradient(
        transparent 0 23px,
        var(--card-rule) 23px 24px
      )
      0 34px / 100% calc(100% - 34px) no-repeat,
    var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-radius: 4px;
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--peel)),
    calc(100% - var(--peel)) 100%,
    0 100%
  );
}

/* The folded flap: the cut-away triangle mirrored across the crease.
   Its top and left borders are the flap's outer edges, and the gradient's 50% line is the crease. */
.flashcard-surface::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  width: var(--peel);
  height: var(--peel);
  box-sizing: border-box;
  border-style: solid;
  border-color: var(--color-coffee);
  border-width: min(2px, var(--peel)) 0 0 min(2px, var(--peel));
  border-top-left-radius: 4px;
  /* The flap is the unruled back of the card, shaded darker toward the crease */
  background: linear-gradient(
      to bottom right,
      color-mix(in srgb, var(--card-paper), var(--color-coffee) 6%),
      color-mix(in srgb, var(--card-paper), var(--color-coffee) 16%) calc(50% - 1.5px),
      var(--color-coffee) calc(50% - 1.5px) 50%,
      transparent 50%
    )
    border-box;
  /* Shadow the lifted flap casts back onto the card */
  filter: drop-shadow(-2px -2px 3px rgba(0, 0, 0, 0.3));
  pointer-events: none;
}

/* Double-click flipping can select the card text: keep the selection (so copy still works)
   but paint it invisibly. Color is explicit because 'inherit' in ::selection resolves to the
   parent's highlight in newer Chrome, which would turn the text black. */
.flashcard-surface::selection,
.flashcard-surface *::selection {
  background: transparent;
  color: var(--color-ink);
}

/* --- Deck: two-card "Next" animation ---
   Lives on a wrapper around .flashcard-body, so the flip transform,
   the face layers and the --peel corner are left completely alone. */

/* Lets the outgoing card slide past the viewport edge without a scrollbar flash */
.quiz-page {
  overflow-x: clip;
}

.flashcard-container {
  position: relative;
}

/* The rest of the deck: one more card edge behind the staged card. It hangs off the
   container, not a .deck-card, so it never moves with the flip or the Next animation. */
.flashcard-container::before {
  content: "";
  position: absolute;
  inset: 0;
  transform: translate(10px, 10px);
  background-color: var(--card-paper);
  border: 2px solid var(--color-coffee);
  border-radius: 4px;
}

.deck-card {
  position: absolute;
  inset: 0;
  /* Perspective moved here from the container: an intermediate transformed
     wrapper would otherwise flatten the flip into an orthographic one */
  perspective: 1000px;
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}

.deck-card.is-active {
  z-index: 2;
  transform: none;
}

/* Waiting underneath, peeking out bottom-right so it reads as a stack of index cards */
.deck-card.is-staged {
  z-index: 1;
  transform: translate(5px, 5px);
  pointer-events: none;
}

/* Hides the text of the card underneath so it doesn't ruin the flip illusion */
.deck-card.is-staged .flashcard-surface {
  color: transparent;
  transition: color 0.3s ease-in; /* Smoothly fades the text in when it slides to the front */
}

/* Outgoing card: no pointer events, so its peel retracts as it leaves */
.deck-card.is-leaving {
  pointer-events: none;
  animation: deck-cycle 0.9s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

/* Lifted off the deck (up, a touch bigger, tilted), then out to the right on top, drop
   behind once fully clear, and tuck back into the staged pose. Every step lists the same
   transform functions so each one blends smoothly into the next. */
@keyframes deck-cycle {
  0% {
    z-index: 3;
    transform: translate(0, 0) rotate(0deg) scale(1);
  }
  22% {
    z-index: 3;
    transform: translate(-6px, -16px) rotate(-2deg) scale(1.03);
  }
  59.9% {
    z-index: 3;
  }
  60% {
    z-index: 0;
    transform: translate(115%, -16px) rotate(6deg) scale(1.03);
  }
  100% {
    z-index: 0;
    transform: translate(5px, 5px) rotate(0deg) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .deck-card {
    transition: none;
  }
  .deck-card.is-leaving {
    animation-duration: 1ms;
  }
}
