/* OneOneTyping v6 — reset, shared chrome (nav / footer), buttons, utilities.
 * Loaded by every page after tokens.css.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: 1.55;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img,
svg,
video {
  display: block;
  max-width: 100%;
}

/* Sprite icons default to the size of the text around them. Without this an
   icon dropped somewhere no rule anticipated (a table cell, a chip) stretches
   to the width of its box — that is how a crown ended up 130px tall in the
   course catalog. Contextual rules like `.btn svg` still win on specificity. */
svg.ic {
  flex: 0 0 auto;
  width: 1em;
  height: 1em;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  color: var(--text-strong);
  font-weight: var(--fw-bold);
  line-height: 1.2;
}

/* Headings stay near-black. Painting them in the accent was the single
   biggest reason the page looked cheap — see the note in tokens.css. */
h2 {
  color: var(--text-strong);
}

p {
  margin: 0;
}

a {
  color: var(--blue-dark);
  text-decoration: none;
}

a:hover {
  color: var(--blue-hover);
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

table {
  border-collapse: collapse;
  width: 100%;
}

:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: 4px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link — first tab stop on every page. */
.skip-link {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 300;
  padding: 10px 16px;
  border-radius: var(--radius-ctl);
  background: var(--blue-deep);
  color: #fff;
  font-weight: var(--fw-semibold);
  transition: top 0.2s var(--ease);
}

.skip-link:focus {
  top: 10px;
  color: #fff;
}

/* ---------------------------------------------------------------- layout */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 24px;
}

.wrap-wide {
  max-width: var(--max-wide);
}

/* ------------------------------------------------------------ top nav bar */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 100;
  height: var(--nav-h);
  background: var(--nav-bg);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  backdrop-filter: saturate(180%) blur(18px);
  border-bottom: 1px solid var(--line-soft);
}

.nav-inner {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  flex: 0 0 auto;
  color: var(--nav-ink);
  font-size: 16px;
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
}

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

.brand-mark {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  object-fit: contain;
}

/* The gold cursor in the mark blinks while you point at it. */
/* Pointing at the logo gives the garlic a little hop. */

.brand:hover .brand-mark { animation: brand-hop 0.36s var(--ease); }

@keyframes brand-hop {
  0%, 100% { transform: translateY(0); }
  40% { transform: translateY(-4px); }
  70% { transform: translateY(1px); }
}

.brand-word {
  display: inline-flex;
  align-items: baseline;
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* The name is set in Quicksand on every page — the marketing pages use the
   system stack for their text, but the wordmark is the brand, not text. */
.brand .brand-word,
.mk .brand .brand-word {
  font-family: 'Quicksand', var(--font-sans, system-ui), sans-serif;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* The gold typing cursor after the name — the same gold as 小一's mouth. */
.brand-caret {
  display: inline-block;
  width: 0.5em;
  height: 0.16em;
  margin-left: 2px;
  background: #f8c64a;
  box-shadow: 0 1px 0 #d9a52e;
  animation: brand-caret 1.1s steps(1, end) infinite;
}

@keyframes brand-caret {
  0%, 55% { opacity: 1; }
  56%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .brand-caret, .brand:hover .brand-mark { animation: none; }
}

/* "OneOne" in ink, "Typing" in the brand blue — the name split the same way
   the mark is: the two ones, then the thing you do with them. */
.brand em {
  color: var(--accent-ink);
  font-style: normal;
}

html[data-theme='dark'] .brand em {
  color: var(--accent-mid);
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: nowrap;
  white-space: nowrap;
}

.nav-links a {
  display: inline-flex;
  align-items: center;
  padding: 7px 11px;
  border-radius: var(--radius-ctl);
  color: var(--text-muted);
  font-size: 13.5px;
  font-weight: var(--fw-semibold);
  transition: color 0.2s, background-color 0.2s;
}

.nav-links a:hover {
  color: var(--text-strong);
  background: rgba(50, 149, 218, 0.08);
}

.nav-links a[aria-current='page'] {
  color: var(--blue-dark);
  background: var(--blue-soft);
}

/* 会员 is the one accented nav item — typing.com's "Get PLUS!" chip. */
.nav-links a.nav-vip {
  margin-left: 4px;
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  background: var(--gold);
  color: var(--gold-ink);
  font-weight: var(--fw-bold);
}

.nav-links a.nav-vip:hover {
  background: var(--gold-dark);
  color: var(--gold-ink);
}

.nav-links a.nav-vip[aria-current='page'] {
  background: var(--gold-dark);
  color: var(--gold-ink);
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

.nav-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  transition: color 0.2s, border-color 0.2s, background-color 0.2s;
}

.nav-icon-btn:hover {
  color: var(--blue-dark);
  border-color: var(--blue);
  background: var(--blue-soft);
}

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

.nav-avatar {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px 4px 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-strong);
  /* It is a <button> now — the account control opens a menu instead of
     navigating straight to the account page. */
  font-family: inherit;
  font-size: 13px;
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: border-color 0.2s, background-color 0.2s;
}

.nav-avatar:hover {
  border-color: var(--blue);
  background: var(--blue-soft);
}

.nav-avatar img,
.nav-avatar .avatar-fallback {
  width: 26px;
  height: 26px;
}

/* The shape belongs to the avatar, not to each place that shows one.
   It used to be set per context, which is how the same avatar ended up round in
   the nav, square on the account page and unsized on the stats page. */
.avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border-radius: 50%;
  overflow: hidden;
  background: var(--blue-deep);
  color: #fff;
  font-size: 12px;
  font-weight: var(--fw-bold);
}

