/*
 * Zdex 智体 — 官网样式
 *
 * The palette is the product's own dark theme, not a website palette invented
 * beside it: the grounds, the ink ramp and the link blue are the values
 * packages/client/ui-theme ships, so the site and the client cannot drift into
 * looking like two products. The wordmark is the same traced outline the
 * client masks its sheen through (apps/bahzt-desktop/brand/zdex-lockup.svg).
 */

:root {
  --ground: #0e0e0e;
  --raised: #181818;
  --line: rgba(184, 184, 184, 0.16);
  --line-soft: rgba(184, 184, 184, 0.09);

  /* The client's label ramp. */
  --ink: rgb(214, 214, 214);
  --ink-2: rgb(173, 178, 184);
  --ink-3: rgb(129, 133, 140);
  --lit: rgb(248, 248, 248);
  --link: rgb(96, 165, 250);

  --font: 'HarmonyOS Sans SC', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont,
    'Segoe UI', system-ui, sans-serif;
  --mono: 'Cascadia Mono', Consolas, ui-monospace, 'SFMono-Regular', monospace;

  --page: 940px;
}

* { box-sizing: border-box; }

html,
body {
  margin: 0;
  /* Inline-equivalent ground, so a slow stylesheet never shows white. */
  background: var(--ground);
  color: var(--ink);
  color-scheme: dark;
}

body {
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }

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

/* ---------------------------------------------------------------- 品牌 */

/*
 * The wordmark is drawn, not set: an outline used as a mask over the same
 * sweeping gradient the client animates. Give it a width; the aspect ratio
 * supplies the height, and the artwork is cropped to its own ink so there is
 * no padding to compensate for.
 */
.lockup {
  display: block;
  width: 132px;
  aspect-ratio: 1681 / 502;
  background-image: linear-gradient(
    100deg,
    rgb(150, 150, 150) 0%,
    rgb(150, 150, 150) 38%,
    /* A plateau, not a point: at a single stop the ramp falls away before the
       band has crossed a letter, and the pass reads pale. */
    #ffffff 47%,
    #ffffff 53%,
    rgb(150, 150, 150) 62%,
    rgb(150, 150, 150) 100%
  );
  background-size: 340% 100%;
  background-repeat: no-repeat;
  -webkit-mask-image: url('/assets/zdex-lockup.svg');
  mask-image: url('/assets/zdex-lockup.svg');
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  animation: sweep 7.2s linear infinite;
}

/* Clipped to glyphs rather than to an outline, but the same sweep. */
.sheen {
  background-image: linear-gradient(
    100deg,
    rgb(150, 150, 150) 0%,
    rgb(150, 150, 150) 38%,
    #ffffff 47%,
    #ffffff 53%,
    rgb(150, 150, 150) 62%,
    rgb(150, 150, 150) 100%
  );
  background-size: 340% 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: sweep 4.2s linear infinite;
}

/* Decreasing the position slides the image right, so the band crosses the box
   from left to right. Stays inside 0%..100%: an oversized no-repeat background
   only covers its box in that range, and outside it the glyphs go blank. */
@keyframes sweep {
  from { background-position: 100% 0; }
  to { background-position: 0% 0; }
}

