/* 順手 — a restrained product page.
 *
 * Deliberately narrow in its means: one accent colour taken from the app icon,
 * the system font stack, and generous space. No gradients behind text, no
 * glass, no emoji, no floating cards. The screenshots are real captures of the
 * running app, which is the only part of a page like this that carries any
 * evidence.
 */

:root {
  --ink: #1d1d1f;
  --ink-soft: #6e6e73;
  --ink-faint: #86868b;
  --line: #d2d2d7;
  --line-soft: #e8e8ed;
  --paper: #ffffff;
  --paper-alt: #f5f5f7;
  --paper-sunk: #fafafc;
  --accent: #1a4fd6;
  --accent-soft: #eef2fd;
  --warn: #9a5b00;
  --warn-soft: #fff5e6;
  --measure: 36rem;
  --page: 64rem;
  --radius: 12px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #f5f5f7;
    --ink-soft: #a1a1a6;
    --ink-faint: #86868b;
    --line: #38383c;
    --line-soft: #2a2a2e;
    --paper: #000000;
    --paper-alt: #0d0d10;
    --paper-sunk: #131316;
    --accent: #5c8bff;
    --accent-soft: #131a2c;
    --warn: #f0b357;
    --warn-soft: #241c0d;
  }
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
    "PingFang TC", "PingFang SC", "Noto Sans CJK TC", "Microsoft JhengHei", sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Links carry their colour, not a rule under them. Apple's own pages leave a
   link undecorated until the pointer is on it, which keeps a paragraph clean
   while still answering "is this clickable?" the moment you go looking.
   The hover underline is what keeps colour from being the only signal, so it
   stays — thinned and dropped clear of the descenders rather than removed. */
