/* N5 practice. Sits on top of drill.css exactly like kana.css does - the
   quiz mechanics are shared, this file is what is specific to N5: the
   stroke-order display on a kanji card, the multiple-choice answer used by
   Kosakata (Arti), and the machine-translation notice both meaning-facing
   modes carry. */

/* .kn-modes is a 3-column grid built for kana's three practice types. N5
   has six tiles now - two even columns, wrapping to three rows, rather
   than three with gaps.
   This does NOT get overridden by kana.css's own mobile rule the way the
   comment here used to claim: .js-n5-setup .kn-modes (two classes) beats
   plain .kn-modes (one class) on specificity regardless of which one sits
   inside a media query, so without the second rule below this stayed
   two-column on every phone width - confirmed live by a screenshot, not
   caught by anything in this session's own testing, which never rendered
   real CSS against a real viewport at all. */
.js-n5-setup .kn-modes { grid-template-columns: repeat(2, 1fr); }

@media (max-width: 700px) {
  .js-n5-setup .kn-modes { grid-template-columns: 1fr; }
}

/* Small, not shouty: this is a disclosure, not a warning. Reused as both
   the inline note on the Kosakata (Arti) mode tile and the banner atop the
   quiz screen for both meaning-facing modes. */
.kn-mt-flag {
  display: inline-block;
  color: var(--c-muted);
  font-style: italic;
}
p.kn-mt-flag {
  margin: 0 0 14px;
  padding: 8px 12px;
  border-radius: 10px;
  background: var(--c-wash);
  font-size: 12.5px;
  font-style: normal;
}

.kn-lane--kanji { height: 190px; }
.kn-card--kanji { padding-top: 10px; }

/* Kosakata (Arti) shows the reading alongside the expression - it is given
   information there, not the thing being tested, so unlike .kn-card__romaji
   it is never hidden. */
.kn-card__given {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .03em;
  color: var(--c-muted);
}

/* Four answer buttons instead of the text box, used by Kosakata (Arti)
   only - see n5.js's MODES.kosakataArti.inputType. Two columns so all four
   read as one deliberate choice rather than a list to scan top to bottom. */