.mark {
  display: inline-block;
  background: currentcolor;
  /* The glyph alone. brand.svg keeps the icon's tile for the favicon, and a
     mask reads alpha - that opaque tile would render as a solid square. */
  -webkit-mask-image: url('/assets/zdex-glyph.svg');
  mask-image: url('/assets/zdex-glyph.svg');
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* ---------------------------------------------------------------- 页头 */

.top {
  display: flex;
  align-items: flex-end;
  gap: 14px;
  padding: 34px 0 0;
}

.top .domain {
  font-size: 12px;
  color: var(--ink-3);
  /* On the wordmark's own bottom bar, which sits a shade below the letters. */
  margin-bottom: -1px;
}

.top nav {
  margin-left: auto;
  display: flex;
  gap: 26px;
  font-size: 13.5px;
}

.top nav a { color: var(--ink-2); }
.top nav a:hover { color: var(--ink); text-decoration: none; }
.top nav a[aria-current] { color: var(--lit); }

/* ---------------------------------------------------------------- 首屏 */

.hero { padding: 132px 0 96px; }

.hero h1 {
  margin: 0;
  font-size: 76px;
  line-height: 1.14;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.hero .lead {
  max-width: 620px;
  margin: 30px 0 0;
  font-size: 19px;
  color: var(--ink-2);
}

.hero .sub {
  max-width: 560px;
  margin: 14px 0 0;
  font-size: 15px;
  color: var(--ink-3);
}

.actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 44px; }

.btn {
  display: inline-flex;
  align-items: center;
  height: 44px;
  padding: 0 24px;
  border-radius: 22px;
  font-size: 14px;
  font-weight: 500;
  background: rgb(236, 236, 236);
  color: #1a1a1a;
  transition: background-color 160ms ease;
}

.btn:hover { background: #ffffff; text-decoration: none; }

.btn.ghost {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--ink);
}

.btn.ghost:hover { background: rgba(255, 255, 255, 0.06); }

.requirement { margin-top: 18px; font-size: 12.5px; color: var(--ink-3); }

/* ---------------------------------------------------------------- 分节 */

.band { padding: 92px 0; }

.band h2 {
  margin: 0 0 20px;
  font-size: 40px;
  line-height: 1.28;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.band > p { max-width: 660px; margin: 0; font-size: 16px; color: var(--ink-2); }

.pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 54px;
  margin-top: 46px;
}

.pair h3 { margin: 0 0 8px; font-size: 17px; font-weight: 600; color: var(--ink); }
.pair p { margin: 0; font-size: 14.5px; color: var(--ink-3); }

.uses {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 54px;
  margin-top: 40px;
}

.uses div {
  padding: 16px 0;
  border-bottom: 1px solid var(--line-soft);
  display: flex;
  gap: 18px;
  font-size: 14.5px;
}

.uses b { min-width: 84px; font-weight: 600; color: var(--ink); }
.uses span { color: var(--ink-3); }

.close { padding: 110px 0 130px; text-align: center; }
.close h2 { margin: 0; font-size: 46px; font-weight: 600; letter-spacing: -0.01em; }
.close .actions { justify-content: center; }

/* ---------------------------------------------------------------- 页脚 */

.foot {
  border-top: 1px solid var(--line-soft);
  padding: 30px 0 64px;
  font-size: 12.5px;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 12px;
}

.foot .mark { width: 20px; height: 20px; opacity: 0.75; }
.foot nav { margin-left: auto; display: flex; gap: 22px; }
.foot nav a { color: var(--ink-3); }

/* ---------------------------------------------------------------- 文档 */

.docs {
  display: grid;
  grid-template-columns: 250px minmax(0, 1fr);
  align-items: start;
  min-height: 100vh;
}

.docs-rail {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  padding: 22px 18px 30px;
  border-right: 1px solid var(--line-soft);
  background: var(--raised);
}

.docs-rail .brand { display: flex; align-items: flex-end; gap: 7px; padding-left: 4px; }
.docs-rail .lockup { width: 104px; }
.docs-rail .domain { font-size: 11px; color: var(--ink-3); margin-bottom: -1px; }

.docs-rail .heading {
  margin: 26px 0 10px 12px;
  font: 600 11.5px/1 var(--mono);
  letter-spacing: 0.16em;
  color: var(--ink-3);
}

.docs-rail nav { display: flex; flex-direction: column; gap: 1px; }

.docs-rail nav a {
  padding: 7px 12px;
  border-radius: 8px;
  font-size: 13px;
  color: var(--ink-2);
}

.docs-rail nav a:hover { background: rgba(255, 255, 255, 0.05); text-decoration: none; }
.docs-rail nav a.current { background: rgba(255, 255, 255, 0.07); color: var(--lit); }

.docs-body { max-width: 820px; padding: 76px 56px 120px; }
.docs-body h1 { margin: 0 0 18px; font-size: 40px; font-weight: 600; letter-spacing: -0.01em; }
.docs-body .lead { margin: 0 0 26px; font-size: 17px; color: var(--ink-2); }

.docs-body h2 {
  margin: 52px 0 14px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
  font-size: 24px;
  font-weight: 600;
}

.docs-body h3 { margin: 28px 0 8px; font-size: 16.5px; font-weight: 600; }
.docs-body p { margin: 0 0 12px; font-size: 15px; color: var(--ink-2); }
.docs-body b { color: var(--ink); font-weight: 600; }
.docs-body ul { margin: 8px 0 16px; padding-left: 20px; }
.docs-body li { margin: 5px 0; font-size: 15px; color: var(--ink-2); }

.docs-body .note {
  margin: 16px 0 20px;
  padding: 6px 0 6px 14px;
  border-left: 2px solid var(--line);
  font-size: 13.5px;
  color: var(--ink-3);
}

.docs-body .quote {
  margin: 22px 0;
  font-size: 19px;
  color: var(--ink);
  letter-spacing: 0.01em;
}

.docs-body table { width: 100%; border-collapse: collapse; margin: 14px 0 20px; font-size: 14px; }
.docs-body th,
.docs-body td { text-align: left; padding: 9px 12px 9px 0; border-bottom: 1px solid var(--line-soft); }
.docs-body th { color: var(--ink-3); font-weight: 500; font-size: 12.5px; }
.docs-body td { color: var(--ink-2); }
.docs-body td:first-child { color: var(--ink); width: 190px; }

.docs-body .steps { counter-reset: step; list-style: none; padding-left: 0; }
.docs-body .steps li { counter-increment: step; padding-left: 34px; position: relative; }

.docs-body .steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 4px;
  width: 22px;
  height: 22px;
  border: 1px solid var(--line);
  border-radius: 50%;
  font: 500 11px/20px var(--mono);
  text-align: center;
  color: var(--ink-3);
}