a {
  color: var(--accent);
  text-decoration: none;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
a:hover { text-decoration: underline; }

/* One visible focus ring everywhere, rather than each control inventing one. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 8px;
  background: var(--paper);
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid var(--line);
  z-index: 100;
}
.skip:focus { left: 12px; }

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

/* Navigation ------------------------------------------------------------- */

.nav {
  border-bottom: 1px solid var(--line-soft);
  position: sticky;
  top: 0;
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  z-index: 10;
}

.nav .wrap {
  display: flex;
  align-items: center;
  gap: 20px;
  height: 52px;
  font-size: 14px;
}

.nav .mark {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.nav .mark img { border-radius: 5px; display: block; }
.nav .mark:hover { text-decoration: none; }
.nav .nav-link { color: var(--ink-soft); }
.nav .spacer { margin-left: auto; }

/* The language switcher: a segmented control, not a dropdown. Three options
   fit, and a dropdown hides from a reader in one language that the page is
   available in theirs. */
.langs {
  display: inline-flex;
  background: var(--paper-alt);
  border-radius: 980px;
  padding: 2px;
  gap: 2px;
}
.langs a {
  color: var(--ink-soft);
  padding: 4px 11px;
  border-radius: 980px;
  font-size: 13px;
  line-height: 1.5;
  white-space: nowrap;
}
.langs a:hover { text-decoration: none; color: var(--ink); }
.langs a[aria-current="true"] {
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.08);
}
@media (prefers-color-scheme: dark) {
  .langs a[aria-current="true"] { background: #2a2a2e; box-shadow: none; }
}

.pill {
  border: 1px solid var(--line);
  border-radius: 980px;
  padding: 5px 14px;
  font-size: 13px;
  color: var(--accent);
}
.pill:hover { text-decoration: none; background: var(--accent-soft); }

/* Nothing in the bar may wrap. These are labels, not prose: broken across
   two lines they overflow the bar's fixed height, and a pill with a line
   break in it stops looking like a button at all. */
.nav .mark, .nav .nav-link, .pill, .langs a { white-space: nowrap; }

/* The switcher carries both spellings; one is shown at a time. */
.lang-short { display: none; }

@media (max-width: 620px) {
  .nav .wrap { gap: 12px; }
  .nav .mark span { display: none; }
  .langs a { padding: 4px 8px; font-size: 12px; }
}

/* On a phone the three names spelled out take half the bar, which is what
   pushed the documentation link and the download pill into wrapping. Below
   this width the switcher shows its short forms instead. The full name stays
   in the markup for anything reading the page rather than looking at it. */
@media (max-width: 520px) {
  .lang-full {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .lang-short { display: inline; }
  .nav .wrap { gap: 10px; }
  .langs a { padding: 4px 9px; }
}

/* Narrower still, the documentation link goes: it is the one item repeated
   in the footer and on every page, so losing it here costs the least.
   The threshold clears 400px rather than 380 because the English labels are
   the long ones — "Documentation" beside "Download" is what overflows first,
   and the Chinese pages have room to spare at the same width. */
@media (max-width: 400px) {
  .nav .nav-link { display: none; }
}

/* Hero -------------------------------------------------------------------- */

.hero { padding: 0; }

.hero h1 {
  font-size: clamp(40px, 7vw, 66px);
  line-height: 1.07;
  letter-spacing: -0.028em;
  font-weight: 600;
  margin: 0 0 18px;
  text-wrap: balance;
}

.hero .lede {
  font-size: clamp(18px, 2.2vw, 21px);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 34ch;
  margin: 0 0 30px;
  text-wrap: pretty;
}

.actions { display: flex; gap: 12px; flex-wrap: wrap; }

.button {
  display: inline-block;
  background: var(--accent);
  color: #fff;
  border-radius: 980px;
  padding: 11px 26px;
  font-size: 16px;
  font-weight: 500;
  border: 1px solid transparent;
  transition: filter 0.15s ease, transform 0.15s ease;
}
.button:hover { text-decoration: none; filter: brightness(1.08); }
.button:active { transform: scale(0.98); }
.button.secondary { background: transparent; color: var(--accent); border-color: var(--line); }
.button.secondary:hover { background: var(--accent-soft); filter: none; }
@media (prefers-reduced-motion: reduce) { .button { transition: none; } }

.hero .fineprint { color: var(--ink-faint); font-size: 13px; margin-top: 16px; }

/* The bar sits on a shelf rather than floating on the page: it is a small,
   dark object, and dropped straight onto white it reads as a stray graphic. */
.hero-shot {
  margin: 0;
  padding: 44px 24px;
  background: var(--paper-alt);
  border-radius: 18px;
}
.hero-shot img { display: block; margin: 0 auto; max-width: 100%; height: auto; }

/* Sections ---------------------------------------------------------------- */

section { padding: 92px 0; }
section.alt { background: var(--paper-alt); }
section + section:not(.alt) { border-top: 1px solid var(--line-soft); }

h2 {
  font-size: clamp(27px, 3.6vw, 36px);
  letter-spacing: -0.022em;
  font-weight: 600;
  margin: 0 0 14px;
  line-height: 1.18;
  text-wrap: balance;
}

p.body { color: var(--ink-soft); max-width: var(--measure); margin: 0 0 8px; text-wrap: pretty; }

.feature { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: center; }
.feature.flip > .text { order: 2; }
.feature.solo { grid-template-columns: 1fr; max-width: var(--measure); }
.media img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--line-soft);
  box-shadow: 0 12px 40px rgb(0 0 0 / 0.10);
  display: block;
}
@media (prefers-color-scheme: dark) {
  .media img { box-shadow: 0 12px 40px rgb(0 0 0 / 0.5); }
}

@media (max-width: 780px) {
  .feature { grid-template-columns: 1fr; gap: 32px; }
  .feature.flip > .text { order: 0; }
  section { padding: 64px 0; }
}

/* A definition list rather than bullets: each line is a name and what it
   means, and bullets would hide that shape. */
.points { list-style: none; padding: 0; margin: 22px 0 0; }
.points li {
  display: grid;
  grid-template-columns: minmax(6.5rem, auto) 1fr;
  gap: 4px 16px;
  padding: 12px 0;
  border-top: 1px solid var(--line-soft);
  align-items: baseline;
}
.points .term { color: var(--ink); font-weight: 500; font-size: 15px; }
.points .detail { color: var(--ink-soft); font-size: 15px; }
@media (max-width: 480px) {
  .points li { grid-template-columns: 1fr; }
}

/* Pricing ----------------------------------------------------------------- */

.price { text-align: center; }
.price .terms {
  color: var(--ink-soft);
  max-width: var(--measure);
  margin: 0 auto 14px;
  text-wrap: pretty;
}
.price .button { margin-top: 14px; }

/* Specifications ---------------------------------------------------------- */

.specs { padding: 64px 0 80px; }
.quiet-heading {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 20px;
}
.spec-grid {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0 40px;
}
.spec { padding: 14px 0; border-top: 1px solid var(--line-soft); }
.spec dt { font-size: 13px; color: var(--ink-faint); }
.spec dd { margin: 2px 0 0; font-size: 15px; }

/* Footer ------------------------------------------------------------------ */

footer {
  border-top: 1px solid var(--line-soft);
  padding: 28px 0;
  font-size: 13px;
  color: var(--ink-faint);
  background: var(--paper);
}
footer .wrap { display: flex; gap: 10px 20px; flex-wrap: wrap; align-items: center; }
footer .spacer { margin-left: auto; }
footer a { color: var(--ink-soft); }
footer a:hover { color: var(--ink); }
footer .mark { display: inline-flex; align-items: center; gap: 7px; color: var(--ink); font-weight: 500; }
footer .mark img { border-radius: 4px; display: block; }
footer .mark:hover { text-decoration: none; }
/* On a phone the links keep one row and the fine print drops below them,
   rather than the whole footer wrapping wherever it happens to run out. */
@media (max-width: 620px) {
  footer .spacer { flex-basis: 100%; height: 0; margin: 0; }
}

/* Not found ---------------------------------------------------------------- */

.lost {
  min-height: 100vh;
  display: grid;
  place-content: center;
  justify-items: center;
  text-align: center;
  padding: 24px;
}
.lost img { border-radius: 14px; margin-bottom: 18px; }
.lost-code {
  margin: 0;
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.lost h1 {
  font-size: clamp(28px, 4vw, 36px);
  letter-spacing: -0.022em;
  font-weight: 600;
  margin: 4px 0 28px;
}

/* Documentation ------------------------------------------------------------ */

.docs-layout {
  display: grid;
  grid-template-columns: 13rem 1fr;
  gap: 56px;
  padding-top: 56px;
  padding-bottom: 88px;
  align-items: start;
}

.toc { position: sticky; top: 76px; font-size: 14px; }
.toc-title {
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 10px;
}
.toc ul { list-style: none; margin: 0; padding: 0; }
.toc li { margin: 0 0 2px; }
.toc a {
  display: block;
  color: var(--ink-soft);
  padding: 5px 10px;
  border-radius: 7px;
  line-height: 1.4;
}
.toc a:hover { text-decoration: none; background: var(--paper-alt); color: var(--ink); }

.prose { max-width: 40rem; min-width: 0; }
.prose h1 {
  font-size: clamp(30px, 4vw, 40px);
  letter-spacing: -0.022em;
  font-weight: 600;
  margin: 0 0 10px;
  line-height: 1.15;
}
.prose .intro { color: var(--ink-soft); font-size: 18px; margin: 0 0 8px; }
.doc-section { padding: 36px 0 0; scroll-margin-top: 76px; }
.doc-section h2 {
  font-size: 22px;
  letter-spacing: -0.012em;
  margin: 0 0 12px;
  padding-top: 28px;
  border-top: 1px solid var(--line-soft);
  position: relative;
}
.anchor {
  position: absolute;
  left: -1.1em;
  color: var(--line);
  opacity: 0;
  font-weight: 400;
}
.doc-section h2:hover .anchor { opacity: 1; }
.anchor:hover { text-decoration: none; color: var(--accent); }
@media (max-width: 900px) { .anchor { display: none; } }

.prose p { color: var(--ink-soft); text-wrap: pretty; }
/* Body copy in the docs follows the same rule as everywhere else: underlined
   only on hover. A page of prose with every link ruled through reads as
   marked-up text rather than as writing. */
.prose a:hover { text-decoration: underline; }

.steps, .bullets { color: var(--ink-soft); padding-left: 1.3em; margin: 0 0 16px; }
.steps li, .bullets li { margin: 0 0 8px; }
.steps { counter-reset: step; }

.defs { margin: 0 0 18px; }
.defs dt {
  font-weight: 500;
  color: var(--ink);
  font-size: 15px;
  margin-top: 16px;
}
.defs dd { margin: 3px 0 0; color: var(--ink-soft); font-size: 15px; }

.callout {
  border-radius: 10px;
  padding: 13px 16px;
  font-size: 15px;
  margin: 0 0 18px;
  border: 1px solid transparent;
}
.callout.note { background: var(--accent-soft); color: var(--ink-soft); }
.callout.warn { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }

kbd {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.85em;
  background: var(--paper-alt);
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 5px;
  padding: 1px 5px;
  color: var(--ink);
}

pre {
  background: var(--paper-sunk);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  padding: 14px 16px;
  overflow-x: auto;
  font-size: 13px;
  margin: 0 0 18px;
}
pre code { font-family: ui-monospace, "SF Mono", Menlo, monospace; }

@media (max-width: 860px) {
  .docs-layout { grid-template-columns: 1fr; gap: 8px; }
  .toc {
    position: static;
    border-bottom: 1px solid var(--line-soft);
    padding-bottom: 16px;
    margin-bottom: 16px;
  }
  .toc ul { display: flex; flex-wrap: wrap; gap: 2px; }
}

/* Admin ------------------------------------------------------------------- */

body.admin { background: var(--paper-alt); }
.admin-login { max-width: 320px; margin: 14vh auto; padding: 0 24px; }
.admin-login h1, .admin-main h1 { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
.admin-login form { display: grid; gap: 8px; }
.admin-main { max-width: 1000px; margin: 0 auto; padding: 32px 24px 80px; }
.admin-main header { display: flex; align-items: baseline; gap: 16px; margin-bottom: 24px; }
.admin-main header form { margin-left: auto; }
.admin-main section { padding: 24px 0; border-top: 1px solid var(--line); }
.admin-main h2 { font-size: 17px; font-weight: 600; margin: 0 0 12px; }
.admin-main .count { color: var(--ink-soft); font-weight: 400; font-size: 14px; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr) auto; gap: 12px; align-items: end; }
.grid label { display: grid; gap: 4px; font-size: 13px; color: var(--ink-soft); }
.grid label.wide { grid-column: 1 / -1; }
input, textarea, button {
  font: inherit;
  font-size: 14px;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--paper);
  color: var(--ink);
}
button { background: var(--accent); color: #fff; border-color: transparent; cursor: pointer; }
button.quiet { background: transparent; color: var(--ink-soft); border-color: var(--line); }
.hint, .notice, .error { font-size: 13px; }
.hint { color: var(--ink-soft); max-width: 44rem; }
.notice { color: var(--accent); }
.error { color: #c1121f; }
table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 8px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); white-space: nowrap; }
th { color: var(--ink-soft); font-weight: 500; }
tr.used { color: var(--ink-soft); }
tr.used code { text-decoration: line-through; }
.free { color: var(--accent); }
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; }
@media (max-width: 700px) { .grid { grid-template-columns: 1fr; } }