/* A picture fills the disc; the initial stays centred text. */
img.avatar-fallback {
  object-fit: cover;
}

.nav-burger {
  display: none;
}

/* ------------------------------------------------------- nav menus
   Two dropdowns hang off the nav: the language list and the account panel.
   Both share .nav-menu-wrap (positioning) and .nav-menu (the panel), so the
   open/close logic in ui.js can treat them identically. */

.nav-menu-wrap {
  position: relative;
  display: inline-flex;
}

.nav-lang-short {
  font-size: 12px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
}

.nav-avatar-name {
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-caret {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  color: var(--text-dim);
  transition: transform 0.2s var(--ease);
}

.nav-avatar[aria-expanded='true'] .nav-caret {
  transform: rotate(180deg);
}

.nav-avatar[aria-expanded='true'] {
  border-color: var(--blue);
  background: var(--blue-soft);
}

.nav-menu {
  position: absolute;
  top: calc(100% + 9px);
  right: 0;
  z-index: 60;
  min-width: 208px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--bg-elevated);
  box-shadow: 0 14px 38px rgba(16, 24, 40, 0.16), 0 2px 6px rgba(16, 24, 40, 0.06);
}

.nav-menu[hidden] {
  display: none;
}

.nav-menu-account {
  min-width: 274px;
}

.nav-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: var(--fw-medium);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s, color 0.15s;
}

.nav-menu-item:hover {
  background: var(--bg-soft);
  color: var(--text-strong);
}

.nav-menu-item svg {
  width: 17px;
  height: 17px;
  flex: 0 0 auto;
  color: var(--text-dim);
}

.nav-menu-item.is-danger {
  color: var(--wrong-ink);
}

.nav-menu-item.is-danger svg {
  color: currentColor;
}

.nav-menu-item.is-danger:hover {
  background: var(--red-soft);
}

/* Language rows carry a tick on the active one. */
.nav-menu-tick {
  margin-left: auto;
  opacity: 0;
  color: var(--accent) !important;
}

.nav-menu-item[aria-checked='true'] {
  color: var(--text-strong);
  font-weight: var(--fw-semibold);
}

.nav-menu-item[aria-checked='true'] .nav-menu-tick {
  opacity: 1;
}

.nav-menu-sep {
  height: 1px;
  margin: 6px 4px;
  background: var(--line-soft);
}

/* The account panel's header: avatar, name, email. */
.nav-menu-head {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px 11px;
}

.nav-menu-avatar .avatar-fallback {
  width: 42px;
  height: 42px;
  font-size: 17px;
}

