@font-face {
  font-family: 'ShadowpacketNotoSansSC';
  src: url('fonts/NotoSansCJKsc-Shadowpacket.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}

:root {
  --brand: #2879bd;
  --hover: #ff6600;
  --bg: #ffffff;
  --band: #f5f6f8;
  --fg: #111111;
  --muted: #6b7280;
  --line: #e3e5e9;
  --card: #ffffff;
  --link: #2879bd;
  --code-bg: rgba(0, 0, 0, 0.075);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14161a;
    --band: #1a1d22;
    --fg: #e8eaed;
    --muted: #9aa1ac;
    --line: #2a2e35;
    --card: #1b1e23;
    --link: #6c9bff;
    --code-bg: rgba(255, 255, 255, 0.09);
  }
}

* { box-sizing: border-box; }

/* The `hidden` attribute is a UA rule of the lowest specificity, so any class
   that sets `display` beats it -- `.layout { display: grid }` did, and the
   admin console rendered underneath its own login gate. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  /* Both pages are ordinary documents now, so they centre against the same
     width -- but only if they agree on whether a scrollbar is there. The home
     page is short enough not to scroll on a tall window and the FAQ always
     scrolls, so the scrollbar is always reserved and the brand logo does not
     move between them. */
  overflow-y: scroll;
}

body {
  margin: 0;
  /* The footer sits at the bottom of the window when the content is shorter
     than it, which is what the Flutter version did with a Spacer inside a
     viewport-height box. Without this the footer's rule lands right under the
     content and the empty page below it reads as an enormous footer. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--fg);
  font-family: 'ShadowpacketNotoSansSC', -apple-system, BlinkMacSystemFont,
    'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
    'Noto Sans CJK SC', sans-serif;
  font-size: 16px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

.width {
  /* `width` as well as `max-width`, because `margin: 0 auto` on a flex item
     turns off the stretch it would otherwise get from the body's column flex
     and leaves it shrink-to-fit. The admin console then measured its own
     content: the wide download tables filled the 1120, the one line of the
     empty Bug Reporter did not, and the sidebar slid to the middle of the
     window every time the view changed. */
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 24px;
}

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

.topbar {
  height: 64px;
  background: var(--brand);
  position: sticky;
  top: 0;
  z-index: 10;
}

.topbar .width {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  color: #fff;
  text-decoration: none;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.19px;
}

.brand img { width: 30px; height: 30px; display: block; }

/* An <a> on the public pages and a <button> on the admin one, so it states
   what a button does not inherit: the page's font, and no border. */
.topnav {
  color: #fff;
  font-family: inherit;
  text-decoration: none;
  border: none;
  cursor: pointer;
  font-size: 15px;
  padding: 7px 16px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.22);
  transition: background 150ms;
}

.topnav:hover { background: rgba(255, 255, 255, 0.3); }

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

.layout {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: 40px;
  /* Horizontal padding repeated from .width. This element carries both
     classes, and a `padding` shorthand here silently zeroed the 24px, so the
     article sat 24px left of the top bar's own logo -- and 24px left of where
     the home page puts its content. */
  padding: 44px 24px 80px;
}

/* The sticky sidebar CSS gives for free is the whole reason this page is not
   drawn by Flutter: keeping a nav in place beside a scrolling article was a
   fight there and is one declaration here. */
.sidebar {
  position: sticky;
  top: 108px;
  align-self: start;
  /* 108 above, plus the pinned footer and a little air below. */
  max-height: calc(100vh - 176px);
  overflow-y: auto;
}

.sidebar .label {
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  padding: 0 0 10px 12px;
}

.sidebar a {
  display: block;
  border-left: 2px solid var(--line);
  padding: 6px 10px 6px 12px;
  color: var(--muted);
  text-decoration: none;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.5;
}

.sidebar a.sub {
  padding-left: 24px;
  font-size: 13.5px;
  font-weight: 400;
}

.sidebar a:hover { color: var(--fg); }

.sidebar a.active {
  color: var(--link);
  border-left-color: var(--link);
  font-weight: 600;
}

/* ---- article ---- */

h1 {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.26px;
  margin: 0 0 10px;
}

.lede { color: var(--muted); margin: 0 0 34px; }