/* The first screen ---------------------------------------------------------
 *
 * Everything that answers "what is this and can I have it?" is in the first
 * screen at once: the title, the two buttons, the screenshot, and — under the
 * buttons — one of three moments a Windows user recognises.
 *
 * Only that last line changes. Scrolling the hero advances it from the first
 * frustration to the third and then to the closing line, while the title, the
 * buttons and the picture beside them hold still. The hero is pinned for that
 * run and then lets go, so the cost is about one and a half extra screens of
 * scrolling rather than the four the full-bleed version took.
 *
 * As always the beats are plain paragraphs in the markup. The default is to
 * show them as a short stacked list; the pinned rotation is added only where
 * the browser can drive it and the reader has not asked for less motion.
 */

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
  padding-top: 72px;
  padding-bottom: 72px;
}

.hero-see { min-width: 0; }
.hero-stage .hero-shot img { max-width: 100%; height: auto; }

/* The rotating line sits under the buttons, separated by a rule rather than
   by distance: it is a different kind of statement from the pitch above it,
   and the hero is not tall enough to spend space saying so. */
.beats {
  margin-top: 36px;
  padding-top: 28px;
  border-top: 1px solid var(--line-soft);
}

.pain {
  font-size: clamp(19px, 1.9vw, 24px);
  line-height: 1.35;
  letter-spacing: -0.012em;
  font-weight: 600;
  margin: 0 0 14px;
  max-width: 30ch;
  text-wrap: pretty;
}