.nav-menu-avatar img.avatar-fallback {
  object-fit: cover;
}

.nav-menu-id {
  min-width: 0;
}

.nav-menu-id b {
  display: block;
  color: var(--text-strong);
  font-size: 14px;
  font-weight: var(--fw-bold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-menu-id span {
  display: block;
  margin-top: 2px;
  color: var(--text-dim);
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-menu-bio {
  margin: 0 10px 8px;
  color: var(--text-muted);
  font-size: 12.5px;
  line-height: 1.55;
}

.nav-menu-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  padding: 0 6px 8px;
}

.nav-menu-stats > div {
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--bg-soft);
}

.nav-menu-stats b {
  display: block;
  color: var(--text-strong);
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}

.nav-menu-stats span {
  display: block;
  margin-top: 2px;
  color: var(--text-dim);
  font-size: 11px;
}

.nav-signin {
  color: var(--text);
}


/* ------------------------------------------------------------ member mark
   Sits immediately after a display name, the way a verified check does. Sized
   in `em` so it tracks whatever text it is attached to — it appears beside 13px
   nav names, 20px page headings and 15px table rows. */

.pro-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: -0.14em;
  width: 1.05em;
  height: 1.05em;
  margin-left: 0.28em;
  color: var(--blue-deep);
  flex: 0 0 auto;
}

.pro-badge svg {
  width: 100%;
  height: 100%;
  display: block;
}

[data-theme='dark'] .pro-badge {
  color: #4aa3e8;
}

/* --------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 11px 22px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: var(--blue-deep);
  color: #fff;
  font-size: 15px;
  font-weight: var(--fw-bold);
  line-height: 1.2;
  white-space: nowrap;
  transition: background-color 0.2s, box-shadow 0.2s, color 0.2s,
    border-color 0.2s, transform 0.15s var(--ease);
}

.btn:hover {
  background: var(--blue-hover);
  color: #fff;
  box-shadow: 0 12px 26px -14px rgba(29, 95, 143, 0.7);
}

.btn:active {
  transform: translateY(1px);
}

.btn svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}

.btn-lg {
  padding: 14px 30px;
  font-size: 16.5px;
}

.btn-sm {
  padding: 8px 16px;
  font-size: 13.5px;
}

.btn-gold {
  background: linear-gradient(var(--gold-300), var(--gold-400));
  border-color: var(--gold-500);
  color: var(--gold-ink);
  box-shadow: 0 1px 0 var(--gold-lip), var(--shadow-sm);
}

.btn-gold:hover {
  background: linear-gradient(var(--gold-200), var(--gold-300));
  border-color: var(--gold-500);
  color: var(--gold-ink);
  box-shadow: 0 1px 0 var(--gold-lip), var(--shadow-md);
}

.btn-ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--text-strong);
}

.btn-ghost:hover {
  background: var(--blue-soft);
  border-color: var(--blue);
  color: var(--blue-dark);
  box-shadow: none;
}

.btn-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
  font-weight: var(--fw-semibold);
}

.btn-quiet:hover {
  background: var(--bg-sunken);
  color: var(--text-strong);
  box-shadow: none;
}

.btn[disabled],
.btn[aria-disabled='true'] {
  opacity: 0.5;
  pointer-events: none;
}

/* App-store style stacked button — the hero's primary CTA. */
.btn-store {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding: 12px 26px 12px 22px;
  border: 1px solid transparent;
  border-radius: 22px;
  background: var(--blue-deep);
  color: #fff;
  text-align: left;
  box-shadow: 0 10px 26px -14px rgba(36, 112, 166, 0.75);
  transition: box-shadow 0.25s, background-color 0.2s, transform 0.15s var(--ease);
}

.btn-store:hover {
  background: var(--blue-hover);
  color: #fff;
  box-shadow: 0 16px 34px -17px rgba(29, 95, 143, 0.85);
}

.btn-store:active {
  transform: translateY(1px);
}

.btn-store svg {
  width: 26px;
  height: 26px;
  fill: currentColor;
  flex: 0 0 auto;
}