h2 {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.2px;
  margin: 44px 0 14px;
  scroll-margin-top: 88px;
}

h3 {
  font-size: 15.5px;
  font-weight: 700;
  margin: 30px 0 12px;
  scroll-margin-top: 88px;
}

h4 {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--muted);
  margin: 24px 0 10px;
}

h2:first-of-type { margin-top: 0; }

p { margin: 0 0 14px; }

ul, ol { margin: 0 0 14px; padding-left: 22px; }
ol { padding-left: 24px; }
li { margin-bottom: 8px; }
li::marker { color: var(--muted); }

/* Click-by-click instructions: numbered, roomier, and the taps stand out. */
ol.steps { margin: 0 0 16px; }
ol.steps li { margin-bottom: 10px; }
ol.steps b {
  font-weight: 600;
  background: var(--code-bg);
  border-radius: 3px;
  padding: 1px 6px;
}

a { color: var(--link); }

code {
  /* The CJK subset sits after the monospace faces, not before: Latin and the
     alignment of the configuration samples stay monospaced, and the Chinese
     placeholders inside them stop being boxes. Consolas and friends carry no
     CJK at all, so without this the subset never got a chance. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas,
    'ShadowpacketNotoSansSC', monospace;
  font-size: 13.5px;
  background: var(--code-bg);
  border-radius: 3px;
  padding: 1px 4px;
}

pre {
  background: var(--band);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
  margin: 0 0 14px;
  overflow-x: auto;
}

pre code {
  background: none;
  padding: 0;
  font-size: 13.5px;
  line-height: 1.5;
}

.table-scroll { overflow-x: auto; margin: 0 0 14px; }

table {
  border-collapse: collapse;
  width: 100%;
  min-width: 620px;
}

th, td {
  text-align: left;
  vertical-align: top;
  padding: 9px 10px;
  border-bottom: 1px solid var(--line);
  line-height: 1.5;
}

th { color: var(--muted); font-size: 13px; font-weight: 600; }
td { font-size: 14.5px; }

/* Collapsed by default: the nginx configuration is 120 lines and would bury the
   rest of the chapter, but it is the one thing a reader cannot reconstruct. */
details {
  border: 1px solid var(--line);
  border-radius: 8px;
  margin: 0 0 14px;
  background: var(--card);
}

details > summary {
  cursor: pointer;
  padding: 11px 16px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--link);
  list-style: none;
}

details > summary::-webkit-details-marker { display: none; }

details > summary::before {
  content: "▸";
  display: inline-block;
  width: 1.2em;
  color: var(--muted);
}

details[open] > summary::before { content: "▾"; }

details[open] > summary { border-bottom: 1px solid var(--line); }

details > summary:hover { color: var(--hover); }

/* The hint has to say which way it will go, so it flips with the state. */
details > summary::after {
  content: "（点击展开）";
  color: var(--muted);
  font-weight: 400;
}

details[open] > summary::after { content: "（点击折叠）"; }

details .body { padding: 14px 16px 2px; }

details .body > pre:last-child { margin-bottom: 12px; }

/* A callout: the thing a Markdown page cannot say without inventing syntax. */
.note {
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  background: var(--card);
  border-radius: 8px;
  padding: 14px 18px;
  margin: 0 0 14px;
}

.note.warn { border-left-color: var(--hover); }

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

.note .head {
  font-weight: 700;
  font-size: 14px;
  margin-bottom: 6px;
}

.toc { display: none; }

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

body > main,
body > .layout { flex: 1 0 auto; }

footer {
  flex-shrink: 0;
  /* Pinned like the top bar, so it is visible without scrolling 18,000px to
     the end of the FAQ. Sticky rather than fixed: it still occupies its own
     space at the true bottom, so nothing is left hidden underneath it there. */
  position: sticky;
  bottom: 0;
  z-index: 9;
  background: var(--bg);
  border-top: 1px solid var(--line);
  color: var(--muted);
  /* 16px/1.7 with 10px above and below, which is what the Flutter version
     measured: the 13px/20px it briefly had here stood 15px taller. */
  font-size: 16px;
  line-height: 1.7;
  padding: 10px 0;
  text-align: center;
}