/* What macOS did instead: lighter, so the eye lands on the action first and
   the disappointment second — the order it actually happened in. */
.pain-turn { color: var(--ink-soft); font-weight: 400; display: block; }

.pain-outro {
  font-size: clamp(19px, 1.9vw, 24px);
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--accent);
  margin: 0;
  text-wrap: pretty;
}

@supports (animation-timeline: view()) and (animation-range: 0% 100%) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 861px) {
    /* Half a viewport per beat — enough to read a short line, and far less
       than the full screen the beats used to cost. Beat 0 is already up when
       the page is at rest, so the pinned run only has to carry the remaining
       beats and the closing line, plus a short hold so the last line is read
       before the hero lets go. */
    .hero-track { height: calc(100vh + (var(--n) + 0.6) * 50vh); }

    /* Position and count come from the markup's shape. They cannot be passed
       in style attributes: the Content-Security-Policy refuses inline styles. */
    .hero-track { --n: 3; }
    .hero-track:has(.pain:nth-child(4)) { --n: 4; }
    .hero-track:has(.pain:nth-child(5)) { --n: 5; }
    .pain:nth-child(1) { --i: 0; }
    .pain:nth-child(2) { --i: 1; }
    .pain:nth-child(3) { --i: 2; }
    .pain:nth-child(4) { --i: 3; }
    .pain:nth-child(5) { --i: 4; }

    .hero-stage {
      position: sticky;
      top: 0;
      height: 100vh;
      display: flex;
      align-items: center;
    }

    .hero-stage .hero-grid { width: 100%; padding-top: 0; padding-bottom: 0; }

    /* One cell, so the beats cross-fade in place instead of pushing each
       other down the column. The row is reserved at the height of the tallest
       beat, so the buttons above it never move as the text changes. */
    .beats { display: grid; grid-template-areas: "beat"; align-items: start; }
    .beats > * { grid-area: beat; }

    .pain, .pain-outro { opacity: 0; margin-bottom: 0; }

    .hero { timeline-scope: --hero; }
    .hero-track { view-timeline: --hero block; }

    /* Beat 0 starts already arrived: at rest, before any scrolling, the first
       frustration must be readable under the buttons — an empty slot there is
       the whole problem this layout exists to fix. Its slice therefore begins
       before the hero is pinned, so by the time the page is still it has
       finished rising. The rest follow half a viewport apart. */
    .pain {
      animation: beat-through linear both;
      animation-timeline: --hero;
      animation-range: cover calc(50vh + var(--i) * 50vh) cover
        calc(50vh + (var(--i) + 2) * 50vh);
    }

    .pain-outro {
      animation: beat-arrive linear both;
      animation-timeline: --hero;
      animation-range: cover calc(50vh + (var(--n) + 0.3) * 50vh) cover
        calc(50vh + (var(--n) + 0.8) * 50vh);
    }
  }
}

/* Rise, hold, leave. The hold occupies the middle so a beat is fully legible
   for most of its slice rather than only at one exact scroll offset. */
@keyframes beat-through {
  0%   { opacity: 0; transform: translateY(14px); }
  22%  { opacity: 1; transform: translateY(0); }
  62%  { opacity: 1; transform: translateY(0); }
  85%  { opacity: 0; transform: translateY(-12px); }
  100% { opacity: 0; transform: translateY(-12px); }
}

@keyframes beat-arrive {
  0%   { opacity: 0; transform: translateY(12px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* One column below the breakpoint: side by side, neither the sentences nor
   the screenshot would have room. The beats become the stacked list, which is
   also what the guard above withholds the animation from. */
@media (max-width: 860px) {
  .hero-grid {
    grid-template-columns: 1fr;
    padding-top: 56px;
    padding-bottom: 56px;
  }
  .hero-see { order: -1; }
  .hero .lede { max-width: none; }
}