/* ------------------------------------------------------------ 首屏演示 */

/*
 * The page claims Zdex does the work rather than describing it, so the hero
 * runs one instead of asserting it. The lines are the client's own status
 * copy; app.js plays them and parks on the finished state under
 * prefers-reduced-motion.
 */
.demo {
  max-width: 560px;
  margin: 54px 0 0;
  padding: 16px 18px 18px;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  background: #121212;
  font-size: 13.5px;
  line-height: 1.9;
}

.demo-top {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line-soft);
  font: 11.5px/1 var(--mono);
  color: var(--ink-3);
  letter-spacing: 0.04em;
}

.demo-top .sep { opacity: 0.5; }
.demo-top .elapsed { margin-left: auto; font-variant-numeric: tabular-nums; }

/* Amber while the task runs, green when it lands - the client's own state
   colours, and the only colour on the page. */
.demo-top .state {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgb(251, 191, 36);
  transition: background-color 300ms ease;
}

.demo[data-done] .state { background: rgb(52, 211, 153); }

.demo-ask { margin: 14px 0 4px; color: var(--ink); }
.demo-ask b { margin-right: 10px; font-weight: 600; color: var(--ink-3); }

.demo-ask .caret {
  display: inline-block;
  width: 7px;
  height: 15px;
  margin-left: 2px;
  vertical-align: -2px;
  background: var(--ink-2);
  animation: blink 1.1s steps(1) infinite;
}

.demo[data-typed] .caret { display: none; }

@keyframes blink { 0%, 50% { opacity: 1 } 50.01%, 100% { opacity: 0 } }

.demo-steps { margin: 6px 0 0; padding: 0; list-style: none; }

/*
 * Every row is in the DOM from the first frame, holding the panel at its final
 * height; running the task only turns them on. Appending them as they happen
 * grew the box line by line and shoved the rest of the page down with it.
 */
.demo-steps li {
  display: flex;
  gap: 10px;
  align-items: baseline;
  color: var(--ink-3);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 300ms ease, transform 300ms cubic-bezier(.22, 1, .36, 1);
}

.demo-steps li.in { opacity: 1; transform: none; }

.demo-steps li b { font-weight: 600; color: rgb(52, 211, 153); }
.demo-steps li a { color: var(--link); }
.demo-steps li .tick { color: var(--ink-3); font: 11px/1.9 var(--mono); min-width: 34px; }

/* ------------------------------------------------------------ 进入视野 */

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 620ms cubic-bezier(.22, 1, .36, 1),
    transform 620ms cubic-bezier(.22, 1, .36, 1);
}

.reveal.shown { opacity: 1; transform: none; }

/* A row lights up under the pointer, so the list reads as something you can
   look through rather than a block of text. */
.uses div { transition: color 160ms ease, border-color 160ms ease; }
.uses div:hover { border-color: var(--line); }
.uses div:hover b { color: var(--lit); }
.uses div:hover span { color: var(--ink-2); }

.pair div { transition: transform 260ms cubic-bezier(.22, 1, .36, 1); }
.pair div:hover h3 { color: var(--lit); }


/* ------------------------------------------------------------ 页面动效 */

/*
 * Small, and all of it optional. Each rule below has a counterpart in the
 * reduced-motion block at the foot of this file that parks it in its finished
 * state - nothing here is load-bearing for reading the page.
 */

/* A hairline of progress along the very top, so a long page tells you where
   you are without spending any space on saying so. */
.progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 0;
  z-index: 10;
  background: linear-gradient(90deg, rgb(120, 120, 120), rgb(248, 248, 248));
  pointer-events: none;
}

/* The first screen arrives rather than being there. Staggered a beat apart, in
   reading order, so the eye is led down to the demo instead of landing on the
   whole block at once. */