.btn-store small {
  display: block;
  font-size: 11px;
  font-weight: var(--fw-medium);
  line-height: 1.1;
  opacity: 0.78;
}

.btn-store b {
  display: block;
  font-size: 18px;
  font-weight: var(--fw-bold);
  line-height: 1.15;
}

/* ---------------------------------------------------------- shared bits */

.kicker {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 16px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-dark);
}

.kicker i {
  font-style: normal;
  color: var(--text-dim);
  letter-spacing: 0.06em;
}

.badge-new {
  padding: 2px 8px 3px;
  border-radius: var(--radius-pill);
  background: var(--blue-deep);
  color: #fff;
  font-size: 10.5px;
  letter-spacing: 0.08em;
}

.card {
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-card-lg);
  background: var(--bg-soft);
  padding: 24px 22px;
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s var(--ease);
}

.card:hover {
  border-color: var(--line);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
}

.card h3 {
  margin-bottom: 7px;
  color: var(--text-strong);
  font-size: 16.5px;
  font-weight: var(--fw-bold);
}

.card p {
  color: var(--text-muted);
  font-size: 14.5px;
  line-height: 1.55;
}

/* Pill chip used for statuses and tags. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}

.chip-blue {
  background: var(--blue-soft);
  color: var(--blue-dark);
}

.chip-gold {
  background: var(--gold-soft);
  color: var(--gold-soft-ink);
}

.chip-green {
  background: var(--green-soft);
  color: var(--green-ink);
}

.chip-red {
  background: var(--red-soft);
  color: var(--red);
}

/* Star rating — filled stars are gold, empty ones are a hairline outline. */
.stars {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.stars svg {
  width: 15px;
  height: 15px;
  /* The star symbol paints with currentColor, so colour — not fill — is what
     reaches it. Setting fill here left every rating the colour of the text. */
  color: var(--line);
}

.stars .on svg,
.stars svg.on {
  color: var(--gold);
}

/* Hairline progress bar. */
.bar {
  /* Block even when written as a <span>: inline, the height is ignored and
     the bar vanished (course unit nav, achievement "almost there" list). */
  display: block;
  position: relative;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  overflow: hidden;
}

.bar > i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--green);
  transition: width 0.45s var(--ease);
}

.bar-blue > i {
  background: var(--blue);
}

.bar-gold > i {
  background: var(--gold);
}

/* ---------------------------------------------------------------- footer */

.footer {
  border-top: 1px solid var(--line-soft);
  background: var(--bg-tint);
  padding: 34px 0;
}

.foot-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.foot-inner p {
  color: var(--text-dim);
  font-size: 13px;
}

.foot-links {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
}

.foot-links a {
  color: var(--text-muted);
  font-size: 13.5px;
  font-weight: var(--fw-semibold);
}

.foot-links a:hover {
  color: var(--text-strong);
}

/* ------------------------------------------------------------ utilities */

.stack > * + * {
  margin-top: 16px;
}

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

.dim {
  color: var(--text-dim);
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.num {
  font-variant-numeric: tabular-nums;
}

.center {
  text-align: center;
}

.hidden {
  display: none !important;
}

.row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.row-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.grow {
  flex: 1 1 auto;
  min-width: 0;
}

/* Page entrance used by the marketing pages. */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 980px) {
  .nav-links {
    display: none;
  }

  .nav-burger {
    display: inline-flex;
  }

  .nav.is-open .nav-links {
    position: absolute;
    top: var(--nav-h);
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 10px 16px 16px;
    background: var(--bg-elevated);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-md);
  }

  .nav.is-open .nav-links a {
    padding: 12px 14px;
    font-size: 15px;
  }

  .nav.is-open .nav-links a.nav-vip {
    margin: 6px 0 0;
    justify-content: center;
  }
}

/* The account panel is wider than the language list, so on a phone it is
   anchored to the viewport instead of the button — otherwise it hangs off the
   right edge. */
@media (max-width: 560px) {
  .nav-menu-account {
    position: fixed;
    top: calc(var(--nav-h) + 6px);
    left: 12px;
    right: 12px;
    min-width: 0;
  }

  .nav-menu-lang {
    min-width: 168px;
  }

  .nav-avatar-name {
    display: none;
  }

  .nav-avatar {
    padding: 4px;
  }

  .nav-signup {
    display: none;
  }
}