/* A three-column grid, so the copyright stays where it was: `space-between`
   would have shifted it left by the width of the control beside it. The empty
   first column is what balances the third. The row is the full window rather
   than the page's content column, so the control reaches the right edge. */
.footer-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
}

.footer-row > span { grid-column: 2; }

.footer-contact {
  grid-column: 3;
  justify-self: end;
  /* A button, because it opens a dialog rather than going anywhere; styled as
     the link it replaced so the bar reads the same. */
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--link);
  font-weight: 600;
  cursor: pointer;
}

.footer-contact:hover { text-decoration: underline; }

/* ---- feedback dialog ---- */

.feedback-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(15, 23, 42, 0.45);
}

.feedback {
  width: min(560px, 100%);
  max-height: 100%;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.25);
}

.feedback h2 { font-size: 20px; margin: 0 0 8px; }

.feedback-hint { color: var(--muted); margin: 0 0 16px; font-size: 14px; }

.feedback textarea {
  display: block;
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  font: inherit;
  color: inherit;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
}

.feedback textarea:focus { outline: 2px solid var(--link); outline-offset: -1px; }

.feedback-shots {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 14px;
}

.feedback-shot {
  position: relative;
  width: 120px;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}

.feedback-shot img { display: block; width: 100%; height: 90px; object-fit: cover; }

.feedback-shot span {
  display: block;
  padding: 4px 6px;
  font-size: 12px;
  color: var(--muted);
}

/* Pinned to the thumbnail rather than sitting under it, so removing one image
   never moves the other. */
.feedback-shot button {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 11px;
  background: rgba(15, 23, 42, 0.72);
  color: #fff;
  font-size: 14px;
  line-height: 22px;
  padding: 0;
  cursor: pointer;
}

.feedback-status { margin: 14px 0 0; font-size: 14px; color: var(--hover); }

.feedback-status.ok { color: var(--muted); }

.feedback-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 18px;
}

.feedback-spacer { flex: 1 1 auto; }

.feedback-actions button {
  font: inherit;
  border-radius: 8px;
  padding: 8px 16px;
  cursor: pointer;
  border: 1px solid var(--line);
  background: var(--bg);
  color: inherit;
}

.feedback-actions button:disabled { opacity: 0.55; cursor: default; }

.feedback-send {
  border-color: var(--link);
  background: var(--link);
  color: #fff;
  font-weight: 600;
}

/* ---- narrow ---- */

@media (max-width: 900px) {
  .width { padding: 0 16px; }
  .layout { grid-template-columns: minmax(0, 1fr); gap: 0; padding-top: 32px; }
  .sidebar { display: none; }
  .toc {
    display: block;
    background: var(--band);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 14px 18px;
    margin: 0 0 28px;
  }
  .toc a { color: var(--link); text-decoration: none; margin-right: 18px; font-weight: 600; }
  .toc a:hover { text-decoration: underline; }
  table { min-width: 560px; }
}

/* ---- home page ---- */

.home {
  /* One rhythm for the whole page instead of hand-placed gaps. The Flutter
     version spaced its two sections with a constant and a Spacer, so the gap
     grew with the window and the footer floated. */
  padding: 44px 24px 72px;
  display: flex;
  flex-direction: column;
  gap: 44px;
}

.notice {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 26px 32px;
}

/* The lead is an `h1`. Its own size and weight, so the document has a heading
   without the copy growing to a browser default 2em bold. */
.intro-lead {
  margin: 0 0 14px;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}

.intro-list { margin: 0; padding-left: 22px; }
.intro-list li { margin-bottom: 10px; }
.intro-list li:last-child { margin-bottom: 0; }
.intro-list li::marker { color: var(--muted); }

.intro-tail { margin: 14px 0 0; }

.downloads h2 {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.2px;
  margin: 0 0 16px;
}