/*
 * CSS only, with no class for script to flip: an entrance that starts at
 * opacity 0 and waits for JavaScript is a blank first screen whenever that
 * file fails to arrive.
 */
.hero > * {
  opacity: 0;
  transform: translateY(16px);
  animation: rise 700ms cubic-bezier(.22, 1, .36, 1) forwards;
}

.hero h1 { animation-delay: 60ms; }
.hero .lead { animation-delay: 200ms; }
.hero .sub { animation-delay: 300ms; }
.hero .actions { animation-delay: 400ms; }
.hero .requirement { animation-delay: 480ms; }
.hero .demo { animation-delay: 600ms; }

@keyframes rise {
  to { opacity: 1; transform: none; }
}

/*
 * The headline runs BOTH — the sweep and the entrance — and they have to be
 * listed together. `animation` is a shorthand: a second declaration replaces
 * the first rather than adding to it, and the rule above has the same
 * specificity as .sheen while coming later in the file. That is exactly how
 * the headline lost its sheen once already, silently.
 */
.hero h1.sheen {
  animation:
    sweep 4.2s linear infinite,
    rise 700ms cubic-bezier(.22, 1, .36, 1) 60ms forwards;
}

/* Inside a revealed section the parts follow the heading rather than arriving
   with it; the delay is small enough to read as one movement. */
.reveal.shown h2 { transition-delay: 0ms; }
.reveal .pair > *,
.reveal .uses > * {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 520ms cubic-bezier(.22, 1, .36, 1),
    transform 520ms cubic-bezier(.22, 1, .36, 1);
}

.reveal.shown .pair > *,
.reveal.shown .uses > * { opacity: 1; transform: none; }

.reveal.shown .pair > *:nth-child(2),
.reveal.shown .uses > *:nth-child(2) { transition-delay: 90ms; }
.reveal.shown .uses > *:nth-child(3) { transition-delay: 150ms; }
.reveal.shown .uses > *:nth-child(4) { transition-delay: 210ms; }
.reveal.shown .uses > *:nth-child(5) { transition-delay: 270ms; }
.reveal.shown .uses > *:nth-child(6) { transition-delay: 330ms; }

/* Nav links grow a rule from the left rather than switching one on. */
.top nav a {
  position: relative;
  padding-bottom: 3px;
}

.top nav a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentcolor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 260ms cubic-bezier(.22, 1, .36, 1);
}

.top nav a:hover { text-decoration: none; }
.top nav a:hover::after { transform: scaleX(1); }

/* The buttons lift a little and settle when pressed. */
.btn {
  transition: background-color 160ms ease, transform 160ms cubic-bezier(.22, 1, .36, 1),
    box-shadow 200ms ease;
}

.btn:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5); }
.btn:active { transform: none; box-shadow: none; }

/* The section rule draws itself in as the section is reached, which is what
   makes the page feel like it is being laid out rather than scrolled over. */
.band,
.close { position: relative; }

.band::before,
.close::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--line);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 900ms cubic-bezier(.22, 1, .36, 1);
}

.reveal.shown::before { transform: scaleX(1); }


/* --------------------------------------------------------- 分节进入动效 */

/*
 * Each section announces itself. The heading wipes up from behind its own
 * edge, its rule draws under it, and the parts below follow in order — enough
 * that arriving at a section feels like something happened, without any of it
 * being an effect for its own sake.
 */
.reveal h2 {
  clip-path: inset(0 0 105% 0);
  transform: translateY(22px);
  transition: clip-path 760ms cubic-bezier(.22, 1, .36, 1),
    transform 760ms cubic-bezier(.22, 1, .36, 1);
}

.reveal.shown h2 { clip-path: inset(0 0 -12% 0); transform: none; }

/* A short rule that grows out from under the heading, one beat behind it. */
.reveal h2::after {
  content: '';
  display: block;
  width: 72px;
  height: 2px;
  margin-top: 18px;
  background: linear-gradient(90deg, rgb(230, 230, 230), rgba(230, 230, 230, 0));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 700ms cubic-bezier(.22, 1, .36, 1) 260ms;
}

.reveal.shown h2::after { transform: scaleX(1); }

/* The paragraph under the heading comes with it, a little later. */
.reveal > p {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 620ms cubic-bezier(.22, 1, .36, 1) 180ms,
    transform 620ms cubic-bezier(.22, 1, .36, 1) 180ms;
}

.reveal.shown > p { opacity: 1; transform: none; }