@media (max-width: 560px) {
  .wrap {
    padding: 0 18px;
  }

  .brand span {
    display: none;
  }

  .btn {
    padding: 10px 18px;
    font-size: 14px;
  }
}

/* --------------------------------------------------- site announcement
   The admin panel can publish a notice and toggle maintenance mode. Both
   render as a bar pinned above everything else, offset so they never sit on
   top of the fixed nav. */

.site-announcement {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 150;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 9px 44px 9px 16px;
  background: var(--blue-deep);
  color: #fff;
  font-size: 13.5px;
  font-weight: var(--fw-semibold);
  text-align: center;
}

.site-announcement.is-maintenance {
  background: var(--gold);
  color: var(--gold-ink);
}

.site-announcement .sa-text {
  min-width: 0;
}

.site-announcement .sa-close {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  color: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}

.site-announcement .sa-close:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* Push the fixed nav and the first block of content down while a bar is up. */
body.has-announcement .nav {
  top: 38px;
}

body.has-announcement {
  padding-top: 38px;
}

/* ------------------------------------------- shared list + FAQ components
   Both of these are used by marketing pages AND app pages, but they used to be
   defined only in home.css — which leaderboard, account and test never load.
   With no width rule, an inline <svg> that carries only a viewBox stretches to
   fill its container, so the tick marks and the +/− markers rendered hundreds
   of pixels wide and pushed the real content off the page.

   They live here, in the layer every page loads. home.css still wins on the
   marketing pages because it is linked after this file. */

.ticks {
  display: grid;
  gap: 14px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ticks li {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: 11px;
  align-items: start;
  color: var(--text-muted);
  font-size: 14.5px;
  line-height: 1.6;
}

.ticks li b {
  color: var(--text-strong);
  font-weight: var(--fw-semibold);
}

.ticks svg {
  width: 20px;
  height: 20px;
  margin-top: 2px;
  color: var(--accent);
  flex: 0 0 auto;
}

.faq-list {
  max-width: 760px;
}

.faq-list details {
  border-top: 1px solid var(--line-soft);
}

.faq-list details:last-child {
  border-bottom: 1px solid var(--line-soft);
}

.faq-list summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 18px 2px;
  cursor: pointer;
  list-style: none;
  color: var(--text-strong);
  font-size: 15.5px;
  font-weight: var(--fw-semibold);
}

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

.faq-list summary svg {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--text-dim);
  transition: transform 0.22s var(--ease), color 0.2s;
}

.faq-list details[open] summary svg {
  transform: rotate(45deg);
  color: var(--accent);
}

.faq-list details p {
  max-width: 66ch;
  margin: 0;
  padding: 0 2px 20px;
  color: var(--text-muted);
  font-size: 14.5px;
  line-height: 1.7;
}

/* Author display rules must never override the HTML hidden state. */
[hidden] { display: none !important; }


/* ============================================================ 小一 v2+
   Pixel sprite (assets/js/xiaoyi.js) + a pixel speech bubble: square
   corners notched out, a 3px frame in his outline blue, a hard pixel drop,
   buttons that press down. One pixel world, from the sprite to the bubble. */

.xy {
  --xy-line: #16386a;
  --xy-drop: rgba(22, 56, 106, 0.16);
  --xy-paper: #ffffff;
  position: relative;
  display: inline-flex;
  align-items: flex-end;
  gap: 14px;
}

[data-theme='dark'] .xy {
  --xy-line: #7fb2ea;
  --xy-drop: rgba(0, 0, 0, 0.45);
  --xy-paper: #1b2433;
}

.xy-body {
  position: relative;
  flex: 0 0 auto;
  width: var(--xy-w);
  height: var(--xy-h);
  touch-action: none;
  user-select: none;
}