/* auto-fill, not auto-fit: six cards keep their size on a wide window rather
   than stretching into slabs. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 16px;
}

.card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-height: 184px;
  padding: 26px 16px 20px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--fg);
  font-family: inherit;
  cursor: pointer;
  transition: background 150ms, border-color 150ms, color 150ms;
}

.card[disabled] { opacity: 0.5; cursor: default; }

.card:not([disabled]):hover {
  background: var(--hover);
  border-color: var(--hover);
  color: #fff;
}

.card-icon { display: block; width: 42px; height: 42px; }
.card-icon svg { width: 42px; height: 42px; display: block; }

.card-name { font-size: 15px; font-weight: 500; line-height: 1.7; }

.card-action { font-size: 14px; font-weight: 600; color: var(--link); line-height: 1.7; }
/* 暂未提供 and 暂未上架 say the same kind of thing -- that there is nothing to
   click yet -- so they read the same, and so does the card around them: a
   store card carries `disabled` exactly like a platform whose file has not
   been published, and greys out through the one rule above rather than through
   a second one of its own. */
.card[disabled] .card-action {
  color: var(--muted);
  font-size: 13px;
  font-weight: 400;
}
.card:not([disabled]):hover .card-action { color: #fff; }

.card-size { font-size: 12px; color: var(--muted); line-height: 1.7; }
.card:not([disabled]):hover .card-size { color: #fff; }

.grid-state { color: var(--muted); margin: 16px 0 0; }

@media (max-width: 900px) {
  .home { padding: 32px 16px 56px; gap: 32px; }
  .notice { padding: 20px; }
}

/* ---- backend admin ---- */

.login {
  flex: 1 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 44px 24px;
}

.login-card {
  width: 100%;
  max-width: 420px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 28px 32px;
}

.login-card input {
  width: 100%;
  box-sizing: border-box;
  font-family: inherit;
  font-size: 15px;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 14px;
}

.login-card input:focus { outline: 2px solid var(--brand); outline-offset: -1px; }

/* Present for the password manager, not for the reader. */
.offscreen {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
}

.login-error { color: var(--hover); font-size: 14px; margin: 0 0 16px; }

.login-remember {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: 14px;
  margin: 0 0 16px;
  cursor: pointer;
  user-select: none;
}

/* The rule above sizes the token box; a checkbox wants none of it. */
.login-remember input {
  width: auto;
  margin: 0;
  accent-color: var(--brand);
}

.login-submit {
  width: 100%;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  color: #fff;
  background: var(--brand);
  border: none;
  border-radius: 8px;
  padding: 11px;
  cursor: pointer;
}

.login-submit:hover { background: var(--hover); }
.login-submit[disabled] { opacity: 0.6; cursor: default; }

/* The console reuses the FAQ's two-pane shell, so the navigation is on the
   left and behaves the same on a narrow screen. */
.admin main h1 { font-size: 26px; font-weight: 700; margin: 0 0 20px; }
.admin main h2 { font-size: 17px; margin: 30px 0 12px; }
.admin main h2:first-of-type { margin-top: 0; }

.admin td.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
}

/* The feedback detail's image strip. Scaled down to a readable thumbnail;
   each one links to itself, so a screenshot can be opened full size. */
.feedback-images {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.feedback-images img {
  display: block;
  max-width: 240px;
  max-height: 180px;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.admin-error { color: var(--hover); margin: 0 0 16px; }
.admin-empty { color: var(--muted); }

/* What a table cannot say for itself: how the number above it was counted. */
.admin-note {
  color: var(--muted);
  margin: -8px 0 24px;
  max-width: 70ch;
}

.nav-action {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  border-left: 2px solid transparent;
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 13.5px;
  padding: 6px 10px 6px 12px;
  margin-top: 2px;
  cursor: pointer;
}

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

.row-action {
  font-family: inherit;
  font-size: 13px;
  color: var(--link);
  background: none;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 4px 10px;
  cursor: pointer;
}

.row-action:hover { color: var(--hover); border-color: var(--hover); }
.row-action[disabled] { opacity: 0.5; cursor: default; }

.report {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
  margin-bottom: 10px;
  font-family: inherit;
  color: var(--fg);
  cursor: pointer;
}

.report:hover { border-color: var(--hover); }

.report-title { display: block; font-weight: 600; font-size: 15px; }
.report-message { display: block; font-size: 14px; margin: 4px 0; }
.report-meta { display: block; color: var(--muted); font-size: 12.5px; }

.field { margin-bottom: 16px; }
.field-label { font-weight: 700; font-size: 14px; margin-bottom: 4px; }