/* Columns enter from their own side, so a two-up reads as two things. */
.reveal .pair > * { transform: translateY(18px) translateX(-10px); }
.reveal .pair > *:nth-child(2) { transform: translateY(18px) translateX(10px); }
.reveal.shown .pair > * { transform: none; }

/* Rows come in one after another, left to right down the list. */
.reveal .uses > * { transform: translateY(14px) translateX(-14px); }
.reveal.shown .uses > * { transform: none; }

.reveal.shown .pair > *,
.reveal.shown .uses > * { transition-duration: 620ms; }
.reveal.shown .uses > *:nth-child(1) { transition-delay: 240ms; }
.reveal.shown .uses > *:nth-child(2) { transition-delay: 310ms; }
.reveal.shown .uses > *:nth-child(3) { transition-delay: 380ms; }
.reveal.shown .uses > *:nth-child(4) { transition-delay: 450ms; }
.reveal.shown .uses > *:nth-child(5) { transition-delay: 520ms; }
.reveal.shown .uses > *:nth-child(6) { transition-delay: 590ms; }
.reveal.shown .pair > *:nth-child(1) { transition-delay: 300ms; }
.reveal.shown .pair > *:nth-child(2) { transition-delay: 400ms; }

/* No rule under the closing line: the section is centred and this one is a
   left-aligned block, so it hung out beside the heading rather than under it. */
.close h2::after { content: none; }

/* The closing line is the one place a bigger move is warranted. */
.close h2 { letter-spacing: 0.06em; transition: letter-spacing 900ms cubic-bezier(.22, 1, .36, 1); }
.close.shown h2 { letter-spacing: -0.01em; }

/* The footer mark turns once as it comes into view — a full turn, slowly, and
   only the once. */
.foot .mark { transition: transform 1400ms cubic-bezier(.22, 1, .36, 1), opacity 600ms ease; }
.foot.shown .mark { transform: rotate(360deg); }
.foot { opacity: 0; transition: opacity 700ms ease; }
.foot.shown { opacity: 1; }

/* The demo panel lifts very slightly while it is running, so the eye returns
   to it after the first screen has settled. */
.demo { transition: border-color 500ms ease, box-shadow 700ms ease; }
.demo[data-done] { border-color: rgba(52, 211, 153, 0.28); box-shadow: 0 0 0 1px rgba(52, 211, 153, 0.06); }

/* ------------------------------------------------------------- 自适应 */

@media (max-width: 860px) {
  .wrap { padding: 0 24px; }
  .hero { padding: 84px 0 64px; }
  .hero h1 { font-size: 48px; }
  .band { padding: 64px 0; }
  .band h2 { font-size: 30px; }
  .close h2 { font-size: 32px; }
  .pair,
  .uses { grid-template-columns: 1fr; gap: 0; }
  .pair div { padding: 14px 0; border-bottom: 1px solid var(--line-soft); }
  .docs { grid-template-columns: 1fr; }
  .docs-rail { position: static; height: auto; border-right: 0; border-bottom: 1px solid var(--line-soft); }
  .docs-rail nav { flex-direction: row; flex-wrap: wrap; }
  .docs-body { padding: 40px 24px 80px; }
  .docs-body h1 { font-size: 30px; }
}

/* Stopped, the band rests at an end of its travel, where the whole line is the
   resting grey rather than parked mid-highlight. */
@media (prefers-reduced-motion: reduce) {
  .lockup,
  .sheen {
    animation: none;
    background-position: 100% 0;
  }

  .reveal { opacity: 1; transform: none; transition: none; }
  .demo-steps li,
  .demo-ask .caret { animation: none; }
  .demo-ask .caret { display: none; }

  /* Everything else parks where it would have ended up. */
  .hero > * { opacity: 1; transform: none; animation: none; }
  .hero h1.sheen { animation: none; background-position: 100% 0; }
  .reveal h2 { clip-path: none; transform: none; transition: none; }
  .reveal h2::after { transform: scaleX(1); transition: none; }
  .reveal > p { opacity: 1; transform: none; transition: none; }
  .close h2 { letter-spacing: -0.01em; transition: none; }
  .foot { opacity: 1; transition: none; }
  .foot .mark { transform: none; transition: none; }
  .demo-steps li { opacity: 1; transform: none; transition: none; }
  .reveal .pair > *,
  .reveal .uses > * { opacity: 1; transform: none; transition: none; }
  .band::before,
  .close::before { transform: scaleX(1); transition: none; }
  .top nav a::after { transition: none; }
  .btn { transition: none; }
  .btn:hover { transform: none; box-shadow: none; }
  .progress { display: none; }
}