.xy-body canvas {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

.xy-shadow {
  position: absolute;
  left: 24%;
  right: 24%;
  bottom: 3%;
  height: calc(var(--xy-px) * 2);
  background: rgba(15, 35, 60, 0.14);
  box-shadow: calc(var(--xy-px) * -2) 0 0 rgba(15, 35, 60, 0.07), calc(var(--xy-px) * 2) 0 0 rgba(15, 35, 60, 0.07);
  z-index: -1;
}

/* Mirrored (a visit on the "wrong" side): only the art flips, not the bubble. */
.xy.is-flip > .xy-body > canvas { transform: scaleX(-1); }

/* The other one dropping by (OOT.xiaoyi.visit): stands beside, runs in and
   out in eight pixel steps. */
.xy-visit {
  position: absolute;
  bottom: 0;
  width: var(--xy-w);
  height: var(--xy-h);
  transition: transform 0.64s steps(8, end);
  pointer-events: none;
}
.xy-visit.is-right { left: 76%; }
.xy-visit.is-left { right: 76%; }
.xy-visit.is-right.is-out, .xy-visit.is-right.is-leaving { transform: translateX(160%); }
.xy-visit.is-left.is-out, .xy-visit.is-left.is-leaving { transform: translateX(-160%); }
.xy-visit .xy-shadow { display: none; }

/* 键帽连射 (the 键盘侠 attack): a tiny keycap with a letter on it. */
.xy-cap {
  position: absolute;
  left: 78%;
  top: 52%;
  z-index: 3;
  display: grid;
  place-items: center;
  width: calc(var(--u) * 6);
  height: calc(var(--u) * 6);
  border: var(--u) solid #28304e;
  background: #eef2fa;
  box-shadow: 0 var(--u) 0 #28304e;
  color: #28304e;
  font: 700 calc(var(--u) * 4) / 1 var(--font-mono, monospace);
  font-style: normal;
  pointer-events: none;
}
.xy.is-flip .xy-cap { left: auto; right: 78%; }

/* The transformation banner: red plate, gold frame, pops in steps. */
.xy-banner {
  position: absolute;
  left: 50%;
  bottom: 104%;
  z-index: 4;
  padding: 3px 10px 4px;
  background: #e23e42;
  box-shadow: 0 -3px 0 #facc46, 0 3px 0 #facc46, -3px 0 0 #facc46, 3px 0 0 #facc46, 0 7px 0 -1px #a82434;
  color: #fff;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.08em;
  white-space: nowrap;
  transform: translateX(-50%);
  pointer-events: none;
  animation: xy-banner 1.5s steps(1, end) both;
}

@keyframes xy-banner {
  0% { transform: translateX(-50%) scale(0.4); opacity: 0; }
  8% { transform: translateX(-50%) scale(1.25); opacity: 1; }
  16% { transform: translateX(-50%) scale(0.95); }
  24%, 80% { transform: translateX(-50%) scale(1); opacity: 1; }
  88% { transform: translateX(-50%) translateY(-4px) scale(1); opacity: 0.6; }
  100% { transform: translateX(-50%) translateY(-8px); opacity: 0; }
}

/* Pixel bits (burst()). */
.xy-bit {
  position: absolute;
  left: 50%;
  top: 22%;
  background: var(--c);
  pointer-events: none;
  z-index: 3;
}

.xy-bit.is-star {
  clip-path: polygon(33% 0, 67% 0, 67% 33%, 100% 33%, 100% 67%, 67% 67%, 67% 100%, 33% 100%, 33% 67%, 0 67%, 0 33%, 33% 33%);
}

.xy-bit.is-heart {
  clip-path: polygon(0 0, 40% 0, 40% 20%, 60% 20%, 60% 0, 100% 0, 100% 60%, 80% 60%, 80% 80%, 60% 80%, 60% 100%, 40% 100%, 40% 80%, 20% 80%, 20% 60%, 0 60%);
}

.xy-say {
  position: relative;
  max-width: 300px;
  padding: 10px 14px 11px;
  border: 0;
  border-radius: 0;
  background: var(--xy-paper);
  /* A notched square frame + a hard drop one step below. */
  box-shadow:
    0 -3px 0 0 var(--xy-line),
    0 3px 0 0 var(--xy-line),
    -3px 0 0 0 var(--xy-line),
    3px 0 0 0 var(--xy-line),
    0 9px 0 -3px var(--xy-drop);
  color: var(--text-strong);
  font-size: 14px;
  line-height: 1.65;
  transform-origin: left bottom;
}

.xy-say[hidden] { display: none; }

/* Stepped tail, pointing left by default (bubble to the right of 小一). */
.xy-say::before,
.xy-say::after {
  content: '';
  position: absolute;
  bottom: 14px;
  width: 12px;
  height: 12px;
  clip-path: polygon(100% 0, 100% 100%, 0 100%, 0 75%, 25% 75%, 25% 50%, 50% 50%, 50% 25%, 75% 25%, 75% 0);
}

.xy-say::before {
  left: -14px;
  background: var(--xy-line);
}

.xy-say::after {
  left: -7px;
  bottom: 17px;
  width: 9px;
  height: 9px;
  background: var(--xy-paper);
}

.xy-bubble-left { flex-direction: row-reverse; }
.xy-bubble-left .xy-say { transform-origin: right bottom; }
.xy-bubble-left .xy-say::before { left: auto; right: -14px; transform: scaleX(-1); }
.xy-bubble-left .xy-say::after { left: auto; right: -7px; transform: scaleX(-1); }

.xy-bubble-top { flex-direction: column-reverse; align-items: center; gap: 12px; }
.xy-bubble-top .xy-say { transform-origin: center bottom; }
.xy-bubble-top .xy-say::before { left: calc(50% - 6px); bottom: -14px; transform: rotate(-90deg); }
.xy-bubble-top .xy-say::after { left: calc(50% - 5px); bottom: -7px; transform: rotate(-90deg); }

.xy-say.is-in { animation: xy-pop 0.24s steps(4, end) both; }
.xy-say.is-out { animation: xy-out 0.16s steps(2, end) both; }

@keyframes xy-pop {
  0% { opacity: 0; transform: scale(0.7) translateY(8px); }
  60% { opacity: 1; transform: scale(1.04) translateY(-2px); }
  100% { opacity: 1; transform: none; }
}

@keyframes xy-out {
  to { opacity: 0; transform: scale(0.9) translateY(4px); }
}

.xy-say-text b { color: var(--accent-ink); }

.xy-say-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.xy-say-actions[hidden] { display: none; }

/* Pixel buttons: a square frame and a 3px step that presses down. */
.xy-act {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px;
  border: 2px solid var(--xy-line);
  border-radius: 0;
  background: var(--xy-paper);
  box-shadow: 0 3px 0 0 var(--xy-line);
  color: var(--text-strong);
  font-size: 12.5px;
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: transform 0.05s steps(1), box-shadow 0.05s steps(1);
}

.xy-act:hover { background: color-mix(in srgb, var(--xy-line) 8%, var(--xy-paper)); }
.xy-act:active { transform: translateY(3px); box-shadow: 0 0 0 0 var(--xy-line); }
.xy-act.is-primary { background: #3c8ee0; color: #fff; }
.xy-act.is-primary:hover { background: #2f80d2; }
.xy-act kbd { font-family: var(--font-mono); font-size: 10px; opacity: 0.8; }

@media (prefers-reduced-motion: reduce) {
  .xy-say.is-in, .xy-say.is-out { animation: none; }
}

/* ---- 小一 & 小零 on the sign-in card (assets/js/auth-duo.js): sitting on
   its top edge, 小一 on the left, 小零 on the right. ---- */
.auth-card { position: relative; }
.auth-duo {
  position: absolute;
  left: 0;
  right: 0;
  top: -72px;
  height: 78px;
  pointer-events: none;
}
.auth-duo > div { position: absolute; bottom: 0; display: flex; pointer-events: auto; }
.auth-duo-xiaoyi { left: 22px; }
.auth-duo-xiaoling { right: 22px; }
.auth-duo .xy-shadow { display: none; }

/* ---- 小一 on the site pages (assets/js/companion.js) ---- */
.xy-dock {
  position: fixed;
  right: 18px;
  bottom: 14px;
  z-index: 800;
}

.xy-dock-body { position: relative; }
.xy-dock { transition: transform 0.4s steps(5, end), opacity 0.2s; }
.xy-dock.is-away { transform: translateY(130%); opacity: 0; pointer-events: none; }
.xy-dock-body[hidden] { display: none; }
.xy-dock .xy { align-items: flex-end; }
.xy-dock .xy-say { width: 280px; max-width: calc(100vw - 110px); margin-bottom: 26px; }

.xy-dock-hide {
  position: absolute;
  right: -2px;
  top: -8px;
  z-index: 2;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 2px solid var(--xy-line, #16386a);
  border-radius: 0;
  background: var(--bg-elevated);
  box-shadow: 0 2px 0 0 var(--xy-line, #16386a);
  color: var(--text-strong);
  font-size: 14px;
  line-height: 16px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s;
}

.xy-dock:hover .xy-dock-hide,
.xy-dock-hide:focus-visible { opacity: 1; }

/* Tucked away: the sprite's cell bottom is the window's bottom edge, so
   only his head shows (the peek row does the rising). */
.xy-dock.is-min { right: 26px; bottom: 0; }

.xy-dock-peek {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  line-height: 0;
}

.xy-dock-peek[hidden] { display: none; }
.xy-dock-peek .xy-shadow { display: none; }
.xy-dock-peek:hover .xy-body { transform: translateY(-4px); transition: transform 0.12s steps(2); }
.xy-dock-peek:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

@media (max-width: 640px) {
  .xy-dock { right: 8px; bottom: 8px; }
  .xy-dock .xy-say { width: 220px; }
}

@media print {
  .xy-dock { display: none; }
}

/* ===================================================== pressable controls
   The course pages' feel, everywhere: buttons are keys — a solid edge under
   them, a lift on hover, and they go down when pressed. */
.btn {
  border-radius: 12px;
  box-shadow: 0 4px 0 #0a4f8f;
  transition: transform 0.08s var(--ease), box-shadow 0.08s var(--ease), background 0.15s;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 5px 0 #0a4f8f; }
.btn:active { transform: translateY(3px); box-shadow: 0 1px 0 #0a4f8f; }
.btn-gold { box-shadow: 0 4px 0 #c8962a; }
.btn-gold:hover { box-shadow: 0 5px 0 #c8962a; }
.btn-gold:active { box-shadow: 0 1px 0 #c8962a; }
.btn-ghost { background: var(--bg-elevated); box-shadow: 0 3px 0 var(--line); }
.btn-ghost:hover { box-shadow: 0 4px 0 color-mix(in srgb, var(--blue) 45%, var(--line)); }
.btn-ghost:active { box-shadow: 0 1px 0 var(--line); }
.btn-quiet, .btn-quiet:hover, .btn-quiet:active { box-shadow: none; transform: none; }
.btn-store { box-shadow: 0 5px 0 #0a4f8f; transition: transform 0.08s, box-shadow 0.08s; }
.btn-store:hover { transform: translateY(-1px); box-shadow: 0 6px 0 #0a4f8f; }
.btn-store:active { transform: translateY(4px); box-shadow: 0 1px 0 #0a4f8f; }
@media (prefers-reduced-motion: reduce) {
  .btn, .btn:hover, .btn:active, .btn-store:hover, .btn-store:active { transform: none; }
}

/* 小一 in the nav blinks now and then, and when you point at him. */
.brand-mark .xy-eye {
  transform-box: fill-box;
  transform-origin: 50% 60%;
  animation: brand-blink 6s infinite;
}
.brand:hover .brand-mark .xy-eye { animation: brand-blink-now 0.32s ease-in-out; }
@keyframes brand-blink {
  0%, 94%, 100% { transform: scaleY(1); }
  96% { transform: scaleY(0.12); }
}
@keyframes brand-blink-now {
  0%, 100% { transform: scaleY(1); }
  45% { transform: scaleY(0.12); }
}
@media (prefers-reduced-motion: reduce) {
  .brand-mark .xy-eye { animation: none; }
}
