/* 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;
  /* a jump to an anchor (membership.html#offer, the 闲鱼 /r/ links) stops below
     the sticky nav instead of tucking the heading under it */
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

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 {
  /* the same column as .pg-body (app.css): --max wide with --gutter inside, so the
     nav, a page's header and its content start and end at the same edges */
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.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;
}

/* 窗口矮的时候菜单在里面滚，最多到窗口底下（高度由 ui.js 打开时按位置算；2026-10-16） */
.nav-menu {
  overflow-y: auto;
  overscroll-behavior: contain;
}

.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:not(.nav-menu-marks) {
  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(3, 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. */

.is-founder .pb-lip { fill: #c9661c; }
.is-founder .pb-face { fill: #f59a3a; }

.pro-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: -0.16em;
  width: 1.1em;
  height: 1.1em;
  margin-left: 0.3em;
  flex: 0 0 auto;
  filter: drop-shadow(0 1px 1px rgba(160, 100, 10, 0.25));
}
/* 排位段位 beside a name: the winged crest, a touch taller than the text. */
.rank-badge {
  display: inline-flex;
  flex: 0 0 auto;
  vertical-align: -0.34em;
  width: 2.05em;
  height: 1.38em;
  margin-left: 0.22em;
}
.rank-badge img { display: block; width: 100%; height: 100%; object-fit: contain; }
.rank-badge[data-tier="diamond"] img,
.rank-badge[data-tier="master"] img,
.rank-badge[data-tier="grandmaster"] img,
.rank-badge[data-tier="king"] img { filter: drop-shadow(0 0 3px rgba(255, 214, 120, 0.55)); }
.nav-menu-stats b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

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

.pro-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 0.5em;
  padding: 2px 9px 2px 4px;
  border-radius: 9999px;
  background: linear-gradient(100deg, #fff4d6, #fde6a8);
  box-shadow: inset 0 0 0 1px rgba(208, 154, 42, 0.35);
  color: #7a5412;
  font-size: 12px;
  font-weight: var(--fw-bold);
  line-height: 18px;
  vertical-align: 0.2em;
  white-space: nowrap;
}
.pro-pill svg { width: 17px; height: 17px; }
.pro-pill.is-founder {
  position: relative;
  overflow: hidden;
  padding-right: 4px;
  background: linear-gradient(180deg, #1d3767, #0e1e3f);
  box-shadow: inset 0 0 0 1px rgba(246, 205, 110, 0.7), 0 2px 6px rgba(14, 30, 63, 0.25);
  color: #f8dc8e;
  letter-spacing: 0.02em;
}
.pro-pill.is-founder em {
  padding: 0 7px;
  border-radius: 9999px;
  background: linear-gradient(180deg, #fff2c6, #e9b84a);
  color: #2b1c05;
  font-family: "Roboto Mono", ui-monospace, monospace;
  font-size: 11px;
  font-style: normal;
  font-weight: 800;
  line-height: 16px;
}
/* a slow sheen across the founder pill */
.pro-pill.is-founder::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255, 236, 170, 0.35) 50%, transparent 65%) no-repeat;
  background-size: 250% 100%;
  background-position: 160% 0;
  animation: founder-sheen 5.5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes founder-sheen { 0%, 72% { background-position: 160% 0; } 100% { background-position: -60% 0; } }
@media (prefers-reduced-motion: reduce) { .pro-pill.is-founder::after { animation: none; display: none; } }
/* 班级会员（老师的教师会员带来的）：蓝绿色，和自己买的金色分开 */
.pro-badge.is-class { filter: drop-shadow(0 1px 1px rgba(20, 110, 95, 0.25)); }
.pro-pill.is-class { background: linear-gradient(100deg, #e3f7f2, #c9efe6); box-shadow: inset 0 0 0 1px rgba(31, 138, 122, 0.3); color: #17695d; }
[data-theme='dark'] .pro-pill.is-class { background: rgba(60, 191, 168, 0.14); color: #8fe0d0; }
[data-theme='dark'] .pro-pill { background: rgba(244, 198, 83, 0.14); color: #f7d78c; }
[data-theme='dark'] .pro-pill.is-founder { background: linear-gradient(180deg, #1d3767, #0e1e3f); color: #f8dc8e; }
/* 永久会员 + 教师会员（10-22）：紫金，和创始会员一样有一道慢慢扫过的光 */
.pro-badge.is-elite, .pro-badge.is-founder-teacher { filter: drop-shadow(0 1px 2px rgba(80, 50, 170, 0.35)); }
.pro-pill.is-elite {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, #6a4be6, #3f27a3);
  box-shadow: inset 0 0 0 1px rgba(246, 205, 110, 0.75), 0 2px 6px rgba(63, 39, 163, 0.3);
  color: #fbe3a4;
  letter-spacing: 0.02em;
}
.pro-pill.is-elite::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255, 236, 170, 0.35) 50%, transparent 65%) no-repeat;
  background-size: 250% 100%;
  background-position: 160% 0;
  animation: founder-sheen 5.5s ease-in-out infinite;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .pro-pill.is-elite::after { animation: none; display: none; } }
[data-theme='dark'] .pro-pill.is-elite { background: linear-gradient(180deg, #6a4be6, #3f27a3); color: #fbe3a4; }
.pro-badge.is-founder { width: 1.15em; height: 1.15em; filter: drop-shadow(0 0 2.5px rgba(240, 190, 80, 0.5)) drop-shadow(0 1px 1px rgba(14, 30, 63, 0.3)); }

/* 段位 crest + member mark beside a name: one group, centred on the text */
.name-marks { display: inline-flex; align-items: center; gap: 4px; margin-left: 0.3em; vertical-align: middle; line-height: 0; white-space: nowrap; }
.name-marks .rank-badge, .name-marks .pro-badge, .name-marks .pro-pill { margin: 0; vertical-align: 0; }
.name-marks .pro-pill { line-height: 18px; }
.name-marks .rank-badge { width: 27px; height: 18px; }
.name-marks .pro-badge { width: 17px; height: 17px; }
.name-marks .pro-badge.is-founder { width: 18px; height: 18px; }
.rank-pill { display: inline-flex; align-items: center; gap: 4px; padding: 1px 9px 1px 4px; border-radius: 9999px; background: var(--bg-soft); box-shadow: inset 0 0 0 1px var(--line-soft); color: var(--text-muted); font-size: 12px; font-weight: var(--fw-bold); line-height: 20px; white-space: nowrap; }
.rank-pill .rank-badge { width: 27px; height: 18px; margin: 0; vertical-align: 0; }
.nav-menu-marks { display: flex !important; flex-wrap: wrap; gap: 6px; margin: 6px 0 2px; }
.nav-menu-marks .pro-pill { margin: 0; vertical-align: 0; }

/* --------------------------------------------------------------- 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
   ui.js renderFooter: the brand and a line about the site, the site by what
   it is for in five columns, then the bottom bar — the copyright on the left,
   the legal pages on the right. */

/* The footer stays at the bottom of the window: on a short page (or while a
   page's content is still loading) it used to ride up into the middle of the
   screen. The page is a column; main takes the room that is left. */
body:has(> footer.footer) {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}
body:has(> footer.footer) > main { flex: 1 0 auto; }
body:has(> footer.footer) > footer.footer { flex: none; }

.footer {
  border-top: 1px solid var(--line-soft);
  background: var(--bg-tint);
}
.foot-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2.4fr);
  gap: 56px;
}
.footer .foot-top { padding-top: 48px; padding-bottom: 40px; }
.foot-brand .brand-word { display: inline; }
.foot-brand .brand { display: inline-flex; align-items: center; gap: 8px; color: var(--text-strong); text-decoration: none; }
.foot-brand p { max-width: 300px; margin: 14px 0 0; color: var(--text-muted); font-size: 13.5px; line-height: 1.75; }
.foot-cols { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 24px; }
.foot-col h2 { margin: 4px 0 14px; color: var(--text-strong); font-size: 13px; font-weight: var(--fw-bold); letter-spacing: 0.02em; }
.foot-col ul { display: flex; flex-direction: column; gap: 10px; margin: 0; padding: 0; list-style: none; }
.foot-col a { color: var(--text-muted); font-size: 13.5px; text-decoration: none; transition: color 0.15s; }
.foot-col a:hover { color: var(--accent-ink, var(--accent)); }

.foot-bottom { border-top: 1px solid var(--line-soft); }
.foot-bottom-inner { display: flex; align-items: center; justify-content: space-between; gap: 8px 24px; flex-wrap: wrap; padding: 18px 0 20px; }
.foot-copy { margin: 0; color: var(--text-dim); font-size: 12.5px; }
.foot-legal { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 20px; }
.foot-legal a { color: var(--text-muted); font-size: 12.5px; text-decoration: none; transition: color 0.15s; }
.foot-legal a:hover { color: var(--accent-ink, var(--accent)); text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 860px) {
  .foot-top { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  .footer .foot-top { padding-top: 36px; padding-bottom: 32px; }
  .foot-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 16px; }
  .foot-bottom-inner { flex-direction: column; align-items: flex-start; }
}

/* ------------------------------------------------------------ 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) {

  .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; }
  /* phones: the peeking head is smaller, tucked in the corner, and slips away while the page scrolls */
  .xy-dock.is-min { right: 4px; transform: scale(0.72); transform-origin: right bottom; }
  .xy-dock.is-min.is-scrolling { transform: scale(0.72) translateY(110%); }
}

@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; }
}

/* ------------------------------------------------------------- loading
   While the session and data are on their way, a thin bar runs along the top
   (only after 300 ms, so a quick load shows nothing) and the blocks the page
   is about to fill hold their shape as soft shimmering placeholders. A block
   opts in with data-skel (and --skel-h for its height); a page can also put
   .skel shapes of its own into a container it will replace. */

:root { --skel-a: #eef1f5; --skel-b: #f8fafc; }

.oot-loadbar {
  position: fixed;
  z-index: 9999;
  top: 0; left: 0; right: 0;
  height: 3px;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s;
}
.oot-booting .oot-loadbar,
.oot-busy .oot-loadbar { opacity: 1; transition-delay: 0.3s; }
.oot-loadbar::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 38%;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(90deg, transparent, var(--accent) 45%, #8a64e0);
  animation: oot-loadbar 1.25s var(--ease) infinite;
}
@keyframes oot-loadbar { from { transform: translateX(-100%); } to { transform: translateX(280%); } }

.skel,
.oot-booting [data-skel]:empty {
  display: block;
  border-radius: var(--radius-card);
  background: linear-gradient(100deg, var(--skel-a) 30%, var(--skel-b) 50%, var(--skel-a) 70%) 0 0 / 300% 100%;
  animation: oot-shimmer 1.5s linear infinite;
}
.oot-booting [data-skel]:empty { min-height: var(--skel-h, 96px); }
.skel { min-height: var(--skel-h, 16px); }
.skel-line { height: 12px; min-height: 0; border-radius: 6px; }
.skel-stack { display: grid; gap: 16px; }
.skel-row { display: grid; grid-template-columns: repeat(var(--skel-cols, 3), minmax(0, 1fr)); gap: 16px; }
@media (max-width: 720px) { .skel-row { grid-template-columns: repeat(min(2, var(--skel-cols, 3)), minmax(0, 1fr)); } }
@keyframes oot-shimmer { to { background-position: -300% 0; } }

/* Text that is about to be filled reads as a short placeholder bar. */
.is-pending #profileName,
.is-pending #profileMeta {
  width: 9em;
  max-width: 100%;
  border-radius: 8px;
  color: transparent;
  background: linear-gradient(100deg, var(--skel-a) 30%, var(--skel-b) 50%, var(--skel-a) 70%) 0 0 / 300% 100%;
  animation: oot-shimmer 1.5s linear infinite;
}
.is-pending #profileMeta { display: inline-block; width: 12em; }

/* What arrives settles in rather than popping. */
.oot-ready [data-skel] > *,
.skel-done > * { animation: oot-arrive 0.36s var(--ease) both; }
@keyframes oot-arrive { from { opacity: 0; transform: translateY(6px); } }

@media (prefers-reduced-motion: reduce) {
  .skel, .oot-booting [data-skel]:empty, .oot-loadbar::before, .is-pending #profileName, .is-pending #profileMeta { animation: none; }
  .oot-ready [data-skel] > *, .skel-done > * { animation: none; }
}

/* A number that is about to be filled: keep its box, hide its default. */
.oot-booting [data-skel-text] {
  border-radius: 6px;
  color: transparent !important;
  background: linear-gradient(100deg, var(--skel-a) 30%, var(--skel-b) 50%, var(--skel-a) 70%) 0 0 / 300% 100%;
  animation: oot-shimmer 1.5s linear infinite;
}

/* The practice screen before its lesson arrives: a title bar and three lines
   of "text" where the passage will be. */
#practiceTitle:empty::before {
  content: '';
  display: inline-block;
  width: 7em;
  height: 0.85em;
  border-radius: 8px;
  background: var(--skel-a);
  animation: oot-pulse 1.2s ease-in-out infinite alternate;
}
.typing-text:empty::before {
  content: '';
  display: block;
  height: 118px;
  background:
    linear-gradient(var(--skel-a), var(--skel-a)) 0 10px / 94% 20px no-repeat,
    linear-gradient(var(--skel-a), var(--skel-a)) 0 54px / 88% 20px no-repeat,
    linear-gradient(var(--skel-a), var(--skel-a)) 0 98px / 56% 20px no-repeat;
  animation: oot-pulse 1.2s ease-in-out infinite alternate;
}
@keyframes oot-pulse { from { opacity: 1; } to { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
  #practiceTitle:empty::before, .typing-text:empty::before { animation: none; }
}

/* The music toggle, when on: accent colour and a gentle bob in time. */
.nav-icon-btn.is-on { color: var(--accent); }
.nav-icon-btn.is-on svg { animation: music-bob 1.43s ease-in-out infinite; }
@keyframes music-bob { 0%, 100% { transform: none; } 50% { transform: translateY(-1.5px) rotate(-4deg); } }
@media (prefers-reduced-motion: reduce) { .nav-icon-btn.is-on svg { animation: none; } }

/* English edition: the page stays hidden for the moment it takes to
   translate, so Chinese never flashes first (i18n-dom.js; 2.5 s failsafe). */
.i18n-pending body { visibility: hidden; }

/* announcement tones and link (后台 → 站点设置) */
.site-announcement.is-success { background: linear-gradient(90deg, #e9f8ef, #f2fbf5); color: #1f6b43; }
.site-announcement.is-warn { background: linear-gradient(90deg, #fff4dc, #fff9ec); color: #7a5412; }
.site-announcement .sa-link { margin-left: 10px; color: inherit; font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }


/* ------------------------------------------------ 快捷设置 (nav) */
.nav-menu-quick { min-width: 236px; }
.nav-qs-lang { display: flex; align-items: center; gap: 10px; padding: 6px 8px 6px 10px; }
.nav-qs-lang > svg { width: 17px; height: 17px; flex: 0 0 auto; color: var(--text-dim); }
.nav-qs-seg { display: inline-flex; flex: 1 1 auto; padding: 3px; border-radius: 10px; background: var(--bg-sunken); }
.nav-qs-seg button { flex: 1 1 0; padding: 5px 8px; border: 0; border-radius: 8px; background: transparent; color: var(--text-muted); font: inherit; font-size: 13px; font-weight: var(--fw-semibold); cursor: pointer; }
.nav-qs-seg button[aria-checked='true'] { background: var(--bg-elevated); color: var(--text-strong); box-shadow: 0 1px 3px rgba(16, 24, 40, 0.12); }
.nav-qs-row .nav-qs-label { display: grid; flex: 1 1 auto; min-width: 0; }
.nav-qs-row .nav-qs-label small { overflow: hidden; color: var(--text-dim); font-size: 11.5px; font-weight: var(--fw-medium); text-overflow: ellipsis; white-space: nowrap; }
.nav-qs-row .nav-qs-label small:empty { display: none; }
.nav-switch { position: relative; flex: 0 0 auto; width: 34px; height: 20px; border-radius: 9999px; background: var(--line); transition: background-color 0.2s; }
.nav-switch i { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); transition: transform 0.2s cubic-bezier(0.3, 1.3, 0.5, 1); }
.nav-qs-row[aria-checked='true'] .nav-switch { background: var(--accent); }
.nav-qs-row[aria-checked='true'] .nav-switch i { transform: translateX(14px); }
.nav-qs-row[aria-checked='true'] > svg { color: var(--accent); }

/* the account chip: members' avatars wear a ring — gold for 会员, a gilded double ring for 创始会员 */
.nav-avatar.is-pro .avatar-fallback { box-shadow: 0 0 0 2px var(--bg-elevated), 0 0 0 3.5px #f0bf45; }
.nav-avatar.is-founder .avatar-fallback { box-shadow: 0 0 0 2px var(--bg-elevated), 0 0 0 4px #e9b84a, 0 0 0 5px #132a55; }
.nav-avatar .name-marks { margin-left: 0; }

/* ------------------------------------------------ 手机、平板 (touch.js) */
/* the hidden field a game types through: pinned at the top so focusing it never scrolls the page */
.touch-bridge { position: fixed; top: 0; left: 0; z-index: -1; width: 1px; height: 1px; padding: 0; border: 0; opacity: 0.01; font-size: 16px; color: transparent; background: transparent; caret-color: transparent; resize: none; pointer-events: none; }
/* keyboard shortcuts mean nothing on a phone */
.is-touch .kbd-only, .is-touch .eng-footer { display: none !important; }
/* with the phone's keyboard up, keep the site's chrome out of the way */
.is-kb-open .footer, .is-kb-open .xy-companion, .is-kb-open .companion { display: none !important; }

/* the friends button in the nav (every page; the dock itself is live.css) */
.nav-friends { position: relative; cursor: pointer; }
.nav-friends-count { position: absolute; top: -5px; right: -5px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; background: #34c46a; color: #fff; font-size: 11px; font-weight: 800; line-height: 18px; text-align: center; box-shadow: 0 0 0 2px var(--bg-elevated); }
.nav-friends-count.is-req { background: #eb5757; }
.nav-friends .nav-friends-count { top: -4px; right: -4px; }

/* ===================== 更新了什么 / 网站已更新 (assets/js/updates.js) */
.upd-overlay { position: fixed; inset: 0; z-index: 9000; display: grid; place-items: center; padding: 20px; background: rgba(15, 22, 36, 0.42); opacity: 0; transition: opacity 0.22s; }
.upd-overlay.is-in { opacity: 1; }
.upd-card { width: min(480px, 100%); max-height: min(78vh, 640px); overflow: auto; padding: 24px 26px 20px; border-radius: 20px; background: var(--bg-elevated, #fff); color: var(--text, #2a3346); box-shadow: 0 24px 60px -20px rgba(15, 22, 36, 0.45); transform: translateY(8px); transition: transform 0.25s; }
.upd-overlay.is-in .upd-card { transform: none; }
.upd-eyebrow { margin: 0 0 4px; color: var(--accent-ink, #0a5ca6); font-size: 12px; font-weight: 700; letter-spacing: 0.04em; }
.upd-card h2 { margin: 0 0 6px; color: var(--text-strong, #111827); font-size: 20px; line-height: 1.35; }
.upd-card h3 { margin: 16px 0 4px; color: var(--text-strong, #111827); font-size: 15px; }
.upd-meta { margin: 0 0 8px; color: var(--text-dim, #8a93a3); font-size: 12px; }
.upd-card ul { margin: 0; padding-left: 20px; list-style: disc; }
.upd-card li { margin: 4px 0; font-size: 14px; line-height: 1.6; }
.upd-acts { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 18px; }
.upd-all { font-size: 13px; }
.upd-pill { position: fixed; left: 50%; bottom: 20px; z-index: 8000; display: flex; align-items: center; gap: 10px; padding: 8px 8px 8px 16px; border-radius: 999px; background: var(--bg-elevated, #fff); color: var(--text, #2a3346); box-shadow: 0 10px 30px -10px rgba(15, 22, 36, 0.4), 0 0 0 1px var(--line-soft, #e6eaf0); font-size: 13.5px; transform: translateX(-50%); }
.upd-x { width: 28px; height: 28px; border: 0; border-radius: 50%; background: none; color: var(--text-dim, #8a93a3); font-size: 18px; cursor: pointer; }
.foot-ver { color: inherit; text-decoration: none; }
.foot-ver:hover { text-decoration: underline; }

/* ===================== 保存进度 (guest.js gate): asked at a break, keyboard first */
.guest-gate { position: fixed; inset: 0; z-index: 8500; display: grid; place-items: center; padding: 20px; background: rgba(15, 22, 36, .45); opacity: 0; transition: opacity .18s; }
.guest-gate.is-in { opacity: 1; }
.guest-gate-card { width: min(460px, 100%); padding: 26px 26px 18px; border-radius: 20px; background: var(--bg-elevated, #fff); color: var(--text, #2a3346); text-align: center; box-shadow: 0 24px 60px -20px rgba(15, 22, 36, .45); transform: translateY(8px) scale(.98); transition: transform .2s; }
.guest-gate.is-in .guest-gate-card { transform: none; }
.guest-gate-card .guest-buddy { display: inline-block; width: 56px; height: 56px; }
.guest-gate-card .guest-buddy svg { width: 100%; height: 100%; }
.guest-gate-card h2 { margin: 8px 0 8px; color: var(--text-strong, #111827); font-size: 20px; line-height: 1.35; }
.guest-gate-card p { margin: 0 0 18px; color: var(--text-muted, #5a6476); font-size: 14px; line-height: 1.7; }
.guest-gate-card p b { color: var(--text-strong, #111827); }
.guest-gate-acts { display: grid; gap: 10px; }
.guest-gate-acts .btn { justify-content: center; }
.guest-gate-acts kbd { margin-left: 6px; padding: 1px 6px; border-radius: 5px; background: rgba(0, 0, 0, .08); font-size: 11px; font-family: inherit; }
.guest-gate-card .guest-login { display: inline-block; margin-top: 12px; color: var(--text-dim, #8a93a3); font-size: 12.5px; }

/* ===================== phones (assets/js/phone.js): 打字要用电脑键盘 */
html.ph-locked, html.ph-locked body { overflow: hidden; }
.ph-sheet { position: fixed; inset: 0; z-index: 9500; display: flex; align-items: flex-end; background: rgba(15, 22, 36, .5); opacity: 0; transition: opacity .2s; }
.ph-sheet.is-in { opacity: 1; }
.ph-card { width: 100%; max-height: 92vh; overflow: auto; padding: 22px 20px calc(18px + env(safe-area-inset-bottom)); border-radius: 22px 22px 0 0; background: var(--bg-elevated, #fff); color: var(--text, #2a3346); text-align: center; transform: translateY(30px); transition: transform .25s cubic-bezier(.2, .8, .2, 1); }
.ph-sheet.is-in .ph-card { transform: none; }
.ph-card h2 { margin: 10px 0 8px; color: var(--text-strong, #111827); font-size: 21px; }
.ph-card p { margin: 0 0 18px; color: var(--text-muted, #5a6476); font-size: 14.5px; line-height: 1.7; }
.ph-card p b { color: var(--text-strong, #111827); }
.ph-art { display: flex; align-items: center; justify-content: center; gap: 14px; height: 64px; }
.ph-phone { width: 26px; height: 46px; border: 3px solid #9aa4b4; border-radius: 7px; }
.ph-laptop { position: relative; width: 66px; height: 42px; border: 3px solid #0d76c9; border-radius: 6px 6px 2px 2px; }
.ph-laptop::after { content: ''; position: absolute; left: -12px; right: -12px; bottom: -11px; height: 6px; border-radius: 0 0 6px 6px; background: #0d76c9; }
.ph-arrow { color: #e0a132; font-size: 24px; font-weight: 800; }
.ph-acts { display: grid; gap: 10px; }
.ph-acts .btn { justify-content: center; min-height: 46px; font-size: 15px; }
.ph-kb { padding: 10px; border: 0; background: none; color: var(--text-muted, #5a6476); font: inherit; font-size: 13.5px; cursor: pointer; }
.ph-home { display: inline-block; margin-top: 4px; color: var(--text-dim, #8a93a3); font-size: 13px; }
.ph-banner { position: relative; margin: 12px 14px 0; padding: 14px 14px 10px; border-radius: 16px; background: linear-gradient(135deg, #fff7df, #eef6ff); box-shadow: inset 0 0 0 1px rgba(13, 118, 201, .12); }
.ph-banner-top { display: flex; gap: 10px; margin: 0 26px 12px 0; text-align: left; }
.ph-ico { font-size: 22px; line-height: 1.2; }
.ph-banner b { display: block; color: var(--text-strong, #111827); font-size: 15px; }
.ph-banner span { color: var(--text-muted, #5a6476); font-size: 13px; line-height: 1.6; }
.ph-x { position: absolute; top: 6px; right: 8px; width: 30px; height: 30px; border: 0; border-radius: 50%; background: none; color: var(--text-dim, #8a93a3); font-size: 20px; cursor: pointer; }
.ph-banner .ph-acts .btn { min-height: 42px; font-size: 14px; }

/* ------------------------------------------------------------ 每日挑战
   assets/js/daily.js — shared by the test page (full card) and the home page's
   今天 (compact), so it lives here rather than in app.css. The test page's full card (passage start + today's top
   ten) and the home page's compact one. */
.dc { margin-top: 24px; padding: 22px 24px; border: 1px solid var(--line-soft); border-radius: 20px; background: var(--bg-elevated); box-shadow: var(--shadow-sm); text-align: left; }
.dc-tabs { display: inline-flex; gap: 4px; padding: 4px; border-radius: var(--radius-pill); background: var(--bg-sunken); }
.dc-tabs button { padding: 7px 14px; border: 0; border-radius: var(--radius-pill); background: transparent; color: var(--text-muted); font: inherit; font-size: 13.5px; font-weight: var(--fw-semibold); cursor: pointer; transition: background-color 0.2s, color 0.2s; }
.dc-tabs button:hover { color: var(--text-strong); }
.dc-tabs button[aria-selected='true'] { background: var(--bg-elevated); color: var(--blue-dark); box-shadow: var(--shadow-sm); }
.dc-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
.dc-eyebrow { margin: 0 0 4px; color: var(--accent); font-size: 12.5px; font-weight: var(--fw-bold); letter-spacing: 0.04em; }
.dc-head h2 { margin: 0; color: var(--text-strong); font-size: 20px; line-height: 1.3; }
.dc-tabs button small { margin-left: 6px; color: var(--text-dim); font-size: 11.5px; font-weight: var(--fw-semibold); }
.dc-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; align-items: start; }
.dc-go { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; min-width: 0; }
.dc-text { margin: 0; padding: 12px 14px; border-radius: 12px; background: var(--bg-soft); color: var(--text-muted); font-size: 14px; line-height: 1.7; width: 100%; box-sizing: border-box; }
.dc-text[lang='en'] { font-family: var(--font-mono); font-size: 13px; }
.dc-me { margin: 0; color: var(--text-muted); font-size: 13.5px; }
.dc-me b { color: var(--text-strong); }
.dc-y { margin: 0; color: var(--text-dim); font-size: 12.5px; }
.dc-board { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.dc-board li { display: grid; grid-template-columns: 22px 28px minmax(0, 1fr) auto auto; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 10px; }
.dc-board li.is-me { background: var(--accent-tint); }
.dc-rank { color: var(--text-dim); font-weight: var(--fw-bold); font-size: 13px; text-align: center; font-variant-numeric: tabular-nums; }
.dc-board li:nth-child(1) .dc-rank { color: #d69e2e; }
.dc-board li:nth-child(2) .dc-rank { color: #8a99ad; }
.dc-board li:nth-child(3) .dc-rank { color: #c07a43; }
.dc-av { width: 28px; height: 28px; border-radius: 50%; font-size: 12px; }
.dc-name { display: flex; align-items: center; gap: 4px; min-width: 0; color: var(--text-strong); font-size: 14px; }
.dc-name em { font-style: normal; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.dc-board b { color: var(--text-strong); font-size: 15px; font-variant-numeric: tabular-nums; }
.dc-board small { color: var(--text-dim); font-size: 11.5px; }
.dc-empty { margin: 0; padding: 14px; border-radius: 12px; background: var(--bg-soft); color: var(--text-dim); font-size: 13.5px; line-height: 1.6; }
.rs-daily { padding: 10px 14px; border-radius: 12px; background: var(--accent-tint); color: var(--text-muted); font-size: 14px; line-height: 1.6; }
.rs-daily:empty { display: none; }
.rs-daily b { color: var(--text-strong); }
@media (max-width: 760px) {
  .dc { padding: 18px 16px; }
  .dc-body { grid-template-columns: minmax(0, 1fr); gap: 16px; }
}

/* ui.foldList: the switch under a long list (history, boards) */
/* Sections that open (FAQ answers, 详细数据, a game's word list): the same chevron
   as every other fold on the site, turning over when open — drawn as a mask so it
   takes the text colour and needs no icon in the markup. */
:root { --chev: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M6.5 9.5 12 15l5.5-5.5' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/></svg>"); }
.faq-list summary > svg { display: none; }
.faq-list details > summary::after,
.rs-details > summary::after,
.gp-vocab > summary::after {
  content: '';
  flex: none;
  width: 18px;
  height: 18px;
  margin-left: auto;
  background: currentColor;
  -webkit-mask: var(--chev) center / contain no-repeat;
  mask: var(--chev) center / contain no-repeat;
  opacity: 0.6;
  transition: transform 0.22s ease, opacity 0.2s;
}
.faq-list details[open] > summary::after,
.rs-details[open] > summary::after,
.gp-vocab[open] > summary::after { transform: rotate(180deg); opacity: 0.9; }
.gp-vocab > summary { display: flex; align-items: center; gap: 4px; list-style: none; }
.gp-vocab > summary::-webkit-details-marker { display: none; }
@media (prefers-reduced-motion: reduce) { .faq-list details > summary::after, .rs-details > summary::after, .gp-vocab > summary::after { transition: none; } }

/* A long list's 「展开全部 N 条 ⌄」 (OOT.ui.foldList): a soft pill centred under the
   list, the list's last rows fading out above it so it reads as "there is more",
   the chevron turning over once it is open. */
.fold-list-toggle { display: flex; align-items: center; justify-content: center; gap: 3px; width: fit-content; margin: 12px auto 0; padding: 8px 12px 8px 16px; border: 0; border-radius: 999px; background: color-mix(in srgb, var(--text-strong) 6%, transparent); color: var(--text-muted); font: inherit; font-size: 13px; font-weight: var(--fw-semibold); line-height: 1; cursor: pointer; transition: background .15s, color .15s; }
.fold-list-toggle:hover { background: var(--accent-tint, rgba(13, 118, 201, .08)); color: var(--accent-ink, var(--accent)); }
.fold-list-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fold-list-toggle svg { width: 15px; height: 15px; transition: transform .2s ease; }
.fold-list-toggle.is-open svg { transform: rotate(180deg); }
.fold-cut { -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 72px), rgba(0, 0, 0, .12)); mask-image: linear-gradient(to bottom, #000 calc(100% - 72px), rgba(0, 0, 0, .12)); }
/* rows that come out of a fold */
.fold-in { animation: fold-in .24s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes fold-in { from { opacity: 0; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) { .fold-in { animation: none; } .fold-list-toggle svg { transition: none; } }

/* 每日挑战 banner and board dialog (daily.js banner / board) */
.dc-banner { position: fixed; left: 20px; bottom: 20px; z-index: 900; display: flex; align-items: flex-start; gap: 12px; width: min(380px, calc(100vw - 40px)); padding: 14px 40px 14px 14px; border: 1px solid var(--line-soft); border-radius: 18px; background: var(--bg-elevated); box-shadow: 0 18px 50px -18px rgba(20, 30, 60, .45); opacity: 0; transform: translateY(16px); transition: opacity .25s, transform .25s; }
.dc-banner.is-in { opacity: 1; transform: none; }
.dc-banner-ico { display: grid; place-items: center; flex: none; width: 40px; height: 40px; border-radius: 12px; background: linear-gradient(135deg, #ffd36b, #f0a830); color: #4a2d00; }
.dc-banner-ico svg { width: 22px; height: 22px; }
.dc-banner-text { display: grid; gap: 3px; min-width: 0; }
.dc-banner-text b { color: var(--text-strong); font-size: 15px; }
.dc-banner-text > span { color: var(--text-muted); font-size: 12.5px; line-height: 1.5; }
.dc-banner-acts { display: flex; gap: 8px; margin-top: 6px; }
.dc-x { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border: 0; border-radius: 8px; background: transparent; color: var(--text-dim); font-size: 18px; line-height: 1; cursor: pointer; }
.dc-x:hover { background: var(--bg-soft); color: var(--text-strong); }
.dc-modal { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 16px; background: rgba(10, 18, 36, .5); backdrop-filter: blur(3px); }
.dc-modal-card { position: relative; width: min(860px, 100%); max-height: calc(100vh - 32px); margin: 0; overflow-y: auto; }
@media (max-width: 560px) { .dc-banner { left: 12px; right: 12px; bottom: 12px; width: auto; } }
/* folded: a pill in the corner (daily.js), tap to open */
.dc-banner-mini { display: none; }
.dc-banner.is-mini { right: auto; align-items: center; gap: 8px; width: auto; padding: 6px 34px 6px 6px; border-radius: 999px; cursor: pointer; }
.dc-banner.is-mini .dc-banner-text { display: none; }
.dc-banner.is-mini .dc-banner-mini { display: block; color: var(--text-strong); font-size: 13px; font-weight: var(--fw-semibold); white-space: nowrap; }
.dc-banner.is-mini .dc-banner-ico { width: 30px; height: 30px; border-radius: 999px; }
.dc-banner.is-mini .dc-banner-ico svg { width: 17px; height: 17px; }
.dc-banner.is-mini .dc-x { top: 50%; right: 4px; width: 26px; height: 26px; transform: translateY(-50%); font-size: 16px; }


/* ---- consent on the sign-up and reset forms (register.html / login.html):
   the boxes to tick before a code is sent; a nudge when one is missing. */
.auth-consent { display: grid; gap: 10px; margin: 6px 0 12px; padding: 12px 14px; border: 1px solid transparent; border-radius: 12px; background: color-mix(in srgb, var(--text-strong) 4%, transparent); }
.auth-check { display: flex; align-items: flex-start; gap: 9px; color: var(--text-muted); font-size: 12.5px; line-height: 1.6; cursor: pointer; }
.auth-check input { flex: none; width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--accent); cursor: pointer; }
.auth-check a { color: var(--accent-ink, var(--accent)); text-decoration: underline; text-underline-offset: 2px; }
.auth-check b { color: var(--text-strong); }
.auth-consent.is-asking { border-color: color-mix(in srgb, var(--red, #d9483b) 55%, transparent); background: color-mix(in srgb, var(--red, #d9483b) 6%, transparent); animation: auth-nudge 0.4s ease; }
@keyframes auth-nudge { 20%, 60% { transform: translateX(-4px); } 40%, 80% { transform: translateX(4px); } }
@media (prefers-reduced-motion: reduce) { .auth-consent.is-asking { animation: none; } }

/* 键盘皮肤 (经验 rewards, store.js REWARDS): one per level, Lv.2 to Lv.12.
   Here, not in keyboard.css, so the previews on the profile and in 练习设置
   read the same colours as the keyboard itself.
     --kbt-tray        the case (a colour or a gradient)
     --kbt-key/-key2   a letter cap, top to bottom; --kbt-text its legend
     --kbt-mod/-mod2   tab, shift, return… ; --kbt-modtext their legend
     --kbt-acc         the return key, the one accent a real set has */
[data-kbt] { --kbt-edge: rgba(0, 0, 0, 0.16); --kbt-shadow: rgba(0, 0, 0, 0.17); }
[data-kbt='classic'] { --kbt-tray: linear-gradient(180deg, #e9eaec, #dcdde0); --kbt-key: #ffffff; --kbt-key2: #f7f7f9; --kbt-text: #1d1d1f; --kbt-mod: #ffffff; --kbt-mod2: #f2f2f5; --kbt-modtext: #6e6e73; --kbt-acc: #ffffff; --kbt-acctext: #6e6e73; }
[data-kbt='mint'] { --kbt-tray: linear-gradient(180deg, #cdeadc, #b4dcc8); --kbt-key: #fbfffd; --kbt-key2: #eaf8f1; --kbt-text: #1f5a47; --kbt-mod: #d7f0e4; --kbt-mod2: #c3e6d5; --kbt-modtext: #2f6f58; --kbt-acc: #5fbf98; --kbt-acctext: #fff; --kbt-edge: rgba(31, 90, 71, 0.22); }
[data-kbt='haze'] { --kbt-tray: linear-gradient(180deg, #e4e1f5, #cfcaea); --kbt-key: #fdfcff; --kbt-key2: #f1effb; --kbt-text: #4b4479; --kbt-mod: #e6e2f8; --kbt-mod2: #d8d3f0; --kbt-modtext: #5d5590; --kbt-acc: #9b8fe0; --kbt-acctext: #fff; --kbt-edge: rgba(75, 68, 121, 0.2); }
[data-kbt='seasalt'] { --kbt-tray: linear-gradient(180deg, #cfe3f2, #b0cee6); --kbt-key: #f9fcff; --kbt-key2: #e8f2fb; --kbt-text: #1e4f78; --kbt-mod: #d6e8f6; --kbt-mod2: #c2dbef; --kbt-modtext: #2b5f8c; --kbt-acc: #3d8fd0; --kbt-acctext: #fff; --kbt-edge: rgba(30, 79, 120, 0.22); }
[data-kbt='sakura'] { --kbt-tray: linear-gradient(180deg, #f6dbe5, #ecc3d2); --kbt-key: #fff9fb; --kbt-key2: #fdedf3; --kbt-text: #7a3550; --kbt-mod: #fbe0ea; --kbt-mod2: #f5cfdd; --kbt-modtext: #8f4664; --kbt-acc: #e87ea3; --kbt-acctext: #fff; --kbt-edge: rgba(122, 53, 80, 0.2); }
[data-kbt='milktea'] { --kbt-tray: linear-gradient(180deg, #e2d1bb, #cdb597); --kbt-key: #fcf6ee; --kbt-key2: #f2e7d7; --kbt-text: #5b4330; --kbt-mod: #b89b7a; --kbt-mod2: #a88a69; --kbt-modtext: #fff8ef; --kbt-acc: #8a5a3b; --kbt-acctext: #fff3e6; --kbt-edge: rgba(91, 67, 48, 0.25); }
[data-kbt='retro'] { --kbt-tray: linear-gradient(180deg, #d6cdb7, #c4bba3); --kbt-key: #f4eedf; --kbt-key2: #e7e0cc; --kbt-text: #46402f; --kbt-mod: #8f8a7c; --kbt-mod2: #7c776a; --kbt-modtext: #f4eedf; --kbt-acc: #d9572c; --kbt-acctext: #fff; --kbt-edge: rgba(70, 64, 47, 0.3); }
[data-kbt='night'] { --kbt-tray: linear-gradient(180deg, #1b2134, #121726); --kbt-key: #2f3956; --kbt-key2: #273048; --kbt-text: #dfe6ff; --kbt-mod: #222a40; --kbt-mod2: #1c2336; --kbt-modtext: #8f9ccc; --kbt-acc: #f2c14e; --kbt-acctext: #3a2c06; --kbt-edge: rgba(0, 0, 0, 0.5); --kbt-shadow: rgba(0, 0, 0, 0.55); }
[data-kbt='matcha'] { --kbt-tray: linear-gradient(180deg, #34503f, #263b2e); --kbt-key: #e9f1df; --kbt-key2: #d9e6cb; --kbt-text: #2d4a36; --kbt-mod: #6f8f5c; --kbt-mod2: #5f7f4d; --kbt-modtext: #f0f6e8; --kbt-acc: #c9a45c; --kbt-acctext: #fff; --kbt-edge: rgba(0, 0, 0, 0.4); --kbt-shadow: rgba(0, 0, 0, 0.4); }
[data-kbt='carbon'] { --kbt-tray: repeating-linear-gradient(45deg, #161616 0 3px, #1e1e1e 3px 6px); --kbt-key: #303030; --kbt-key2: #242424; --kbt-text: #f1f1f1; --kbt-mod: #1f1f1f; --kbt-mod2: #181818; --kbt-modtext: #a8a8a8; --kbt-acc: #ff6a2b; --kbt-acctext: #fff; --kbt-edge: rgba(0, 0, 0, 0.6); --kbt-shadow: rgba(0, 0, 0, 0.6); }
[data-kbt='sunset'] { --kbt-tray: linear-gradient(120deg, #ffb18f, #f58ea8 55%, #a98be0); --kbt-key: #fff7f3; --kbt-key2: #fde9e3; --kbt-text: #6a3550; --kbt-mod: #ffe0d5; --kbt-mod2: #fbcfc3; --kbt-modtext: #8a4562; --kbt-acc: #ff7a59; --kbt-acctext: #fff; --kbt-edge: rgba(106, 53, 80, 0.25); }
[data-kbt='aurora'] { --kbt-tray: linear-gradient(120deg, #0b2a3a, #10203f 45%, #2a1550); --kbt-key: #16304a; --kbt-key2: #12263d; --kbt-text: #9ff3dc; --kbt-mod: #1d2550; --kbt-mod2: #181f45; --kbt-modtext: #c3b3ff; --kbt-acc: linear-gradient(135deg, #3ee0b5, #7b6cf0); --kbt-acctext: #06221c; --kbt-edge: rgba(0, 0, 0, 0.55); --kbt-shadow: rgba(0, 0, 0, 0.6); }
[data-kbt='lemon'] { --kbt-tray: linear-gradient(180deg, #fbf1b8, #f1df86); --kbt-key: #fffef4; --kbt-key2: #fdf8dc; --kbt-text: #6b5a12; --kbt-mod: #f8eaa8; --kbt-mod2: #f1df8c; --kbt-modtext: #7a6716; --kbt-acc: #f2c230; --kbt-acctext: #3d3000; --kbt-edge: rgba(107, 90, 18, 0.22); }
[data-kbt='peach'] { --kbt-tray: linear-gradient(180deg, #ffd9c7, #f9bfa6); --kbt-key: #fffaf7; --kbt-key2: #ffece3; --kbt-text: #7a3f2a; --kbt-mod: #ffe0d2; --kbt-mod2: #fbcdb9; --kbt-modtext: #8c4a33; --kbt-acc: #ff8f6b; --kbt-acctext: #fff; --kbt-edge: rgba(122, 63, 42, 0.2); }
[data-kbt='lavender'] { --kbt-tray: linear-gradient(180deg, #e3d6f5, #cdb9ec); --kbt-key: #fdfbff; --kbt-key2: #f1eafb; --kbt-text: #4d3576; --kbt-mod: #e6dcf7; --kbt-mod2: #d6c7f0; --kbt-modtext: #5e4590; --kbt-acc: #9b6fd9; --kbt-acctext: #fff; --kbt-edge: rgba(77, 53, 118, 0.22); }
[data-kbt='ocean'] { --kbt-tray: linear-gradient(180deg, #0f3a5c, #0a2a45); --kbt-key: #174d75; --kbt-key2: #123f61; --kbt-text: #d7f1ff; --kbt-mod: #0f3555; --kbt-mod2: #0b2b47; --kbt-modtext: #7fc4ea; --kbt-acc: #2ec4d6; --kbt-acctext: #03252b; --kbt-edge: rgba(0, 0, 0, 0.5); --kbt-shadow: rgba(0, 0, 0, 0.5); }
[data-kbt='forest'] { --kbt-tray: linear-gradient(180deg, #2f4a2c, #22381f); --kbt-key: #3d5f39; --kbt-key2: #33522f; --kbt-text: #e5f3d9; --kbt-mod: #2a4127; --kbt-mod2: #22371f; --kbt-modtext: #a9cf93; --kbt-acc: #e0b44c; --kbt-acctext: #2b1e00; --kbt-edge: rgba(0, 0, 0, 0.45); --kbt-shadow: rgba(0, 0, 0, 0.45); }
[data-kbt='coffee'] { --kbt-tray: repeating-linear-gradient(95deg, #5a3d2b 0 7px, #4e3424 7px 9px, #62432f 9px 15px, #4a3122 15px 17px); --kbt-key: #f3e6d6; --kbt-key2: #e6d3bd; --kbt-text: #4a2f1f; --kbt-mod: #7a553c; --kbt-mod2: #684631; --kbt-modtext: #f6e7d4; --kbt-acc: #d4a056; --kbt-acctext: #2e1b06; --kbt-edge: rgba(0, 0, 0, 0.4); --kbt-shadow: rgba(0, 0, 0, 0.4); }
[data-kbt='ink'] { --kbt-tray: linear-gradient(180deg, #ecebe6, #d8d6ce); --kbt-key: #fbfaf6; --kbt-key2: #efede6; --kbt-text: #1c1c1c; --kbt-mod: #3a3a3a; --kbt-mod2: #2a2a2a; --kbt-modtext: #efede6; --kbt-acc: #b23a2e; --kbt-acctext: #fff; --kbt-edge: rgba(0, 0, 0, 0.3); }
[data-kbt='porcelain'] { --kbt-tray: linear-gradient(180deg, #e9f0f8, #d3e0ef); --kbt-key: #ffffff; --kbt-key2: #f1f5fb; --kbt-text: #1d3f7a; --kbt-mod: #2c58a0; --kbt-mod2: #234a8a; --kbt-modtext: #ffffff; --kbt-acc: #1d3f7a; --kbt-acctext: #fff; --kbt-edge: rgba(29, 63, 122, 0.28); }
[data-kbt='cyber'] { --kbt-tray: linear-gradient(120deg, #120b2b, #1d0d3a 55%, #2a0a33); --kbt-key: #22133f; --kbt-key2: #1b0f34; --kbt-text: #5ff6ff; --kbt-mod: #2b1150; --kbt-mod2: #220d42; --kbt-modtext: #ff4fd8; --kbt-acc: linear-gradient(135deg, #ff4fd8, #5ff6ff); --kbt-acctext: #130622; --kbt-edge: rgba(0, 0, 0, 0.6); --kbt-shadow: rgba(0, 0, 0, 0.6); }
[data-kbt='gameboy'] { --kbt-tray: linear-gradient(180deg, #c9c7bd, #b4b2a6); --kbt-key: #9bbc0f; --kbt-key2: #8bac0f; --kbt-text: #0f380f; --kbt-mod: #5a5a64; --kbt-mod2: #4a4a54; --kbt-modtext: #e8e8e8; --kbt-acc: #9c2a5b; --kbt-acctext: #fff; --kbt-edge: rgba(15, 56, 15, 0.35); }
[data-kbt='honey'] { --kbt-tray: linear-gradient(180deg, #ffd88a, #f4bd4f); --kbt-key: #fff8e6; --kbt-key2: #ffefc7; --kbt-text: #6a4300; --kbt-mod: #f5c35c; --kbt-mod2: #e8b145; --kbt-modtext: #5a3900; --kbt-acc: #d98a00; --kbt-acctext: #fff; --kbt-edge: rgba(106, 67, 0, 0.25); }
[data-kbt='sky'] { --kbt-tray: linear-gradient(180deg, #bfe3ff, #96cff7); --kbt-key: #ffffff; --kbt-key2: #eef8ff; --kbt-text: #1b4f7a; --kbt-mod: #d2ecff; --kbt-mod2: #bde2fb; --kbt-modtext: #2a6a9c; --kbt-acc: #ffb648; --kbt-acctext: #3b2500; --kbt-edge: rgba(27, 79, 122, 0.2); }
[data-kbt='rosegold'] { --kbt-tray: linear-gradient(180deg, #f4d3c8, #e5b3a3); --kbt-key: #fff7f4; --kbt-key2: #fbe8e1; --kbt-text: #6e3b33; --kbt-mod: #e8bcae; --kbt-mod2: #dba999; --kbt-modtext: #5e2f28; --kbt-acc: #c97a64; --kbt-acctext: #fff; --kbt-edge: rgba(110, 59, 51, 0.25); }
[data-kbt='graphite'] { --kbt-tray: linear-gradient(180deg, #4a4f57, #3a3e45); --kbt-key: #5c626b; --kbt-key2: #50555d; --kbt-text: #eef1f4; --kbt-mod: #3f434a; --kbt-mod2: #363a40; --kbt-modtext: #b9c0c9; --kbt-acc: #7ad1ff; --kbt-acctext: #06263a; --kbt-edge: rgba(0, 0, 0, 0.45); --kbt-shadow: rgba(0, 0, 0, 0.45); }
[data-kbt='glacier'] { --kbt-tray: linear-gradient(180deg, #e3f6fb, #c4e9f3); --kbt-key: #ffffff; --kbt-key2: #effafd; --kbt-text: #215566; --kbt-mod: #d5f0f7; --kbt-mod2: #c2e6ef; --kbt-modtext: #2f6b7d; --kbt-acc: #62c3dd; --kbt-acctext: #fff; --kbt-edge: rgba(33, 85, 102, 0.2); }
[data-kbt='lava'] { --kbt-tray: linear-gradient(180deg, #2b1410, #1c0c09); --kbt-key: #3a1b15; --kbt-key2: #2f1510; --kbt-text: #ffd2b8; --kbt-mod: #4a1f15; --kbt-mod2: #3c1810; --kbt-modtext: #ff9a6a; --kbt-acc: linear-gradient(135deg, #ff5a1f, #ffc23a); --kbt-acctext: #2a0b00; --kbt-edge: rgba(0, 0, 0, 0.6); --kbt-shadow: rgba(0, 0, 0, 0.6); }
[data-kbt='galaxy'] { --kbt-tray: linear-gradient(120deg, #0d0b2a, #231552 50%, #0b1f4a); --kbt-key: #1f1a4a; --kbt-key2: #19153d; --kbt-text: #f1e9ff; --kbt-mod: #2a2063; --kbt-mod2: #211a52; --kbt-modtext: #b6a4ff; --kbt-acc: linear-gradient(135deg, #f7d26a, #ff8fd1); --kbt-acctext: #2a1300; --kbt-edge: rgba(0, 0, 0, 0.6); --kbt-shadow: rgba(0, 0, 0, 0.6); }
[data-kbt='jade'] { --kbt-tray: linear-gradient(180deg, #bfe3cf, #97cfb2); --kbt-key: #f4fbf7; --kbt-key2: #e2f3ea; --kbt-text: #1f5a3f; --kbt-mod: #5fa784; --kbt-mod2: #4f9473; --kbt-modtext: #f4fbf7; --kbt-acc: #2f8a63; --kbt-acctext: #fff; --kbt-edge: rgba(31, 90, 63, 0.25); }
[data-kbt='terminal'] { --kbt-tray: linear-gradient(180deg, #0c0f0c, #050705); --kbt-key: #121812; --kbt-key2: #0e130e; --kbt-text: #39ff6a; --kbt-mod: #0f150f; --kbt-mod2: #0b100b; --kbt-modtext: #1fbf4a; --kbt-acc: #39ff6a; --kbt-acctext: #031a08; --kbt-edge: rgba(0, 0, 0, 0.7); --kbt-shadow: rgba(0, 0, 0, 0.7); }
[data-kbt='bubblegum'] { --kbt-tray: linear-gradient(120deg, #ffc1e3, #c9b8ff); --kbt-key: #fff7fc; --kbt-key2: #fde9f6; --kbt-text: #7a2a62; --kbt-mod: #ffd6ee; --kbt-mod2: #f7c4e4; --kbt-modtext: #8a3a72; --kbt-acc: #ff5fae; --kbt-acctext: #fff; --kbt-edge: rgba(122, 42, 98, 0.2); }
[data-kbt='gilded'] { --kbt-tray: linear-gradient(180deg, #2a2418, #1a160e); --kbt-key: #3b3220; --kbt-key2: #30291a; --kbt-text: #ffe6a3; --kbt-mod: #4a3d22; --kbt-mod2: #3d321b; --kbt-modtext: #e9c46a; --kbt-acc: linear-gradient(135deg, #ffe8a3, #c8922a); --kbt-acctext: #2a1c00; --kbt-edge: rgba(0, 0, 0, 0.55); --kbt-shadow: rgba(0, 0, 0, 0.55); }
[data-kbt='obsidian'] { --kbt-tray: linear-gradient(180deg, #121216, #08080b); --kbt-key: #1c1c22; --kbt-key2: #16161b; --kbt-text: #e8e8f0; --kbt-mod: #24242c; --kbt-mod2: #1c1c23; --kbt-modtext: #8e8ea0; --kbt-acc: linear-gradient(135deg, #8a5cff, #ff5c8a); --kbt-acctext: #fff; --kbt-edge: rgba(0, 0, 0, 0.7); --kbt-shadow: rgba(0, 0, 0, 0.7); }
[data-kbt='pixel'] { --kbt-tray: #2b2b4a; --kbt-key: #f4f1e6; --kbt-key2: #f4f1e6; --kbt-text: #2b2b4a; --kbt-mod: #8fd16a; --kbt-mod2: #8fd16a; --kbt-modtext: #1d3a12; --kbt-acc: #ff6b6b; --kbt-acctext: #fff; --kbt-edge: #11112a; }
[data-kbt='candy'] { --kbt-tray: linear-gradient(120deg, #ffe3ef, #e6e3ff 50%, #dcf6ff); --kbt-key: #fff; --kbt-key2: #fff7fb; --kbt-text: #6a3a7a; --kbt-mod: #ffd6e8; --kbt-mod2: #ffc6df; --kbt-modtext: #7a2f5a; --kbt-acc: #ff7ab6; --kbt-acctext: #fff; --kbt-edge: rgba(106, 58, 122, 0.2); }
[data-kbt='typewriter'] { --kbt-tray: linear-gradient(180deg, #2c2a26, #1d1c19); --kbt-key: #f3ecd8; --kbt-key2: #e3d9bd; --kbt-text: #26231c; --kbt-mod: #f3ecd8; --kbt-mod2: #e3d9bd; --kbt-modtext: #26231c; --kbt-acc: #b8342b; --kbt-acctext: #fff; --kbt-edge: #000; --kbt-shadow: rgba(0, 0, 0, 0.6); }
[data-kbt='rgb'] { --kbt-tray: #0c0c10; --kbt-key: #1c1c22; --kbt-key2: #141418; --kbt-text: #f4f4f8; --kbt-mod: #19191e; --kbt-mod2: #121216; --kbt-modtext: #b9b9c6; --kbt-acc: #24242c; --kbt-acctext: #fff; --kbt-edge: rgba(0, 0, 0, 0.7); --kbt-shadow: rgba(0, 0, 0, 0.7); }
[data-kbt='glass'] { --kbt-tray: linear-gradient(135deg, #a8d8ff, #c8b6ff 50%, #ffc6e0); --kbt-key: rgba(255, 255, 255, 0.42); --kbt-key2: rgba(255, 255, 255, 0.28); --kbt-text: #1f2b4a; --kbt-mod: rgba(255, 255, 255, 0.3); --kbt-mod2: rgba(255, 255, 255, 0.2); --kbt-modtext: #2c3a60; --kbt-acc: rgba(255, 255, 255, 0.55); --kbt-acctext: #1f2b4a; --kbt-edge: rgba(255, 255, 255, 0.6); --kbt-shadow: rgba(40, 60, 120, 0.18); }
[data-kbt='galaxy'] { --kbt-tray: linear-gradient(120deg, #0d0b2a, #231552, #0b1f4a, #2a0f4a, #0d0b2a); --kbt-key: #1f1a4a; --kbt-key2: #19153d; --kbt-text: #f1e9ff; --kbt-mod: #2a2063; --kbt-mod2: #211a52; --kbt-modtext: #b6a4ff; --kbt-acc: linear-gradient(135deg, #f7d26a, #ff8fd1); --kbt-acctext: #2a1300; --kbt-edge: rgba(0, 0, 0, 0.6); --kbt-shadow: rgba(0, 0, 0, 0.6); }

/* a tiny keyboard for previews: the case, three rows of caps, a return key */
.kbt-mini {
  position: relative;
  display: inline-block;
  width: 46px;
  height: 28px;
  border-radius: 5px;
  background: var(--kbt-tray);
  box-shadow: inset 0 0 0 1px var(--kbt-edge), 0 1px 2px rgba(0, 0, 0, 0.12);
  overflow: hidden;
}
.kbt-mini::before {
  content: '';
  position: absolute;
  inset: 4px 12px 4px 4px;
  background: repeating-linear-gradient(90deg, var(--kbt-key) 0 5px, transparent 5px 6.5px);
  -webkit-mask: repeating-linear-gradient(180deg, #000 0 5px, transparent 5px 7.5px);
  mask: repeating-linear-gradient(180deg, #000 0 5px, transparent 5px 7.5px);
}
.kbt-mini::after {
  content: '';
  position: absolute;
  top: 4px;
  right: 4px;
  width: 7px;
  height: 12px;
  border-radius: 1.5px;
  background: var(--kbt-acc);
}

/* ===================================================== 等级奖励 (look.js)
   五类：键盘皮肤（上面的 [data-kbt]）、按键音（sfx.js）、打字特效、过关庆祝、证书花纹。 */

/* 一件奖励的小图 */
.lk-item { display: inline-flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; }
.lk-item em { color: var(--text-muted); font-size: 12px; font-style: normal; line-height: 1.35; }
.lk-item .kbt-mini { width: 52px; height: 32px; }
.lk-pic { display: grid; place-items: center; width: 40px; height: 32px; font-size: 22px; line-height: 1; }
.lk-pic-sound { width: 34px; height: 34px; border-radius: 50%; background: var(--accent-soft, #eef4ff); color: var(--accent, #1f86d8); font-size: 17px; font-weight: 800; }
.lk-cert { display: block; width: 46px; height: 32px; border-radius: 4px; border: 3px double #c8922a; background: #fffaf0; }
.lk-cert[data-cert='cloud'] { background: radial-gradient(circle at 30% 60%, #f6e3b4 0 5px, transparent 6px), radial-gradient(circle at 70% 40%, #f6e3b4 0 4px, transparent 5px), #fffaf0; }
.lk-cert[data-cert='fret'] { border-style: solid; box-shadow: inset 0 0 0 2px #fffaf0, inset 0 0 0 4px #c8922a; }
.lk-cert[data-cert='dots'] { background: radial-gradient(#e2c27a 1px, transparent 1.5px) 0 0 / 6px 6px, #fffaf0; }
.lk-cert[data-cert='wave'] { background: repeating-radial-gradient(circle at 50% 120%, #fffaf0 0 3px, #cfe3f2 3px 4px); }
.lk-cert[data-cert='plaid'] { background: repeating-linear-gradient(0deg, transparent 0 5px, rgba(200, 146, 42, .18) 5px 6px), repeating-linear-gradient(90deg, transparent 0 5px, rgba(200, 146, 42, .18) 5px 6px), #fffaf0; }
.lk-cert[data-cert='bamboo'] { border-color: #5c8a4a; background: repeating-linear-gradient(90deg, #fffaf0 0 6px, #cfe2c0 6px 8px); }
.lk-cert[data-cert='blossom'] { border-color: #d98aa5; background: radial-gradient(circle at 75% 25%, #ffc2d4 0 4px, transparent 5px), radial-gradient(circle at 25% 70%, #ffd6e4 0 3px, transparent 4px), #fffaf0; }
.lk-cert[data-cert='starry'] { border-color: #2a2f6a; background: radial-gradient(#fff 0.8px, transparent 1.2px) 0 0 / 7px 7px, #1c2150; }
.lk-cert[data-cert='palace'] { border: 3px solid #b8892a; background: linear-gradient(#a8271f, #8c1d17); }
.lk-cert[data-cert='gold'] { border: 3px solid #c8922a; background: linear-gradient(135deg, #fff4cf, #f2d38a); }
.lk-cert[data-cert='shanshui'] { border-color: #555; background: radial-gradient(60% 50% at 30% 100%, #8a8a8a 60%, transparent 62%), radial-gradient(50% 60% at 75% 100%, #b5b5b5 60%, transparent 62%), #f7f5ee; }

/* 打字特效：从按对的那个字上冒出来 */
.lk-p { position: fixed; z-index: 60; pointer-events: none; font-style: normal; line-height: 1; white-space: nowrap; transform: translate(-50%, -50%); opacity: 0; will-change: transform, opacity; }
.lk-p.is-glow { filter: drop-shadow(0 0 6px #d8ff5a); }
.lk-p.is-glow-pink { filter: drop-shadow(0 0 6px rgba(255, 95, 143, 0.7)); }
.lk-p.is-glow-violet { filter: drop-shadow(0 0 6px rgba(123, 108, 240, 0.8)); }
.lk-p.is-flame { filter: blur(0.3px) drop-shadow(0 0 4px #ff9a2b); }
.lk-p.is-meteor { filter: drop-shadow(0 0 8px #ffcf5a); }
.lk-p.is-bolt { filter: drop-shadow(0 0 6px #ffd400); }
.lk-p.is-coin { filter: drop-shadow(0 1px 0 #b07c00); }
.lk-p.is-bubble { font-weight: 700; filter: drop-shadow(0 0 2px rgba(94, 194, 255, 0.6)); }
.lk-p.is-pixel { image-rendering: pixelated; }
.lk-p.is-label { font-family: var(--font-ui, inherit); font-weight: 800; text-shadow: 0 1px 0 #fff; }
.lk-ripple, .lk-ink { position: fixed; z-index: 60; pointer-events: none; width: 10px; height: 10px; border-radius: 50%; transform: translate(-50%, -50%); }
.lk-ripple { border: 2px solid #4fa8ff; animation: lk-ripple 0.6s ease-out forwards; }
.lk-ink { background: radial-gradient(circle, rgba(30, 30, 30, 0.55), rgba(30, 30, 30, 0) 70%); animation: lk-ink 0.7s ease-out forwards; }
@keyframes lk-ripple { to { width: 46px; height: 46px; opacity: 0; } }
@keyframes lk-ink { 0% { width: 6px; height: 6px; opacity: 0.9; } 100% { width: 40px; height: 40px; opacity: 0; } }
/* 字本身的动画在 look.js 里用 el.animate() 做（引擎会重写字的 className，class 动画会被冲掉） */
.lk-ripple.is-pond { border-color: #5ed2a8; border-width: 1.5px; }

/* 过关庆祝：一张铺满窗口、点不到的画布 */
.lk-celebrate { position: fixed; inset: 0; z-index: 70; width: 100vw; height: 100vh; pointer-events: none; }

/* 升级弹窗 */
.lk-up { position: fixed; inset: 0; z-index: 900; display: grid; place-items: center; padding: 16px; background: rgba(10, 18, 36, 0.5); backdrop-filter: blur(3px); animation: lk-fade 0.2s ease-out; }
.lk-up.is-out { opacity: 0; transition: opacity 0.18s; }
.lk-up-card { width: min(400px, 100%); padding: 24px 24px 16px; border-radius: var(--radius-lg, 20px); text-align: center; background: var(--bg-elevated, #fff);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25); animation: lk-pop 0.32s cubic-bezier(.2, 1.4, .4, 1); }
.lk-up-eye { margin: 0; color: var(--accent); font-size: 13px; font-weight: 700; letter-spacing: 0.2em; }
.lk-up-lv { margin: 6px 0 0; color: var(--text-strong); font: 800 52px/1 var(--font-sans); }
.lk-up-lv small { font-size: 22px; margin-right: 2px; color: var(--text-dim); }
.lk-up-title { margin: 6px 0 0; color: var(--text-muted); font-size: 14px; }
.lk-up-won { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 18px 0 4px; }
.lk-up-won .lk-item { width: 120px; padding: 12px 6px 10px; border: 1px solid var(--line); border-radius: 14px; }
.lk-up-acts { display: flex; justify-content: center; gap: 10px; margin-top: 16px; }
.lk-up-more { margin: 12px 0 0; font-size: 13px; }
.lk-up-more a { color: var(--text-dim); }
@keyframes lk-fade { from { opacity: 0; } }
@keyframes lk-pop { from { transform: scale(0.88); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .lk-up-card { animation: none; } }

/* ---- 账号菜单（2026-10-13 站长：图标对齐、内容更充分；收起后名字后面带会员标） */
.nav-avatar .pro-badge { width: 17px; height: 17px; margin: 0 0 0 -2px; flex: none; }
.nav-menu-account { min-width: 300px; padding: 8px; }
.nav-menu-head { align-items: flex-start; gap: 12px; padding: 8px 8px 10px; }
.nav-menu-avatar .avatar-fallback { width: 52px; height: 52px; font-size: 20px; }
.nav-menu-name { display: flex !important; align-items: center; gap: 6px; font-size: 15px !important; }
.nav-menu-name > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-menu-name .pro-badge { width: 18px; height: 18px; margin: 0; flex: none; }
.nav-menu-marks { display: flex !important; align-items: center; margin: 5px 0 3px; }
.nav-menu-email { display: block; color: var(--text-dim); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-menu-bio { margin: 0 8px 10px; padding: 8px 10px; border-radius: 10px; background: var(--bg-soft); color: var(--text-muted); font-size: 12.5px; line-height: 1.55; }
/* 等级：一整行可点，去「等级与奖励」 */
.nav-menu-level { display: block; margin: 0 4px 8px; padding: 10px 12px; border-radius: 12px; background: linear-gradient(120deg, var(--accent-soft, #eaf4ff), var(--bg-soft)); color: inherit; text-decoration: none; }
.nav-menu-level:hover { box-shadow: inset 0 0 0 1px var(--accent-line, #9cc8ef); }
.nml-top { display: flex; align-items: baseline; gap: 6px; }
.nml-top b { color: var(--accent-ink, var(--accent)); font: 800 16px/1.2 var(--font-sans); }
.nml-top > span { color: var(--text-strong); font-size: 13px; font-weight: 700; }
.nml-top em { margin-left: auto; color: var(--text-dim); font-size: 11.5px; font-style: normal; }
.nml-bar { display: block; height: 6px; margin: 7px 0 6px; border-radius: 99px; background: rgba(255, 255, 255, 0.8); overflow: hidden; }
.nml-bar i { display: block; height: 100%; border-radius: 99px; background: linear-gradient(90deg, #f2b632, #ffd66b); }
.nml-foot { display: flex; justify-content: space-between; color: var(--text-dim); font-size: 11.5px; }
.nml-foot span { color: var(--accent-ink, var(--accent)); font-weight: 600; }
.nav-menu-stats { gap: 6px; padding: 0 4px 6px; }
.nav-menu-stats > div, .nav-menu-stats > a { display: block; padding: 8px 10px; border-radius: 10px; background: var(--bg-soft); color: inherit; text-decoration: none; }
.nav-menu-stats > a.is-cta { background: #fff4d6; }
.nav-menu-stats b { font-family: var(--font-sans); font-size: 15px; font-weight: 800; }
.nav-menu-stats b small { margin-left: 1px; color: var(--text-dim); font-size: 11px; font-weight: 600; }
.nav-menu-stats b.is-text { font-size: 13.5px; }
/* 菜单项：图标放在同样大小的格子里，全部对齐 */
.nav-menu-account .nav-menu-item { gap: 12px; padding: 8px 10px; }
.nav-menu-account .nav-menu-item svg { box-sizing: content-box; width: 16px; height: 16px; padding: 6px; border-radius: 8px; background: var(--bg-soft); color: var(--text-muted); }
.nav-menu-account .nav-menu-item:hover svg { background: var(--bg-elevated); color: var(--accent); }
.nav-menu-account .nav-menu-item.is-danger svg { background: var(--red-soft, #fdecec); color: currentColor; }

/* ---- 今日面板（daily.js banner，2026-10-13）：每日挑战 + 每日任务，左下角；能收成药丸、能收成小圆钮 */
.td-dock { padding: 0; width: min(360px, calc(100vw - 40px)); display: block; }
.td-dock .td-full { padding: 14px 14px 12px; }
.td-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding-right: 30px; }
.td-head b { color: var(--text-strong); font-size: 14.5px; }
.td-head span { color: var(--text-dim); font-size: 12px; }
.td-min { margin-left: auto; display: grid; place-items: center; width: 26px; height: 26px; border: 0; border-radius: 8px; background: transparent; color: var(--text-dim); cursor: pointer; }
.td-min:hover { background: var(--bg-soft); }
.td-min svg { width: 16px; height: 16px; }
.td-dock .dc-x { top: 12px; }
.td-sec { padding: 10px; border-radius: 14px; background: var(--bg-soft); }
.td-sec + .td-sec { margin-top: 8px; }
.td-daily { display: flex; gap: 10px; align-items: flex-start; }
.td-daily b, .td-sub b { color: var(--text-strong); font-size: 13.5px; }
.td-daily b em, .td-row em { margin-left: 6px; padding: 1px 7px; border-radius: 99px; background: #fff3cf; color: #9a6a00; font-size: 11.5px; font-style: normal; font-weight: 800; }
.td-daily small, .td-sub small { display: block; color: var(--text-dim); font-size: 12px; }
.td-daily .dc-banner-acts { margin-top: 8px; }
.td-daily.is-done .dc-banner-ico { background: var(--green-ink, #2f9e44); color: #fff; }
.td-sub { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.td-row { display: flex; align-items: center; gap: 8px; padding: 6px 4px; border-radius: 8px; color: var(--text); font-size: 13px; text-decoration: none; }
.td-row:hover { background: var(--bg-elevated); }
.td-row i { display: grid; place-items: center; flex: none; width: 18px; height: 18px; border: 2px solid var(--line); border-radius: 50%; }
.td-row i svg { width: 12px; height: 12px; }
.td-row.is-done { color: var(--text-dim); }
.td-row.is-done i { border-color: var(--green-ink, #2f9e44); background: var(--green-ink, #2f9e44); color: #fff; }
.td-row.is-done em { background: transparent; color: var(--text-dim); text-decoration: line-through; }
.td-row em { margin-left: auto; }
/* 药丸 / 小圆钮 */
.td-pill { display: none; align-items: center; gap: 10px; width: 100%; padding: 8px 14px 8px 8px; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.td-ring { --p: 0; flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
  background: radial-gradient(closest-side, var(--bg-elevated) 76%, transparent 78%), conic-gradient(#f0a830 calc(var(--p) * 1%), var(--bg-sunken, #eef2f7) 0); color: #c98a10; }
.td-ring svg { width: 16px; height: 16px; }
.td-pill-text { display: grid; line-height: 1.2; }
.td-pill-text b { color: var(--text-strong); font-size: 13.5px; }
.td-pill-text small { color: var(--text-dim); font-size: 11.5px; }
.td-dock.is-mini, .td-dock.is-dot { width: auto; padding: 0; border-radius: 99px; }
.td-dock.is-mini .td-full, .td-dock.is-dot .td-full { display: none; }
.td-dock.is-mini .td-pill, .td-dock.is-dot .td-pill { display: flex; }
.td-dock.is-dot .td-pill { padding: 6px; }
.td-dock.is-dot .td-pill-text { display: none; }
.td-dock.is-mini .dc-x, .td-dock.is-dot .dc-x { display: none; }
@media (max-width: 560px) { .td-dock.is-mini, .td-dock.is-dot { right: auto; } }


/* ============================================================ guest mode
   The slide-in invitation (bottom-left: 小一 the companion owns the right
   corner) and the inline card used on results, the leaderboard and profile. */

.guest-card {
  position: fixed;
  z-index: 60;
  left: 20px;
  bottom: 20px;
  width: min(340px, calc(100vw - 32px));
  padding: 16px 18px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 18px;
  background: var(--bg-elevated);
  box-shadow: 0 24px 48px -18px rgba(16, 24, 40, 0.32), 0 2px 6px rgba(16, 24, 40, 0.06);
  opacity: 0;
  transform: translateY(18px) scale(0.97);
  transform-origin: bottom left;
  transition: opacity 0.28s var(--ease), transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.guest-card.is-in { opacity: 1; transform: none; }
.guest-card-top { display: flex; align-items: center; gap: 11px; margin-bottom: 8px; padding-right: 18px; }
.guest-buddy {
  display: grid;
  flex: none;
  place-items: center;
  width: 42px; height: 42px;
  border-radius: 13px;
  background: var(--accent-tint);
  animation: guest-hop 2.6s var(--ease) 0.5s infinite;
}
.guest-buddy svg { width: 30px; height: 30px; }
.guest-card-top b { display: block; color: var(--text-strong); font-size: 15px; font-weight: var(--fw-bold); line-height: 1.3; }
.guest-tag {
  display: inline-block;
  margin-top: 3px;
  padding: 0 7px;
  border-radius: 9999px;
  background: var(--bg-sunken);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  line-height: 18px;
}
.guest-card p { margin: 0 0 12px; color: var(--text); font-size: 13.5px; line-height: 1.65; }
.guest-card p b { color: var(--text-strong); }
.guest-card-actions { display: flex; align-items: center; gap: 8px; }
.guest-login { display: inline-block; margin-top: 9px; color: var(--text-dim); font-size: 12px; }
.guest-login:hover { color: var(--accent-ink); }
.guest-x {
  position: absolute;
  top: 8px; right: 10px;
  width: 26px; height: 26px;
  border: 0; border-radius: 8px;
  background: none;
  color: var(--text-dim);
  font-size: 18px; line-height: 1;
  cursor: pointer;
}
.guest-x:hover { background: var(--bg-sunken); color: var(--text-strong); }
@keyframes guest-hop {
  0%, 70%, 100% { transform: none; }
  78% { transform: translateY(-5px) rotate(-4deg); }
  86% { transform: translateY(0) rotate(2deg); }
}
@media (max-width: 640px) {
  .guest-card { left: 16px; right: 16px; bottom: 16px; width: auto; }
}

.guest-invite {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--gold-500) 45%, transparent);
  border-radius: 14px;
  background: linear-gradient(100deg, var(--gold-100), color-mix(in srgb, var(--gold-100) 35%, var(--bg-elevated)));
  text-align: left;
}
.guest-invite-ico {
  display: grid;
  flex: none;
  place-items: center;
  width: 38px; height: 38px;
  border-radius: 12px;
  background: var(--bg-elevated);
  color: var(--gold-ink);
  box-shadow: var(--shadow-sm);
}
.guest-invite-ico svg { width: 19px; height: 19px; }
.guest-invite-text { flex: 1; min-width: 0; }
.guest-invite-text b { display: block; color: var(--text-strong); font-size: 14px; font-weight: var(--fw-bold); }
.guest-invite-text span { display: block; margin-top: 2px; color: var(--gold-ink); font-size: 12.5px; line-height: 1.5; opacity: 0.9; }
.guest-invite .btn { flex: none; }
@media (max-width: 560px) {
  .guest-invite { flex-wrap: wrap; }
  .guest-invite .btn { width: 100%; justify-content: center; }
}
html[data-theme='dark'] .guest-invite { background: color-mix(in srgb, var(--gold-500) 12%, var(--bg-elevated)); }
html[data-theme='dark'] .guest-invite-text span { color: var(--gold-300); }

@media (prefers-reduced-motion: reduce) {
  .guest-card { transition: opacity 0.2s; transform: none; }
  .guest-buddy { animation: none; }
}

/* the 今日 dock (daily.js) sits in the same corner: the card goes above it */
body:has(.td-dock) .guest-card { bottom: 84px; }

/* ============================================================ 会员奖励线 (store.js MEMBER_TRACK)
   Six keyboards, a halo effect and two certificate previews; the animated parts are in keyboard.css. */
[data-kbt='m-aurora'] { --kbt-tray: linear-gradient(120deg, #0b2a3a, #12405a, #2a1550, #0f4a4a, #0b2a3a); --kbt-key: #14324a; --kbt-key2: #10283d; --kbt-text: #b6fff0; --kbt-mod: #1d2550; --kbt-mod2: #181f45; --kbt-modtext: #d3c6ff; --kbt-acc: linear-gradient(135deg, #3ee0b5, #b06cf0); --kbt-acctext: #06221c; --kbt-edge: rgba(0, 0, 0, 0.55); --kbt-shadow: rgba(0, 0, 0, 0.6); }
[data-kbt='m-neon'] { --kbt-tray: #0b0716; --kbt-key: #170f2a; --kbt-key2: #120b22; --kbt-text: #ff6bd5; --kbt-mod: #120c24; --kbt-mod2: #0e091c; --kbt-modtext: #6ff3ff; --kbt-acc: linear-gradient(135deg, #ff4fd8, #3ff0ff); --kbt-acctext: #12051f; --kbt-edge: rgba(0, 0, 0, 0.7); --kbt-shadow: rgba(0, 0, 0, 0.7); }
[data-kbt='m-sakura'] { --kbt-tray: radial-gradient(circle at 20% 30%, #ffd1e0 0 3px, transparent 4px) 0 0 / 46px 38px, radial-gradient(circle at 70% 70%, #ffbcd2 0 2.5px, transparent 3.5px) 0 0 / 34px 30px, linear-gradient(180deg, #ffe4ee, #f8c3d6); --kbt-key: #fffafc; --kbt-key2: #ffeef4; --kbt-text: #8a2f55; --kbt-mod: #ffd9e6; --kbt-mod2: #fbc6d8; --kbt-modtext: #9a3a62; --kbt-acc: #f06a9a; --kbt-acctext: #fff; --kbt-edge: rgba(138, 47, 85, 0.2); }
[data-kbt='m-goldflow'] { --kbt-tray: linear-gradient(110deg, #2a2112 0%, #4a3a18 40%, #f3d37a 50%, #4a3a18 60%, #2a2112 100%); --kbt-key: #3a2f18; --kbt-key2: #2f2613; --kbt-text: #ffe7a3; --kbt-mod: #4a3c1c; --kbt-mod2: #3d3117; --kbt-modtext: #f0c95c; --kbt-acc: linear-gradient(135deg, #fff0b8, #d09a2a); --kbt-acctext: #2a1c00; --kbt-edge: rgba(0, 0, 0, 0.55); --kbt-shadow: rgba(0, 0, 0, 0.55); }
[data-kbt='m-space'] { --kbt-tray: radial-gradient(#fff 0.8px, transparent 1.3px) 0 0 / 23px 19px, radial-gradient(rgba(255, 255, 255, 0.7) 0.6px, transparent 1px) 7px 11px / 31px 27px, radial-gradient(ellipse at 30% 20%, #1e2a5e, #05060f 70%); --kbt-key: #121633; --kbt-key2: #0d1027; --kbt-text: #e6ecff; --kbt-mod: #171b3d; --kbt-mod2: #11142f; --kbt-modtext: #8fa3ff; --kbt-acc: linear-gradient(135deg, #8fa3ff, #f7d26a); --kbt-acctext: #0b0f2a; --kbt-edge: rgba(0, 0, 0, 0.7); --kbt-shadow: rgba(0, 0, 0, 0.7); }
[data-kbt='m-holo'] { --kbt-tray: linear-gradient(120deg, #ffd6f2, #c9e9ff, #d8ffe9, #fff2c4, #e6d4ff, #ffd6f2); --kbt-key: rgba(255, 255, 255, 0.72); --kbt-key2: rgba(255, 255, 255, 0.55); --kbt-text: #3b2f6a; --kbt-mod: rgba(255, 255, 255, 0.5); --kbt-mod2: rgba(255, 255, 255, 0.38); --kbt-modtext: #4a3d80; --kbt-acc: linear-gradient(135deg, #ff9ad5, #8fd3ff, #b8ffb0); --kbt-acctext: #2a2050; --kbt-edge: rgba(255, 255, 255, 0.8); --kbt-shadow: rgba(80, 60, 160, 0.2); }
.kbt-mini[data-kbt='m-aurora'], .kbt-mini[data-kbt='m-holo'], .kbt-mini[data-kbt='m-goldflow'] { background-size: 300% 300%; animation: kbt-flow 6s ease-in-out infinite; }

.lk-halo { position: fixed; z-index: 60; pointer-events: none; width: 8px; height: 8px; border-radius: 50%; transform: translate(-50%, -50%);
  border: 2px solid #f5c542; box-shadow: 0 0 10px 2px rgba(245, 197, 66, 0.6), inset 0 0 6px rgba(245, 197, 66, 0.5); animation: lk-halo 0.72s ease-out forwards; }
@keyframes lk-halo { to { width: 54px; height: 54px; opacity: 0; } }

.lk-cert[data-cert='m-goldstar'] { border: 3px solid #d4a53a; background: radial-gradient(#ffe08a 1px, transparent 1.6px) 2px 3px / 9px 8px, linear-gradient(135deg, #0f1638, #1f2a66); }
.lk-cert[data-cert='m-aurora'] { border-color: #3ee0b5; background: linear-gradient(160deg, #0b2a3a, #1d5a6a 40%, #6b4fd8 70%, #0b1f3a); }
@keyframes kbt-flow { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
/* 升级弹窗：不是会员时，这一级会员还能拿什么 */
.lk-up-member { display: flex; align-items: center; gap: 10px; margin: 12px 0 0; padding: 8px 12px; border-radius: 12px; background: #1f1a12; color: #f6e7c0; font-size: 12.5px; text-align: left; }
.lk-up-member .lk-item { flex: 0 0 auto; }
.lk-up-member a { color: #ffe7a3; font-weight: 700; }

/* 意见反馈（2026-10-14，ui.js openFeedback）：页脚一个链接 + 一个 kit 卡片小窗 */
.foot-legal .foot-fb { padding: 0; border: 0; background: none; color: var(--text-muted); font: inherit; font-size: 12.5px; cursor: pointer; }
.foot-legal .foot-fb:hover { color: var(--accent-ink, var(--accent)); text-decoration: underline; text-underline-offset: 3px; }
.fb-veil { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 16px; background: rgba(10, 18, 36, 0.45); backdrop-filter: blur(2px); }
.fb-box { width: min(480px, 100%); max-height: calc(100vh - 32px); overflow: auto; box-shadow: var(--shadow-lg, 0 20px 50px rgba(0,0,0,.25)); }
.fb-x { flex: 0 0 auto; width: 32px; height: 32px; border: 0; border-radius: 8px; background: transparent; color: var(--text-dim); font-size: 22px; line-height: 1; cursor: pointer; }
.fb-x:hover { background: var(--bg-sunken); color: var(--text-strong); }
.fb-text, .fb-input { width: 100%; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-ctl, 10px); background: var(--bg-elevated); color: var(--text-strong); font: inherit; font-size: 14px; resize: vertical; }
.fb-text:focus, .fb-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint); }
.fb-msg { margin: 12px 0 0; color: var(--red-ink); font-size: 13px; }
.fb-acts { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }

/* 顶栏「今日」按钮（daily.js，2026-10-16）：一圈进度；点开面板挂在它下面 */
.nav-today { position: relative; cursor: pointer; }
.nav-today[hidden] { display: none; }
.nav-today .td-ring { width: 26px; height: 26px; background: radial-gradient(closest-side, var(--bg-elevated) 72%, transparent 75%), conic-gradient(#f0a830 calc(var(--p) * 1%), var(--bg-sunken, #eef2f7) 0); }
.nav-today .td-ring svg { width: 13px; height: 13px; }
.nav-today.is-bump { animation: tdBump .6s ease .2s; }
@keyframes tdBump { 0%, 100% { transform: none; } 40% { transform: scale(1.22); } 70% { transform: scale(.94); } }
.dc-banner.is-pop { left: auto; bottom: auto; right: 20px; top: 70px; z-index: 950; }
.dc-banner.is-pop:not(.is-in) { transform: translateY(-8px); }
@media (max-width: 560px) { .dc-banner.is-pop { left: 12px; right: 12px; } }
@media (prefers-reduced-motion: reduce) { .nav-today.is-bump { animation: none; } }

/* 意见反馈 · 工单（2026-10-16）：菜单里的未读数、头像上的小红点、反馈小窗里自动附上的出错信息 */
.nav-avatar { position: relative; }
.nav-avatar-dot { position: absolute; top: 2px; left: 26px; width: 10px; height: 10px; border-radius: 50%; background: #eb5757; box-shadow: 0 0 0 2px var(--bg-elevated); }
.nav-menu-badge { margin-left: auto; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: #eb5757; color: #fff; font-size: 11.5px; font-weight: 800; line-height: 20px; text-align: center; }
.fb-detail { margin: 10px 0 0; padding: 8px 10px; border-radius: 10px; background: var(--bg-soft); color: var(--text-dim); font: 12px/1.5 ui-monospace, Menlo, monospace; overflow-wrap: anywhere; }
.fb-box .ui-card-sub a { color: var(--accent); }

/* 手机提醒的一行版（phone.js card，2026-10-16） */
.ph-banner.is-compact { display: flex; align-items: center; gap: 10px; padding: 10px 8px 10px 12px; }
.ph-banner.is-compact .ph-txt { flex: 1 1 auto; min-width: 0; }
.ph-banner.is-compact .ph-txt b { font-size: 14px; }
.ph-banner.is-compact .ph-txt span { font-size: 12.5px; line-height: 1.4; }
.ph-banner.is-compact .btn { flex: none; min-height: 34px; }
.ph-banner.is-compact .ph-x { position: static; flex: none; }

/* 手机上点得到（2026-10-16）：顶栏的几个圆图标在手机上只有 30px，手指不好点。
   样子不变，只把能点的范围往外扩到 40px 左右。 */
@media (pointer: coarse) {
  .nav-icon-btn { position: relative; }
  .nav-icon-btn::after { content: ''; position: absolute; inset: -5px; }
}

/* 课堂时间给游客的一条（guest.js classBar） */
.class-bar { position: fixed; left: 0; right: 0; z-index: 900; border-bottom: 1px solid var(--line-soft); background: color-mix(in srgb, var(--accent) 7%, var(--bg-elevated)); box-shadow: 0 6px 14px -12px rgba(16, 24, 40, .4); }
body.has-class-bar #main { margin-top: 52px; }
.class-bar-in { display: flex; align-items: center; gap: 14px; min-height: 52px; }
.class-bar-ico svg { width: 20px; height: 20px; color: var(--accent); }
.class-bar-form { display: flex; align-items: center; gap: 8px; color: var(--text-strong); font-size: 14px; white-space: nowrap; }
.class-bar-form input { width: 110px; height: 32px; padding: 0 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-elevated); font: 700 14px/1 var(--font-mono); letter-spacing: .1em; text-transform: uppercase; }
.class-bar-teacher { margin-left: auto; color: var(--text-muted); font-size: 13px; white-space: nowrap; }
.class-bar-teacher:hover { color: var(--accent); }
.class-bar-x { width: 28px; height: 28px; border: 0; border-radius: 8px; background: none; color: var(--text-dim); font-size: 18px; cursor: pointer; }
.class-bar-x:hover { background: var(--bg-soft); }
.class-bar .btn { height: 32px; min-height: 0; padding: 0 14px; box-shadow: none; }