.kn-choices {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-top: 22px;
}
.kn-choice {
  padding: 14px 16px;
  border: 2px solid var(--c-border);
  border-radius: 14px;
  background: #fff;
  font: inherit;
  font-size: 14px;
  color: var(--c-body);
  text-align: left;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.kn-choice:hover:not(:disabled) { border-color: var(--c-pink-light); }
.kn-choice:disabled { cursor: default; }
.kn-choice.is-right { border-color: #43b581; background: #f2fbf6; color: #2f8a5f; font-weight: 700; }
.kn-choice.is-wrong { border-color: #e05b5b; background: #fef4f4; color: #c94848; }

@media (max-width: 700px) {
  .kn-choices { grid-template-columns: 1fr; }
}

/* The stroke SVG replaces the plain character once it loads - see n5.js
   renderStroke(). Sized down from KanjiVG's own 109px viewBox so it reads at
   the same visual weight as a kana card; the small grey numbers KanjiVG
   draws next to each stroke are left as they are, not hidden, since knowing
   the order is most of the point of showing this instead of plain text. */
.kn-card__stroke {
  width: 72px;
  height: 72px;
}
.kn-card__stroke path {
  stroke: var(--c-body);
}
.kn-card.is-active .kn-card__stroke path { stroke: var(--c-pink); }

@media (max-width: 1024px) and (min-width: 701px) {
  .kn-lane--kanji { height: 176px; }
}

@media (max-width: 700px) {
  .kn-lane--kanji { height: 168px; margin: 0 -16px; }
  .kn-card__stroke { width: 56px; height: 56px; }
}

/* ============================ tata bahasa ============================ */
/* A browse list, not a lane quiz - see n5.js showGrammar(). Every pattern
   is shown expanded rather than click-to-reveal: ten of them is short
   enough that a toggle would be one more click for no real gain. */

.kn-grammar-pattern {
  border: 1px solid var(--c-border);
  border-radius: 16px;
  padding: 18px 20px;
  margin-bottom: 14px;
}
.kn-grammar-pattern__name {
  margin: 0 0 4px;
  font-family: 'Montserrat Alternates', sans-serif;
  font-size: 17px;
  font-weight: 800;
  color: var(--c-pink);
}
.kn-grammar-pattern__structure {
  margin: 0 0 10px;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 14px;
  color: var(--c-body);
}
.kn-grammar-pattern__explanation {
  margin: 0 0 14px;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--c-body);
}
.kn-grammar-example {
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--c-wash);
  margin-bottom: 8px;
}
.kn-grammar-example__jp {
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 15px;
  color: var(--c-body);
}
.kn-grammar-example__reading {
  font-size: 12px;
  color: var(--c-muted);
  margin: 2px 0;
}
.kn-grammar-example__translation {
  font-size: 13px;
  color: var(--c-body);
}
.kn-grammar-example__source {
  margin-top: 4px;
  font-size: 11px;
  color: var(--c-muted);
}

/* ============================ menyimak ============================ */

/* Kosakata (Menyimak): what is normally the answer key (.kn-card__romaji,
   already hidden until answered) is unchanged, but the expression itself -
   normally shown up front everywhere else - is also hidden here, since
   seeing 水 while listening for みず would make the audio pointless. Reuses
   the exact reveal trigger .kn-card__romaji already has (is-right/is-wrong/
   is-revealed), just applied to a second element. */
.kn-card__kana--hidden { opacity: 0; transition: opacity .2s; }
.kn-card.is-right .kn-card__kana--hidden,
.kn-card.is-wrong .kn-card__kana--hidden,
.kn-card.is-revealed .kn-card__kana--hidden { opacity: 1; }

.kn-card__audio-icon {
  font-size: 28px;
  line-height: 1;
  margin-bottom: 4px;
}

.js-n5-replay { margin-bottom: 14px; }

/* ── One card, Anki style ──────────────────────────────────────────────────
   Replaces the sliding lane for N5. The lane is still right for kana; it is
   wrong here because it queues cards ahead of the one being answered, and a
   spaced-repetition scheduler must choose the next card only after the current
   one is graded. See research/v2 section 1. */

.kn-lane--solo {
  height: auto;
  min-height: 200px;
  /* .kn-lane's own base rule (drill.css) bleeds -24px past its container on
     purpose, for the sliding carousel's fade-to-nothing edge effect
     (.kn-lane__fade) - a single centered card has no such edge to fade, so
     without resetting it back to 0 here that same bleed just pushes a
     solo card's content past the container's own padding on a narrow
     phone, right up against the screen edge. */
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible;
}

.kn-lane--solo .kn-lane__track {
  transform: none !important;
  transition: none !important;
  display: flex;
  justify-content: center;
  width: 100%;
  /* .kn-lane__track's own base rule (drill.css) reserves 24px on the left
     so the carousel's first queued card doesn't start flush against the
     edge before sliding in - solo mode has no queue to make room for, so
     left uncleared it just offsets the centered card 24px right of true
     centre, the same shape of bug .kn-lane's own bled margin was above. */
  padding-left: 0;
}

/* Full width and no scale-down: this card is the only thing being looked at,
   so it has no reason to be small or dimmed the way a queued one did. */
.kn-card--solo {
  flex: 1 1 auto;
  max-width: 560px;
  min-width: 0;
  opacity: 1;
  transform: none;
  padding: 28px 26px;
  background: #fff;
  border-color: var(--c-pink, #e5307d);
  box-shadow: 0 6px 16px rgba(229, 48, 125, .08);
}

/* This card always also carries .is-active (drill.css's own base .kn-card
   state) - that rule's box-shadow (0 8px 24px, .16 opacity) has higher
   specificity than .kn-card--solo alone (two classes vs one) and was
   winning over the softer shadow just above, not the value actually
   being edited when this looked too strong. Three classes here beats
   both two-class rules outright, so this is the one that actually shows. */
.kn-card.kn-card--solo.is-active { box-shadow: 0 6px 16px rgba(229, 48, 125, .08); }

.kn-card--solo.is-right { border-color: #43b581; background: #f2fbf6; }
.kn-card--solo.is-wrong { border-color: #e05b5b; background: #fef4f4; }

/* ── The answer side ─────────────────────────────────────────────────── */

.kn-back {
  max-width: 560px;
  min-height: 260px;
  margin: 12px auto 0;
  padding: 16px 20px;
  border: 1.5px dashed var(--c-border, #e2e2e2);
  border-radius: 16px;
  background: var(--c-wash, #fff7fb);
  text-align: center;
  box-sizing: border-box;
}

.kn-back__answer {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--c-body, #575757);
  line-height: 1.4;
}

/* ── Two buttons ─────────────────────────────────────────────────────── */

.kn-grade {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  max-width: 560px;
  margin: 14px auto 0;
}

.kn-grade__btn {
  /* A grid item's default min-width is auto, not 0 - it refuses to shrink
     narrower than its content's own min-content width (roughly the widest
     unbreakable run inside it, here the interval text like "(10 menit)"),
     even though the track above is a plain 1fr. On a narrow phone that
     forced width is wider than half the container, so the button (and the
     grid holding it) overflows past the edge instead of the label/interval
     wrapping or shrinking. This is the actual fix, not a smaller padding. */
  min-width: 0;
  font: inherit;
  font-weight: 700;
  border: 0;
  border-radius: 999px;
  padding: 14px 18px;
  cursor: pointer;
  color: #fff;
  transition: transform .12s ease, filter .12s ease;
}

.kn-grade__btn:hover { filter: brightness(1.06); }
.kn-grade__btn:active { transform: translateY(1px); }

/* FSRS's real computed interval for that grade - see n5.js
   setGradePreview(). Lighter than the label, same line: Anki puts this on
   a line of its own, but two lines makes the button noticeably taller only
   for the modes that have a scheduler to preview from, which reads as
   inconsistent sizing rather than a feature. */
.kn-grade__interval {
  font-weight: 500;
  opacity: .85;
}

/* Red and green rather than Anki's four greys: with only two choices the
   colours carry the meaning and nobody has to read the labels twice. */
.kn-grade__btn--again { background: #e05b5b; }
.kn-grade__btn--easy  { background: #43b581; }

/* 700px, not 560px: kana.css's own .kn-setup/.kn-quiz/.kn-recap breakpoint
   (the outer container) is already 700px, so a narrower threshold here left
   a real gap between 561-700px where the outer chrome had already shrunk
   but these inner rules had not kicked in yet - the outer box compact, the
   inner card still reserving its full desktop min-height, which reads as
   exactly "the card is too big for its container". */
@media (max-width: 700px) {
  .kn-card--solo { padding: 22px 16px; }
  /* min-height dropped much further than the desktop 260px: that height
     exists to stop the page jumping around as answers of different
     lengths flip in, but at 200px on a phone the reveal box was mostly
     empty space above a couple of short lines - the exact "gap between
     card and container" complaint. 90px still absorbs a one/two-line
     answer without visible jump, just without reserving room for a
     desktop-width paragraph nothing here shows on a phone. */
  .kn-back { padding: 14px 16px; min-height: 90px; }
  .kn-grade__btn { padding: 15px 12px; }
  .kn-setup, .kn-quiz, .kn-recap { padding: 16px 14px; margin: 20px 0 16px; }
  /* Same over-allocation as .kn-back just above, same fix - a solo card's
     own content (a character plus a short sentence line) rarely needs
     200px on a phone. */
  .kn-lane--solo { min-height: 120px; }
}

/* Grading/advancing pinned to the bottom of the screen on a phone, instead
   of wherever the current card's content happens to end - a short card put
   it right under the card, a long one (an image, a full mnemonic, a hint
   expanded) pushed it past the fold, so "where do I tap to continue" moved
   around by card instead of staying in one predictable spot. .kn-reveal
   covers both the real "Lihat jawaban" button and Bacaan's "Lanjut" (same
   class, see study.html's markup), so this one rule covers every one of
   these Anki-style cards without a second copy per screen. Desktop is left
   alone: there is room there for the button to just sit under the content. */
/* kana.css already drops the eyebrow/title while a keyboard is up
   (body.is-keyboard.is-practicing) so the lane and answer box stay on
   screen together - but a flip-graded card (Arti, Radikal, Kanji Arti)
   never focuses a text input, so is-keyboard never gets added, and the
   heading sat there year-round on those three cards. Once a round is
   actually running, the track name repeating "Pembelajaran Lanjutan /
   Jalur Kanji" above every single card is not information, it is the same
   fact as the URL and the hub link just visited - dropped for any active
   round, any width, not only once a keyboard forced the issue. */
body.is-practicing .c-eyebrow,
body.is-practicing .l-intro__title {
  display: none;
}

@media (max-width: 700px) {
  .kn-reveal, .kn-grade {
    position: sticky;
    bottom: 0;
    z-index: 5;
    background: #fff;
    border-top: 1px solid var(--c-border, #e2e2e2);
    margin: 14px -16px 0;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  }
}

/* ── Hub: today, then the three tracks ─────────────────────────────────
   Borrows the house language rather than inventing one: the gradient top rule
   from /form's header card, the pastel tint and soft pink shadow from
   /charavote's rows, the pill eyebrow from this page's own N5 banner. */

/* ============================ hasil latihan (recap) ===================== */

/* .kn-recap/.kn-stat/.kn-weak are drill.css's, shared with /kana/'s own
   recap - which is right for /kana/'s fast kana drill, and was never
   restyled for Pembelajaran Lanjutan, so this screen was the one place
   here still wearing that plain flat look instead of the house card
   language the rest of this page already uses (.kn-today just below,
   .kn-card--solo above). Scoped to .js-n5-recap - the class only this
   page's recap section carries - so /kana/'s own recap is untouched. */
.js-n5-recap.kn-recap {
  position: relative;
  padding: 28px 26px;
  border: 1.5px solid var(--c-border, #e2e2e2);
  border-top: 6px solid transparent;
  border-radius: 20px;
  background-image: linear-gradient(#fff, #fff),
                    linear-gradient(90deg, var(--c-cyan, #79d7f3), var(--c-pink, #e5307d));
  background-origin: border-box;
  background-clip: padding-box, border-box;
  box-shadow: 0 10px 30px rgba(229, 48, 125, .08);
}

/* Same tile language as .kn-today__mark just below: white, bordered,
   Montserrat Alternates number - rather than the flat wash-tinted squares
   drill.css's .kn-stat draws for /kana/. */
.js-n5-recap .kn-stat {
  background: #fff;
  border: 1.5px solid var(--c-border, #e2e2e2);
  border-radius: 14px;
  padding: 14px;
}
.js-n5-recap .kn-stat b {
  font-family: 'Montserrat Alternates', sans-serif;
}

.js-n5-recap .kn-weak {
  border-radius: 14px;
}

.js-n5-recap .kn-share {
  margin-top: 24px;
  padding-top: 22px;
  border-top: 1.5px dashed var(--c-border, #e2e2e2);
}

@media (max-width: 560px) {
  .js-n5-recap.kn-recap { padding: 20px 18px; }
}

.kn-today {
  position: relative;
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
  padding: 22px 24px;
  border: 1.5px solid var(--c-border, #e2e2e2);
  border-top: 6px solid transparent;
  border-radius: 20px;
  margin-bottom: 20px;
  /* Two layers: the flat card, then the gradient showing only through the
     transparent top border. */
  background-image: linear-gradient(#fff, #fff),
                    linear-gradient(90deg, var(--c-cyan, #79d7f3), var(--c-pink, #e5307d));
  background-origin: border-box;
  background-clip: padding-box, border-box;
  box-shadow: 0 10px 30px rgba(229, 48, 125, .08);
}

.kn-today__body {
  flex: 1 1 240px;
  /* The page wrapper is centred, and that leaked in: the label and the note
     inherited it while the number sat in a flex row and stayed left, so the
     three lines each landed somewhere different. */
  text-align: left;
}

/* Sits directly above the number as its caption, not floating on its own line
   across the card. */
.kn-today__label {
  font-size: .62rem; font-weight: 800; letter-spacing: .22em;
  text-transform: uppercase; color: var(--c-pink, #e5307d);
  margin: 0 0 2px;
}

.kn-today__time {
  margin: 0;
  font-size: .95rem;
  color: var(--c-muted, #888);
  display: flex;
  align-items: baseline;
  gap: 7px;
  line-height: 1;
}
.kn-today__time b {
  font-family: 'Montserrat Alternates', sans-serif;
  font-size: 2.6rem;
  font-weight: 800;
  line-height: 1;
  color: var(--c-pink, #e5307d);
  font-variant-numeric: tabular-nums;
}

/* Hangs off the number rather than starting a new column of its own. */
.kn-today__note {
  margin: 10px 0 0;
  font-size: .84rem;
  line-height: 1.5;
  color: var(--c-body, #575757);
  max-width: 34ch;
}

/* Three marks rather than a progress bar. A bar that is never full reads as
   failing; three marks you can actually reach read as a choice of how much
   time you have today. */
.kn-today__marks { display: flex; gap: 10px; flex: 0 0 auto; margin-left: auto; }

.kn-today__mark {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 68px; padding: 11px 8px 9px;
  border: 2px solid var(--c-border, #e2e2e2);
  border-radius: 14px;
  background: #fff;
  transition: border-color .3s ease, background .3s ease, transform .3s ease;
}
.kn-today__mark b {
  font-family: 'Montserrat Alternates', sans-serif;
  font-size: 1.2rem; font-weight: 800; line-height: 1;
  color: #c9c9c9;
  font-variant-numeric: tabular-nums;
}
.kn-today__mark span {
  font-size: .6rem; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: #c9c9c9;
}

.kn-today__mark.is-on {
  border-color: var(--c-pink, #e5307d);
  background: linear-gradient(180deg, #fff6fa, #ffe9f3);
  transform: translateY(-2px);
}
.kn-today__mark.is-on b,
.kn-today__mark.is-on span { color: var(--c-pink, #e5307d); }

/* ── Activity graph (git-commit-style) ───────────────────────────────────
   Green on purpose, not this page's pink - the whole point is borrowing a
   pattern people already read at a glance from GitHub/Anki, not restating
   it in house colours. See hub.js's paintActivity(). */

.kn-activity {
  padding: 20px 22px;
  border: 1.5px solid var(--c-border, #e2e2e2);
  border-radius: 20px;
  margin-bottom: 20px;
  background: #fff;
}

.kn-activity__signedout { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.kn-activity__text { margin: 0; font-size: .86rem; color: var(--c-muted, #888); flex: 1 1 220px; }

/* .kn-chip on its own is plain text (drill.css) - this adds just enough for
   an icon in front of the label, kept as a modifier rather than changed on
   .kn-chip itself since every other .kn-chip on the site is text-only. Also
   overrides .kn-chip's own grey pill look with the same Discord blurple used
   by .kn-account__login (kana.css) elsewhere on the site, so this doesn't
   read as a plain unbranded button next to the real login pill. */
/* a.kn-chip (drill.css) sets display: inline-block at higher specificity
   (tag+class) than a plain .kn-chip--discord rule can beat - matching that
   here is what actually wins the cascade and puts the icon back beside the
   label instead of stacked above it. */
a.kn-chip--discord {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border-color: #5865f2;
  background: #5865f2;
  color: #fff;
  font-weight: 700;
}
a.kn-chip--discord:hover { border-color: #4752c4; background: #4752c4; color: #fff; }
.kn-chip__icon { width: 15px; height: 15px; flex: none; }

.kn-activity__head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; margin-bottom: 12px;
}
.kn-activity__title { margin: 0; font-size: .86rem; color: var(--c-body, #575757); }
.kn-activity__title b { font-family: 'Montserrat Alternates', sans-serif; color: var(--c-ink, #333); }

.kn-activity__streak {
  margin: 0;
  font-size: .86rem;
  color: var(--c-body, #575757);
}
.kn-activity__streak b {
  font-family: 'Montserrat Alternates', sans-serif;
  color: var(--c-pink, #e5307d);
}
.js-adv-activity-streak-best-wrap { color: var(--c-muted, #888); }
.js-adv-activity-streak-best-wrap b { color: var(--c-muted, #888); }

.kn-activity__legend {
  display: flex; align-items: center; gap: 4px;
  font-size: .68rem; color: var(--c-muted, #888);
}

/* Horizontal scroll on its own, narrower than the card: a year of days is
   wider than a phone screen no matter how small the squares get, and
   scrolling just this strip (not the whole page) keeps the legend and
   total in view while you drag through months. */
.kn-activity__scroll {
  overflow-x: auto;
  padding-bottom: 10px;
  /* Firefox's own thin-scrollbar API - the pseudo-elements below are
     WebKit/Chromium/Edge's equivalent, since there is no single standard
     property that reaches both yet. */
  scrollbar-width: thin;
  scrollbar-color: var(--c-pink-light, #f7b2d0) transparent;
}
.kn-activity__scroll::-webkit-scrollbar { height: 6px; }
.kn-activity__scroll::-webkit-scrollbar-track { background: transparent; }
.kn-activity__scroll::-webkit-scrollbar-thumb {
  background: var(--c-pink-light, #f7b2d0);
  border-radius: 999px;
}
.kn-activity__scroll::-webkit-scrollbar-thumb:hover { background: var(--c-pink, #e5307d); }

.kn-activity__grid {
  display: grid;
  grid-template-rows: repeat(7, 14px);
  grid-auto-flow: column;
  grid-auto-columns: 14px;
  gap: 3px;
  width: max-content;
}

/* Real <button>s now (hub.js's renderActivityGrid()), for the tap-to-see-
   detail behaviour a hover title alone cannot give a phone - which means
   undoing the browser's own button chrome first, the same fix
   button.c-btn needed elsewhere on this site (see styles.css), just for
   this class instead of that one. */
.kn-activity__cell {
  width: 14px;
  height: 14px;
  border: 0;
  padding: 0;
  border-radius: 3px;
  background: #ebedf0;
  cursor: pointer;
  font: inherit;
  appearance: none;
}
.kn-activity__cell:focus-visible { outline: 2px solid var(--c-pink, #e5307d); outline-offset: 1px; }
.kn-activity__cell--pad { background: transparent; cursor: default; pointer-events: none; }
.kn-activity__cell[data-level="1"] { background: #9be9a8; }
.kn-activity__cell[data-level="2"] { background: #40c463; }
.kn-activity__cell[data-level="3"] { background: #30a14e; }
.kn-activity__cell[data-level="4"] { background: #216e39; }

/* The legend's own five swatches reuse the identical selectors above -
   sized down a touch since they sit inline with text rather than in the
   grid, and are plain <span>s (nothing to tap, just a key). */
.kn-activity__legend .kn-activity__cell { width: 9px; height: 9px; border-radius: 2px; cursor: default; }

/* A floating bubble over the tapped cell - hub.js's showActivityTooltip()
   positions left/top itself, in fixed (viewport) coordinates, so only the
   bubble's own look and its offset from that anchor point live here. */
.kn-activity__tooltip {
  position: fixed;
  z-index: 40;
  transform: translate(-50%, calc(-100% - 10px));
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--c-ink, #333);
  color: #fff;
  font-size: .74rem;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 6px 16px rgba(0, 0, 0, .18);
  /* hub.js's own compensation for the bubble having been pulled back from
     the screen edge: the bubble's CENTRE and the CELL it is about stop
     being the same horizontal point exactly when that happens, and the
     arrow below needs to track the cell, not the bubble, or it ends up
     aiming at whatever happens to be next to the cell instead of the cell
     itself. Defaults to 50% so a tooltip that was never clamped (the
     common case) still centres the arrow with no JS involved. */
  --arrow-left: 50%;
}
/* The point of the bubble, literally - without it, it just looks like a
   pill floating near the cell rather than one that belongs to it. */
.kn-activity__tooltip::after {
  content: '';
  position: absolute;
  left: var(--arrow-left);
  top: 100%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--c-ink, #333);
}

/* hub.js flips to this when there is no room above the cell (the grid's
   top row) - sits below the anchor point instead, arrow pointing up at it
   rather than down. */
.kn-activity__tooltip--below { transform: translate(-50%, 10px); }
.kn-activity__tooltip--below::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--c-ink, #333);
}

@media (max-width: 560px) {
  .kn-activity { padding: 16px 16px; }
}

/* ── Track cards ───────────────────────────────────────────────────────── */

.kn-tracks { display: grid; gap: 14px; }

.kn-track {
  position: relative;
  overflow: hidden;
  display: block; width: 100%; text-align: left;
  padding: 22px 24px 20px;
  border: 2px solid var(--c-border, #e2e2e2);
  border-radius: 20px;
  background: #fff;
  font: inherit; cursor: pointer;
  transition: border-color .2s ease, transform .2s ease, box-shadow .2s ease;
}

/* The kanji is a watermark, not a label: big, pale, bleeding off the corner,
   the way the character art sits behind a charavote row. */
.kn-track__kanji {
  position: absolute;
  right: -6px; bottom: -18px;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 5.2rem;
  font-weight: 700;
  line-height: 1;
  color: var(--c-pink, #e5307d);
  opacity: .09;
  pointer-events: none;
  user-select: none;
}

.kn-track__name {
  display: block; font-family: 'Montserrat Alternates', sans-serif;
  font-weight: 800; color: #2b2b2b; font-size: 1.15rem; line-height: 1.2;
}
.kn-track__desc {
  display: block; font-size: .87rem; color: var(--c-body, #575757);
  margin-top: 4px; max-width: 42ch;
}
.kn-track__meta {
  display: inline-block; margin-top: 10px;
  font-size: .68rem; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: var(--c-muted, #888);
}

/* The one track that works gets the brand colour, a lift on hover and an
   arrow. The rest are visibly waiting rather than merely faded. */
.kn-track:not(:disabled) {
  border-color: var(--c-pink-light, #f7b2d0);
  background: linear-gradient(180deg, #fff, #fff9fc);
}
.kn-track:not(:disabled)::after {
  content: '\2192';
  position: absolute;
  right: 22px; top: 50%;
  transform: translateY(-50%);
  font-size: 1.5rem;
  color: var(--c-pink, #e5307d);
  transition: transform .2s ease;
}
.kn-track:not(:disabled):hover {
  border-color: var(--c-pink, #e5307d);
  transform: translateY(-3px);
  box-shadow: 0 14px 32px rgba(229, 48, 125, .16);
}
.kn-track:not(:disabled):hover::after { transform: translateY(-50%) translateX(4px); }

.kn-track:disabled {
  cursor: default;
  background: #fcfcfd;
  border-style: dashed;
}
.kn-track:disabled .kn-track__name { color: #8f8f8f; }
.kn-track:disabled .kn-track__kanji { color: #8f8f8f; opacity: .07; }
.kn-track:disabled .kn-track__meta {
  padding: 3px 10px; border-radius: 999px;
  background: #eef0f2; color: #8f8f8f;
}

/* A real control, not a naked word.
   `display: flex` rather than inline-flex on purpose: the section around it
   inherits the page's centre alignment, and an inline box would be centred by
   it. A flex box with a fitted width is out of that inline flow, so the margin
   is what places it - and no other element's alignment has to be overridden to
   make room for this one. */
.kn-backtrack {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: 7px;
  /* Bottom margin matters here specifically because whatever follows is
     usually a .kn-lesson-section-title, and THAT rule zeroes its own
     top margin on the first title of a screen (n5-lessons.js always
     appends backToLesson()/backToMap() first, so that title is always
     "first of type") - without this, the two zero margins stack into no
     gap at all between the back button and the heading under it. */
  margin: 16px auto 16px 0;
  padding: 8px 16px 8px 12px;
  border: 1.5px solid var(--c-border, #e2e2e2);
  border-radius: 999px;
  background: #fff;
  font: inherit;
  font-size: .82rem;
  font-weight: 700;
  color: var(--c-body, #575757);
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}

.kn-backtrack__arrow {
  font-size: 1rem;
  line-height: 1;
  color: var(--c-pink, #e5307d);
  transition: transform .15s ease;
}

.kn-backtrack:hover {
  border-color: var(--c-pink-light, #f7b2d0);
  background: var(--c-wash, #fff7fb);
  color: var(--c-pink, #e5307d);
}
.kn-backtrack:hover .kn-backtrack__arrow { transform: translateX(-3px); }

@media (min-width: 720px) {
  .kn-tracks { grid-template-columns: 1fr 1fr; }
  .kn-tracks > :first-child { grid-column: 1 / -1; }

  /* Jalur Kanji hidden (gate.js, for a visitor without that track) leaves
     Kosakata Inti alone in what would have been a two-tile row - stretched
     full width instead of sitting half-width on its own. [hidden] rather
     than a class: gate.js sets the attribute directly, nothing new to keep
     in sync between the two files. */
  .kn-tracks:has(.js-adv-tile-kanji[hidden]) .js-adv-tile-vocabulary {
    grid-column: 1 / -1;
  }
}

@media (max-width: 560px) {
  .kn-today { gap: 16px; padding: 18px; }
  .kn-today__time b { font-size: 2.1rem; }
  .kn-today__note { max-width: none; }
  .kn-today__marks { width: 100%; margin-left: 0; }
  .kn-today__mark { flex: 1; min-width: 0; }
  .kn-track { padding: 18px 20px; }
  .kn-track__kanji { font-size: 4rem; }
  .kn-track:not(:disabled)::after { display: none; }
}

/* ── Flip flow: reveal, then grade ─────────────────────────────────────── */

.kn-reveal {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  max-width: 560px;
  margin: 14px auto 0;
}

.kn-reveal__btn {
  font: inherit; font-weight: 700;
  border: 2px solid var(--c-pink, #e5307d);
  background: #fff;
  color: var(--c-pink, #e5307d);
  border-radius: 999px;
  padding: 13px 30px;
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.kn-reveal__btn:hover { background: var(--c-pink, #e5307d); color: #fff; }
.kn-reveal__hint { font-size: .76rem; color: var(--c-muted, #888); }

/* A component on its own is a shape, not a word: it gets more room than a
   kanji card's glyph so the strokes are actually readable. */
.kn-card__radical {
  font-size: 4.4rem;
  line-height: 1.1;
}

.kn-card__parts {
  margin-top: 8px;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 1.05rem;
  color: var(--c-muted, #888);
  letter-spacing: .08em;
}

/* Hajimari's word illustration, on the answer side only - see the front vs.
   back split in n5.js's build_hajimari_vocab-fed modes. Capped rather than
   left at the source image's own size, which varies illustration to
   illustration and would otherwise resize the card under it. */
.kn-card__image {
  display: block;
  max-width: 140px;
  max-height: 140px;
  margin: 0 auto 10px;
  border-radius: 8px;
}

/* A mnemonic's optional hint, collapsed - see n5.js mnemonicBlock(). Closed
   by default for the same reason Kosakata Inti's Pitch Accent Notes are:
   the mnemonic is the point, the hint is a fallback for when it doesn't
   land, and showing both open by default buries the one that matters. */
.kn-back__hint {
  margin-top: 6px;
  font-size: .82rem;
  color: var(--c-muted, #888);
}
.kn-back__hint summary {
  cursor: pointer;
  color: var(--c-pink, #e46a8b);
}
.kn-back__hint p {
  margin: 6px 0 0;
  line-height: 1.5;
}

.kn-back__note {
  margin: 8px 0 0;
  font-size: .84rem;
  color: var(--c-body, #575757);
  line-height: 1.5;
}

/* Says out loud which words are ours rather than sourced - the same honesty
   the machine-translation notice carries elsewhere. */
.kn-back__src {
  margin: 6px 0 0;
  font-size: .74rem;
  font-style: italic;
  color: var(--c-muted, #888);
}

@media (max-width: 560px) {
  .kn-card__radical { font-size: 3.4rem; }
  .kn-reveal__hint { display: none; }
  .kn-reveal__btn { width: 100%; }
}

/* A start that failed used to look identical to a start that did nothing.
   This is the difference. */
.kn-setup__hint.is-error {
  color: #b02a37;
  font-weight: 700;
}

/* ------------------------------------------------- furigana & kalimat */

/* Real <ruby>, not kana faked with absolute positioning: it stays selectable,
   it reflows on a narrow screen instead of overlapping the line above, and a
   screen reader reads the word rather than the word and its gloss interleaved. */
ruby { ruby-align: center; }

ruby rt {
  font-size: .5em;
  font-weight: 400;
  color: var(--c-muted, #888);
  /* Chrome leaves ruby text sitting right on top of the kanji. */
  transform: translateY(2px);
}

/* The word on the front of a meaning card, with its reading over it. Ruby adds
   a line of its own above the text, so the line-height has to make room or the
   kana clips into whatever sits above. */
.kn-card__kana--ruby { line-height: 1.9; }

/* The sentence the word lives in. Smaller than the word and lighter, so the eye
   still lands on what is being asked first and reads the sentence second. */
.kn-sentence {
  margin-top: 14px;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 1rem;
  line-height: 2;
  color: var(--c-body, #575757);
}

/* Answer side: the word again, this time readable. */
.kn-back__word {
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 1.5rem;
  line-height: 1.9;
  color: var(--c-ink, #333);
}

.kn-back__jp {
  font-family: 'Noto Sans JP', sans-serif;
  font-size: .98rem;
  line-height: 2;
  color: var(--c-body, #575757);
}

.kn-back__sentence {
  margin: 4px 0 0;
  font-size: .82rem;
  line-height: 1.55;
  color: var(--c-muted, #888);
}

/* A line of Japanese with its play button beside it. */
.kn-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 8px;
}

/* Hearing it is part of knowing it. Small and quiet - this is an aid next to
   the answer, not a control competing with Again/Easy. */
.kn-speak {
  flex: none;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--c-line, #f0d8e0);
  border-radius: 50%;
  background: #fff;
  color: var(--c-pink, #e46a8b);
  font-size: 9px;
  line-height: 1;
  cursor: pointer;
  transition: background .15s, color .15s;
}

.kn-speak:hover { background: var(--c-pink, #e46a8b); color: #fff; }

/* ---------------------------------------------------------- Anki's counts */

/* New / learning / review, the row Anki puts under every card. Three separate
   numbers because they mean different things: new cards make tomorrow bigger,
   review cards make it smaller. */
.kn-counts {
  display: flex;
  justify-content: center;
  gap: 18px;
  margin-top: 10px;
  font-size: 13px;
  color: var(--c-muted, #888);
}

.kn-counts__item b { margin-right: 4px; font-size: 15px; }

/* Anki's own three colours, which a lot of people already read without
   thinking: blue is new, red is still being learned, green is coming back. */
.kn-counts__item--new b { color: #4a8fd4; }
.kn-counts__item--learn b { color: #d4574a; }
.kn-counts__item--review b { color: #4aa564; }

/* --------------------------------------------------------- track credit */

/* Whose approach a track follows. Quiet by design: it belongs at the bottom of
   the setup panel, not competing with the material. */
.kn-setup__credit {
  margin: 14px 0 0;
  font-size: 12px;
  font-style: italic;
  line-height: 1.5;
  color: var(--c-muted, #888);
}

/* ----------------------------------------------------- advanced-access gate */

/* Deliberately just this - no heading, no explanation, no login link. See
   gate.js's header for why: the gate is not something this page advertises
   to a visitor who lands here without access. */
.kn-gate-toast {
  display: inline-block;
  margin: 24px 0;
  padding: 10px 18px;
  border-radius: 999px;
  background: var(--c-pink-soft, #fdeef2);
  color: var(--c-pink, #e46a8b);
  font-size: .88rem;
  font-weight: 600;
}

/* Gabung Discord and Keluar live inside the burger menu on this page
   (gate.js), not the header row - see that file for why. `.c-menu__link`
   (styles.css) is written for an <a>; reused on Keluar's <button> it needs
   its own border/background/padding reset back to plain text first, or the
   button chrome shows through underneath the link styling. */
.c-menu__link--action {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  font: inherit;
  cursor: pointer;
}

@media (max-width: 560px) {
  .kn-counts { gap: 12px; font-size: 12px; }
  .kn-counts__item b { font-size: 14px; }
  .kn-sentence { font-size: .92rem; line-height: 1.95; }
  .kn-back__word { font-size: 1.3rem; }
  .kn-back__jp { font-size: .92rem; }
}

/* ══════════════════════ N5 lessons (Minna no Nihongo 1) ══════════════════
   n5-lessons.js's own screens: the lesson map, one lesson's Materi/Latihan
   overview, the reading-then-quiz flow, the two SRS decks, and the recap.
   Deliberately its own class prefix (.kn-lesson-*, .kn-step*, .kn-lq-*)
   rather than reusing .kn-tracks/.kn-track - those carry hub-specific rules
   (the 720px two-column grid, the first-child-spans-both rule for a hidden
   Jalur Kanji tile) that have nothing to do with 25 lesson nodes or a
   handful of step rows, and reusing the class here would have inherited
   them by accident. Buttons/cards below borrow that same visual language
   (border, radius, hover lift, :disabled treatment) on purpose - it is the
   site's own vocabulary for "a thing you tap to go somewhere", just not
   literally the same selector. */

.kn-lesson-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px;
}

.kn-lesson-node {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  text-align: left;
  padding: 14px 14px 12px;
  border: 2px solid var(--c-border, #e2e2e2);
  border-radius: 16px;
  background: #fff;
  font: inherit;
  cursor: pointer;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.kn-lesson-node:not(:disabled) { border-color: var(--c-pink-light, #f7b2d0); }
.kn-lesson-node:not(:disabled):hover {
  border-color: var(--c-pink, #e5307d);
  transform: translateY(-2px);
  box-shadow: 0 10px 22px rgba(229, 48, 125, .14);
}
.kn-lesson-node:disabled {
  cursor: default;
  background: #fcfcfd;
  border-style: dashed;
  color: #9a9a9a;
}
.kn-lesson-node__num {
  font-family: 'Montserrat Alternates', sans-serif;
  font-weight: 800;
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--c-pink, #e5307d);
}
.kn-lesson-node:disabled .kn-lesson-node__num { color: #a0a0a0; }
.kn-lesson-node__name { font-weight: 700; font-size: .92rem; color: #2b2b2b; }
.kn-lesson-node:disabled .kn-lesson-node__name { color: #8f8f8f; }
.kn-lesson-node__meta { font-size: .74rem; color: var(--c-muted, #888); }

/* ── One lesson: Materi / Latihan / Rekap step lists ─────────────────── */

.kn-steps { display: grid; gap: 8px; margin: 0 0 20px; }

.kn-step {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 13px 16px;
  border: 2px solid var(--c-border, #e2e2e2);
  border-radius: 14px;
  background: #fff;
  font: inherit;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.kn-step:not(:disabled):hover { border-color: var(--c-pink-light, #f7b2d0); }
.kn-step:disabled { cursor: default; background: var(--c-wash, #fff7fb); color: #9a9a9a; }
.kn-step__name { flex: 1; font-weight: 700; font-size: .92rem; color: #2b2b2b; }
.kn-step:disabled .kn-step__name { color: #9a9a9a; }
.kn-step__desc { font-size: .78rem; color: var(--c-muted, #888); }
.kn-step__mark {
  flex: none;
  font-size: .78rem;
  font-weight: 800;
  color: var(--c-pink, #e5307d);
}
.kn-step:disabled .kn-step__mark { color: #b0b0b0; }
.kn-step--done { border-color: #43b581; background: #f2fbf6; }
.kn-step--done .kn-step__mark { color: #2f8a5f; }

.kn-lesson-section-title {
  font-family: 'Montserrat Alternates', sans-serif;
  font-size: 1rem;
  font-weight: 800;
  color: var(--c-pink, #e5307d);
  margin: 22px 0 10px;
}
.kn-lesson-section-title:first-of-type { margin-top: 0; }

/* ── Materi reading content (grammar/conversation/reading) ─────────────── */

/* English/Indonesian toggle (n5-lessons.js's langToggle()) - both languages
   are always in the DOM, [data-lang] on .kn-materi-content just picks which
   one is visible, so switching is instant with no re-render. English is the
   default (kn-lang-id starts hidden here; the [data-lang="id"] rule below
   flips which side is hidden once toggled) since it is the MNN source text,
   not the machine translation - see mtBanner(). */
.kn-lang-toggle {
  display: block;
  margin: 0 0 14px;
  padding: 7px 14px;
  border: 1.5px solid var(--c-border, #e2e2e2);
  border-radius: 999px;
  background: #fff;
  font: inherit;
  font-size: .8rem;
  font-weight: 700;
  color: var(--c-pink, #e5307d);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.kn-lang-toggle:hover { border-color: var(--c-pink-light, #f7b2d0); background: var(--c-wash); }

/* Forcing block back on rather than restoring "whatever it was" is safe
   here specifically because every kn-lang-en/kn-lang-id node is either a
   plain <div> (textNode()) or the <p> mtBanner() itself - both already
   default to block, and neither this file's kn-grammar-pattern__explanation/
   kn-grammar-example__translation/kn-convo-line__id/kn-reading-line__id nor
   p.kn-mt-flag override display, so there is no "whatever it was" to lose. */
.kn-materi-content .kn-lang-id { display: none; }
.kn-materi-content[data-lang='id'] .kn-lang-id { display: block; }
.kn-materi-content[data-lang='id'] .kn-lang-en { display: none; }

.kn-materi-content { margin-bottom: 22px; }

.kn-grammar-image {
  display: block;
  max-width: 100%;
  border-radius: 12px;
  margin: 10px 0 0;
  border: 1px solid var(--c-border, #e2e2e2);
}

.kn-convo-line {
  padding: 10px 0;
  border-bottom: 1px solid var(--c-border, #eee);
}
.kn-convo-line:last-child { border-bottom: 0; }
.kn-convo-line__speaker {
  font-size: .7rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--c-pink, #e5307d);
  margin-bottom: 2px;
}
.kn-convo-line__jp { font-family: 'Noto Sans JP', sans-serif; font-size: 1rem; color: #2b2b2b; }
.kn-convo-line__id { font-size: .85rem; color: var(--c-muted, #888); margin-top: 2px; }

.kn-reading-section { margin-bottom: 18px; }
.kn-reading-section__title {
  font-weight: 800;
  font-size: .92rem;
  color: #2b2b2b;
  margin-bottom: 8px;
}
.kn-reading-line { padding: 6px 0; }
.kn-reading-line__label {
  font-size: .68rem;
  font-weight: 800;
  color: var(--c-muted, #888);
}
.kn-reading-line__jp { font-family: 'Noto Sans JP', sans-serif; font-size: .98rem; color: #2b2b2b; }
.kn-reading-line__id { font-size: .85rem; color: var(--c-muted, #888); margin-top: 2px; }

/* ── A materi/listening quiz round: prompt, choices, next ──────────────── */

.kn-lq-progress { font-size: .78rem; color: var(--c-muted, #888); margin: 0 0 8px; }
.kn-lq-prompt {
  font-size: 1.15rem;
  font-weight: 700;
  color: #2b2b2b;
  line-height: 1.5;
  margin-bottom: 4px;
}
.kn-lq-choices { margin-top: 14px; }
.kn-lq-reveal {
  margin-top: 16px;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--c-wash, #fff7fb);
}
.kn-lq-script { font-family: 'Noto Sans JP', sans-serif; font-size: .95rem; color: #2b2b2b; margin: 0 0 4px; }
.kn-lq-script--id { font-family: inherit; font-size: .85rem; color: var(--c-muted, #888); }
.kn-lq-next { margin-top: 16px; }

@media (max-width: 560px) {
  /* Fixed 2 columns rather than auto-fill's own minmax(100px, 1fr): at
     phone width that packed 3 cramped columns instead of a clean 2, since
     auto-fill fits as many tracks as the container allows rather than a
     predictable count. 25 lesson nodes read better as a steady 2-wide list
     than a shifting column count that depends on exact viewport width. */
  .kn-lesson-grid { grid-template-columns: repeat(2, 1fr); }
  .kn-step { padding: 12px 14px; }
}

.kn-recap__daily {
  margin: -6px 0 16px;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(67, 181, 129, .12);
  color: #2f9e68;
  font-weight: 700;
  font-size: .88rem;
}

/* ── Daily "keep going?" confirm ───────────────────────────────────────────
 * A short two-button popup (n5.js's openDailyPause()), deliberately its
 * own small component rather than a reuse of .kn-tutorial's classes below
 * - that component's mobile bottom sheet, drag handle and its own
 * scrollbar styling all exist for a long, scrollable explanation with a
 * demo card, and this is two sentences that never scroll; reusing its
 * class names would also risk a querySelector()-grabs-the-wrong-element
 * collision the moment both are ever in the DOM together, the exact bug
 * this file's own tutorial section has hit once already. */
.kn-confirm {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.kn-confirm__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(28, 20, 30, .45);
  backdrop-filter: blur(2px);
}

.kn-confirm__panel {
  position: relative;
  width: 100%;
  max-width: 380px;
  background: #fff;
  border-radius: 24px;
  padding: 28px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .25);
  opacity: 0;
  transform: translateY(10px) scale(.95);
  transition: opacity .22s ease, transform .22s ease;
}

.kn-confirm.is-open .kn-confirm__panel {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.kn-confirm__title {
  margin: 0 0 10px;
  font-family: 'Montserrat Alternates', sans-serif;
  font-weight: 800;
  font-size: 1.2rem;
  color: #1c1c1c;
}

.kn-confirm__body {
  margin: 0 0 22px;
  color: var(--c-body);
  font-size: .92rem;
  line-height: 1.6;
}

.kn-confirm__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}

@media (prefers-reduced-motion: reduce) {
  .kn-confirm__panel { transition: none; }
}

@media (max-width: 560px) {
  .kn-confirm__actions { flex-direction: column-reverse; align-items: stretch; }
}

/* ── Tutorial popup ────────────────────────────────────────────────────────
 * One .js-n5-tutorial per mode, content swapped in by n5.js's
 * openTutorial() - see study.html's own comment on this section for the
 * "why" behind the inert example card vs. the live Again/Good pair.
 * The site's first true overlay/backdrop component - nothing existing to
 * stay consistent with beyond the card language .kn-recap already
 * established (pastel wash, pill eyebrow, rounded panel). */

/* Toggled on <html> by n5.js's openTutorial()/closeTutorial() - stops the
   page underneath from scrolling while the popup is open, so a drag/scroll
   gesture on the popup itself can't also move the page behind it. */
html.kn-scroll-lock {
  overflow: hidden;
}

.kn-tutorial {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.kn-tutorial__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(28, 20, 30, .45);
  backdrop-filter: blur(2px);
}

/* Fixed header (X) and footer (actions) with ONE scrolling region between
   them (.kn-tutorial__scroll below) - the panel itself no longer scrolls.
   Previously overflow-y lived directly on this element, which meant the
   scrollbar rendered flush with the panel's own rounded corners/shadow and
   read as if it belonged to the page behind it rather than to the popup;
   a dedicated inner scroll element keeps the scrollbar visually inside the
   card. flex column + min-height:0 on the scroll child is what lets that
   child actually shrink and scroll instead of stretching the panel. */
.kn-tutorial__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 620px;
  max-height: calc(100dvh - 40px);
  background: #fff;
  border-radius: 24px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .25);
  opacity: 0;
  transform: translateY(10px) scale(.95);
  transition: opacity .22s ease, transform .22s ease;
}

.kn-tutorial.is-open .kn-tutorial__panel {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.kn-tutorial__handle {
  display: none; /* shown only in the mobile bottom-sheet layout below */
}

.kn-tutorial__x {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: var(--c-wash);
  color: var(--c-muted);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.kn-tutorial__x:hover {
  background: var(--c-pink-light);
  color: #fff;
}

.kn-tutorial__scroll {
  overflow-y: auto;
  min-height: 0;
  padding: 44px 28px 4px;
  /* Firefox's own two-value scrollbar styling - thin track, pink thumb
     matching the rest of this popup's palette, rather than the OS default
     grey bar. The ::-webkit- rules below give Chrome/Safari/Edge the same
     look; there is no single property that covers both. */
  scrollbar-width: thin;
  scrollbar-color: var(--c-pink-light) transparent;
}

.kn-tutorial__scroll::-webkit-scrollbar {
  width: 8px;
}

.kn-tutorial__scroll::-webkit-scrollbar-track {
  background: transparent;
}

.kn-tutorial__scroll::-webkit-scrollbar-thumb {
  background-color: var(--c-pink-light);
  border-radius: 999px;
  /* A transparent border padded by background-clip is the only way to put
     visual breathing room around a scrollbar thumb - margin/padding do not
     apply to these pseudo-elements at all. */
  border: 2px solid transparent;
  background-clip: padding-box;
}

.kn-tutorial__scroll::-webkit-scrollbar-thumb:hover {
  background-color: var(--c-pink);
}

.kn-tutorial__title {
  margin: 0 0 10px;
  font-family: 'Montserrat Alternates', sans-serif;
  font-weight: 800;
  font-size: 1.3rem;
  color: #1c1c1c;
}

.kn-tutorial__step-label {
  display: inline-block;
  margin: 0 0 8px;
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--c-pink);
}

/* min-height reserves room for the longest step's text up front - without
   it, jumping from a step with a long paragraph to one with a short one
   (e.g. clicking Again/Good, which jumps straight from step 2's longer
   "Klik Lihat jawaban..." text to step 3's short "Sekarang coba klik
   Again atau Good...") visibly shrank the whole popup, since nothing else
   in the panel was tall enough to keep its height steady across that
   swap. */
.kn-tutorial__body {
  margin: 0 0 18px;
  min-height: 4.8em;
  color: var(--c-body);
  font-size: .92rem;
  line-height: 1.6;
}

.kn-tutorial__demo {
  background: var(--c-wash);
  border: 1px solid var(--c-border);
  border-radius: 16px;
  padding: 16px;
  margin-bottom: 18px;
}

.kn-tutorial__demo-label {
  margin: 0 0 10px;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--c-muted);
}

/* Not clickable, not focusable (study.html has no interactive attributes
   on any of these) - a still preview of what the real card shows before
   and after answering, not a working flip card. Front and back are two
   separate boxes shown at once (not an actual flip toggle) precisely so
   both states are visible without needing any interaction to see the
   second one. */
.kn-tutorial__front {
  background: #fff;
  border: 1.5px solid var(--c-pink-light);
  border-radius: 18px;
  padding: 22px 16px;
  text-align: center;
  margin-bottom: 14px;
}

.kn-tutorial__front-char {
  margin: 0;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 1.9rem;
  font-weight: 700;
  color: #1c1c1c;
}

.kn-tutorial__front-sentence {
  margin: 10px 0 0;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: .92rem;
  color: var(--c-body);
}

/* Inert previews of the real "Lihat jawaban"/"Lanjut" buttons (same
   .kn-reveal__btn look, reused rather than copied) - scoped to these two
   specific elements rather than overriding .kn-reveal__btn itself, which
   the real quiz's own working buttons also use. pointer-events:none is
   what actually stops the hover fill (.kn-reveal__btn:hover above) from
   firing on them; aria-disabled/tabindex="-1" in study.html keep them out
   of the tab order and off assistive tech's radar on top of that. */
.js-n5-tutorial-reveal,
.js-n5-tutorial-next {
  cursor: default;
  pointer-events: none;
}

/* Reuses the real answer box's own look (drill.css's .kn-answer__input) so
   the demo reads as "the same input", not a lookalike - disabled in
   study.html since this is a still preview, not something to actually type
   into. */
.kn-tutorial__input-demo {
  margin-bottom: 6px;
}

.kn-tutorial__input-demo .kn-answer__input {
  opacity: .8;
  cursor: default;
}

/* Bacaan's step 2 "graded" preview - styled like a correct-answer toast
   rather than plain text so it visually matches what the real auto-check
   actually looks like. */
.kn-tutorial__feedback {
  margin: 0 0 10px;
  padding: 8px 14px;
  border-radius: 10px;
  background: rgba(67, 181, 129, .12);
  color: #2f9e68;
  font-size: .85rem;
  font-weight: 700;
  text-align: center;
}

.kn-tutorial__back {
  background: #fff;
  border-radius: 14px;
  padding: 16px;
  text-align: center;
}

.kn-tutorial__back-title {
  margin: 0 0 10px;
  font-weight: 800;
  font-size: 1rem;
  color: #1c1c1c;
}

.kn-tutorial__back-word,
.kn-tutorial__back-sentence {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 0 0 10px;
  font-family: 'Noto Sans JP', sans-serif;
}

.kn-tutorial__back-word { font-size: 1.3rem; font-weight: 700; line-height: 1.9; }
.kn-tutorial__back-sentence { font-size: .92rem; line-height: 2; }
/* No rt override here - the global `ruby rt` rule above (muted grey,
   .5em, nudged down 2px for Chrome) is the real card's own furigana
   style, not a look invented for this popup. */

.kn-tutorial__back-translation {
  margin: 0 0 4px;
  font-size: .85rem;
  color: var(--c-body);
}

.kn-tutorial__back-note {
  margin: 0 0 4px;
  font-size: .8rem;
  color: var(--c-muted);
}

.kn-tutorial__back-mnemonic {
  margin: 10px 0 0;
  padding-top: 10px;
  border-top: 1px dashed var(--c-border);
  font-size: .85rem;
  line-height: 1.55;
  color: var(--c-body);
  text-align: left;
}

/* Visible only for modes whose real card has one (Bacaan/Arti - see
   TUTORIALS.inti/.intiArti's hasWordAudio/hasSentenceAudio in n5.js).
   aria-disabled + tabindex="-1" rather than the disabled attribute: this
   is styled to look exactly like the real audio row's play button, and
   disabled would additionally grey it out, which reads as "broken" rather
   than "a preview". */
.kn-tutorial__play {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--c-pink-light);
  background: var(--c-wash);
  color: var(--c-pink);
  font-size: .6rem;
  line-height: 1;
  cursor: default;
}

.kn-tutorial__grade {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--c-border);
  text-align: center;
}

/* .kn-grade's own max-width/margin (n5.css above) already centers this at
   a sane width; only the extra top margin needs cancelling since it isn't
   the first thing in this block. position:relative is what actually
   anchors .kn-tutorial__tooltip (now a DOM child of this row, not of
   .kn-tutorial__grade above - see study.html) directly above the buttons
   themselves, not above the dashed divider/padding that used to sit
   between the tooltip and the row it's pointing at. */
.kn-tutorial__grade-btns {
  position: relative;
  margin-top: 0;
}

/* Plain text with a highlighter mark behind it, not a button - the mark
   itself (light pink, honeyworks.jp's own nav-link hover effect,
   .c-menu__mainList-link/::before in their honeyworks.common.css, pulled
   directly for the colour/height) is painted as a background on the
   <span> around the text (study.html), not as a ::before on this
   paragraph. A ::before on the paragraph draws ONE rectangle spanning the
   whole block's width, which for wrapped two-line text (this hint
   regularly wraps) looked like a straight bar sitting under both lines
   rather than following the actual shape of the text; a background on the
   inline span, combined with box-decoration-break: clone, gets painted
   PER LINE FRAGMENT instead, at each fragment's own width - so on a wrap,
   each line gets its own mark sized to what that line actually says. The
   linear-gradient (solid colour at both stops) is just a background image
   whose extent background-size/-position can animate; a plain
   background-color can't be given a size at all. Loops continuously
   rather than waiting for a :hover like honeyworks' own version, same
   reasoning as before this became a per-line mark: this needs to catch
   the eye on its own, including on mobile where nothing can hover it. */
.kn-tutorial__grade-hint {
  margin: 10px 0 0;
  font-size: .8rem;
  font-weight: 800;
  line-height: 1.3;
  color: #1c1c1c;
}

.kn-tutorial__grade-hint span {
  background-image: linear-gradient(var(--c-pink-light), var(--c-pink-light));
  background-repeat: no-repeat;
  background-position: 0 82%;
  background-size: 0 .6em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  animation: kn-tutorial-hint-sweep 2.2s cubic-bezier(0.25, 1, 0.5, 1) infinite;
}

/* background-position's X percentage anchors whichever edge stays fixed
   while background-size's width grows/shrinks (0% keeps the LEFT edge
   fixed as width increases, 100% keeps the RIGHT edge fixed as it
   decreases) - so this grows in from the left, holds, then shrinks out to
   the right, same motion the transform-origin-flip version had, just
   driven by background geometry so it fragments correctly per line. */
@keyframes kn-tutorial-hint-sweep {
  0%   { background-position: 0 82%;   background-size: 0 .6em; }
  40%  { background-position: 0 82%;   background-size: 100% .6em; }
  60%  { background-position: 100% 82%; background-size: 100% .6em; }
  100% { background-position: 100% 82%; background-size: 0 .6em; }
}

@media (prefers-reduced-motion: reduce) {
  .kn-tutorial__grade-hint span {
    animation: none;
    background-size: 100% .6em;
  }
}

/* Floats ABOVE whichever button (Again/Good) triggered it, pointing down at
   that button with a small triangle - not centred over the pair. n5.js's
   showTip() sets --tip-x to that button's own centre X (relative to
   .kn-tutorial__grade, this element's positioning parent), so this only
   needs to read that variable; it never has to know which button it was.
   Absolutely positioned/out of flow on purpose - it floats OVER the demo
   content above it rather than pushing anything else in the panel around,
   same "floating, not pushing layout" call as the very first version of
   this tooltip, just now anchored above instead of below. */
.kn-tutorial__tooltip {
  position: absolute;
  bottom: 100%;
  left: var(--tip-x, 50%);
  transform: translateX(-50%);
  margin-bottom: 10px;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: max-content;
  max-width: min(280px, calc(100vw - 96px));
  z-index: 10;
  padding: 10px 10px 10px 14px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: 12px;
  box-shadow: 0 10px 24px rgba(0, 0, 0, .15);
  text-align: left;
}

/* The pointer - centred on the tooltip's own box, which is itself already
   centred on --tip-x, so this lands on the triggering button without
   needing its own copy of that position. */
.kn-tutorial__tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  /* --tip-arrow-x (n5.js's showTip()) is where the triggering button
     actually falls within this box, not always its centre - once the box
     itself gets clamped to stay on-screen, centring the pointer at a flat
     50% would point it at empty air instead of the button it's meant to
     be attached to. */
  left: var(--tip-arrow-x, 50%);
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border: 7px solid transparent;
  border-top-color: #fff;
  filter: drop-shadow(0 2px 1px rgba(0, 0, 0, .06));
}

.kn-tutorial__tooltip-text {
  margin: 0;
  font-size: .84rem;
  line-height: 1.5;
  color: #1c1c1c;
}

.kn-tutorial__tooltip-close {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 50%;
  background: var(--c-wash);
  color: var(--c-muted);
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.kn-tutorial__tooltip-close:hover {
  background: var(--c-pink-light);
  color: #fff;
}

.kn-tutorial__checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
  padding-bottom: 14px;
  font-size: .86rem;
  color: var(--c-body);
  cursor: pointer;
}

.kn-tutorial__checkbox input {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  accent-color: var(--c-pink);
  cursor: pointer;
}

/* Pinned outside .kn-tutorial__scroll (see study.html) so Tutup/Mulai stay
   reachable without scrolling to the bottom of a long tutorial - a
   top border is what visually separates it from the scrolling content
   above rather than a shadow, to keep it flat/simple against the panel. */
.kn-tutorial__actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  padding: 14px 28px 24px;
  border-top: 1px solid var(--c-border);
}

@media (prefers-reduced-motion: reduce) {
  .kn-tutorial__panel {
    transition: none;
  }
}

/* Bottom sheet below 560px (same breakpoint the rest of n5.css already
 * treats as "phone") rather than a small centered card - a 460px dialog has
 * nowhere near that much room on a phone, and a bottom sheet that slides up
 * is the platform-native pattern for "read this before you continue" there
 * anyway. Stops at 90dvh rather than the full viewport height so the sheet
 * visibly doesn't reach the very top - a sliver of the page (and status
 * bar) stays visible above it, which is what actually reads as "a sheet
 * over the page" rather than "a whole new screen".
 *
 * is-open (added by n5.js's openTutorial() a couple of frames after
 * unhiding, removed immediately in closeTutorial(), and dragged live by
 * setupTutorialDrag()'s own inline transform) is what drives the slide:
 * .kn-tutorial__panel starts pushed fully below the viewport and
 * transitions up to rest. Closing doesn't get the same animated treatment
 * in reverse elsewhere in this codebase (see cf23.js's carousel panels for
 * the same call) and doesn't here either - is-open's removal plus this
 * transition still plays it, closeTutorial() just also delays actually
 * hiding the element until that transition has had time to finish
 * (window.setTimeout in n5.js) instead of cutting it off mid-slide. */
@media (max-width: 560px) {
  .kn-tutorial {
    padding: 0;
    align-items: flex-end;
  }
  .kn-tutorial__panel {
    max-width: none;
    width: 100%;
    height: 90dvh;
    max-height: 90dvh;
    border-radius: 20px 20px 0 0;
    /* Slide only - no fade/scale on mobile, that combo read as janky on a
       full-width sheet; the desktop opacity/scale from the base rule above
       is overridden back to a plain visible state here. */
    opacity: 1;
    transform: translateY(100%);
    transition: transform .32s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .kn-tutorial.is-open .kn-tutorial__panel {
    transform: translateY(0);
  }
  .kn-tutorial__handle {
    display: block;
    flex-shrink: 0;
    width: 40px;
    height: 5px;
    margin: 10px auto 0;
    border-radius: 999px;
    background: var(--c-border);
  }
  .kn-tutorial__x {
    top: 12px;
  }
  .kn-tutorial__scroll {
    padding: 20px 22px 4px;
  }
  .kn-tutorial__actions {
    flex-direction: column-reverse;
    align-items: stretch;
    padding: 14px 22px 20px;
  }
}
