<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ceramic</title>
<script defer src="/analytics.js"></script>
<meta name="description" content="Ceramic is a lightweight Mastodon client for your desktop. Read your timeline, follow conversations in columns, and share your thoughts.">
<meta name="theme-color" content="#eeeef0" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#171719" media="(prefers-color-scheme: dark)">
<meta property="og:title" content="Ceramic">
<meta property="og:description" content="A lightweight Mastodon client for your desktop.">
<meta property="og:image" content="/icon-512.png">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<script>
  // Pick the language before first paint: saved choice, then the browser's.
  (function () {
    var lang;
    try { lang = localStorage.getItem("ceramic-lang"); } catch (e) {}
    if (lang !== "zh" && lang !== "en") {
      lang = /^zh\b/i.test(navigator.language || "") ? "zh" : "en";
    }
    document.documentElement.lang = lang === "zh" ? "zh-CN" : "en";
    document.documentElement.dataset.lang = lang;
  })();
</script>
<style>
:root {
  --workspace: #eeeef0;
  --paper: #ffffff;
  --ink: #1c1c1e;
  --text: #1c1c1e;
  --muted: #6c6c72;
  --faint: #a4a4aa;
  --line: rgba(28, 28, 30, 0.08);
  --blob: #1c1c1e;
  --eye: #ffffff;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  --tint-warn: #c48a38;
  --tint-boost: #9263c7;
  --tint-reply: #4f89b7;
  --serif: ui-serif, "New York", "Songti SC", "Noto Serif CJK SC", "Source Han Serif SC", Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --gutter: clamp(16px, 5vw, 64px);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
  :root {
    --workspace: #171719;
    --paper: #232325;
    --ink: #eeeef0;
    --text: #ececee;
    --muted: #9a9aa0;
    --faint: #6a6a70;
    --line: rgba(255, 255, 255, 0.08);
    --blob: #38383c;
    --eye: #eeeef0;
    --shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--workspace);
  color: var(--text);
  font: 16px/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
a { color: inherit; }
h1, h2 { text-wrap: balance; }
img, svg { display: block; }

/* One language at a time; both ship in the HTML so the page reads without JS. */
html[data-lang="zh"] [lang="en"], html[data-lang="en"] [lang="zh"] { display: none !important; }
html:not([data-lang]) [lang="en"] { display: none !important; }

.wrap { width: min(1120px, 100%); margin: 0 auto; padding: 0 var(--gutter); }

/* ---------- top bar ---------- */
.bar {
  display: flex; align-items: center; justify-content: space-between;
  padding-top: 22px; padding-bottom: 22px;
}
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; font-weight: 600; letter-spacing: -0.01em; }
.brand img { width: 28px; height: 28px; }
.bar nav { display: flex; align-items: center; gap: 4px; font-size: 14px; }
.bar nav a, .lang {
  text-decoration: none; color: var(--muted); padding: 6px 10px; border-radius: 8px;
  background: none; border: 0; font: inherit; cursor: pointer;
}
.bar nav a:hover, .lang:hover { color: var(--text); background: var(--line); }
.lang { font-family: var(--mono); font-size: 12px; letter-spacing: 0.04em; }

/* ---------- hero ---------- */
.hero { position: relative; overflow: hidden; }
.hero h1 {
  font: 400 clamp(44px, 8.4vw, 104px)/1.02 var(--serif);
  letter-spacing: -0.035em;
  margin: clamp(40px, 9vw, 110px) 0 0;
  max-width: 15ch;
}
html[data-lang="zh"] .hero h1 { letter-spacing: -0.02em; max-width: 9em; line-height: 1.12; }
.hero h1 em { font-style: italic; color: var(--muted); }
html[data-lang="zh"] .hero h1 em { font-style: normal; }
.lede { max-width: 34em; margin: 28px 0 0; font-size: clamp(17px, 1.6vw, 19px); color: var(--muted); }
.lede strong { color: var(--text); font-weight: 600; }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; margin-top: 36px; }
.btn {
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--ink); color: var(--workspace);
  text-decoration: none; font-weight: 600; font-size: 15px;
  padding: 13px 20px 13px 18px; border-radius: 999px;
  transition: transform 160ms var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: scale(0.97); }
.btn svg { width: 18px; height: 18px; }
.quiet-link { font-size: 15px; color: var(--muted); text-decoration: none; border-bottom: 1px solid var(--line); padding-bottom: 1px; }
.quiet-link:hover { color: var(--text); border-color: currentColor; }
.platforms { margin-top: 14px; font-size: 13px; color: var(--faint); }
.hero .platforms { margin-bottom: 0; }
.assure {
  display: flex; gap: 10px; align-items: flex-start; max-width: 30em;
  margin: 28px 0 0; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 14px; line-height: 1.6; color: var(--muted);
}
.assure svg { width: 16px; height: 16px; flex: none; margin-top: 3px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* The blob peeks from the hero's bottom edge, as it does in the app. */
.hero .peek {
  position: absolute; bottom: 0; right: max(var(--gutter), calc(50% - 560px + var(--gutter)));
  width: clamp(190px, 30vw, 360px);
  transform: translateY(22%);
}

/* ---------- mascot ---------- */
.blob .body { fill: var(--blob); }
.blob .eye { transition: transform 260ms var(--ease); }
.blob .ball { fill: var(--eye); transform-box: fill-box; transform-origin: center; animation: blink 4.6s infinite; }
.blob .eye + .eye .ball { animation-delay: 0.02s; }
@keyframes blink {
  0%, 96.6%, 100% { transform: scaleY(1); }
  98% { transform: scaleY(0.12); }
}
@media (prefers-reduced-motion: reduce) {
  .blob .ball { animation: none; }
  .blob .eye { transition: none; }
}

/* ---------- sections ---------- */
section { padding: clamp(72px, 11vw, 140px) 0; }
#columns { padding-bottom: clamp(28px, 4vw, 48px); }
#native { padding-top: clamp(28px, 4vw, 48px); padding-bottom: clamp(28px, 4vw, 48px); }
#download { padding-top: clamp(28px, 4vw, 48px); }
.kicker { font: 500 12px/1 var(--mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); margin: 0 0 18px; }
h2 {
  font: 400 clamp(32px, 4.8vw, 56px)/1.08 var(--serif);
  letter-spacing: -0.025em; margin: 0; max-width: 16ch;
}
html[data-lang="zh"] h2 { letter-spacing: 0; line-height: 1.2; max-width: 14em; }
.section-lede { max-width: 36em; color: var(--muted); margin: 20px 0 0; font-size: 17px; }

/* ---------- product screenshot ---------- */
.product-shot { margin: 0 0 48px; }
.product-shot img { width: 100%; height: auto; border-radius: 14px; border: 1px solid var(--line); box-shadow: var(--shadow); }
.product-shot figcaption { margin-top: 12px; color: var(--muted); font-size: 13px; }

/* ---------- demo window ---------- */
.stage { margin-top: 48px; }
.window {
  --col: 300px;
  position: relative;
  background: var(--workspace);
  border-radius: 14px;
  border: 1px solid var(--line);
  box-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.28), 0 2px 8px rgba(0, 0, 0, 0.05);
  height: 520px;
  display: flex; overflow: hidden;
  isolation: isolate;
}
@media (prefers-color-scheme: light) { .window { background: #eeeef0; outline: 6px solid rgba(255,255,255,0.6); } }
.rail {
  width: 64px; flex: none; padding: 14px 0; display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: color-mix(in srgb, var(--paper) 45%, var(--workspace));
  border-right: 1px solid var(--line);
}
.lights { display: flex; gap: 6px; margin-bottom: 14px; }
.lights i { width: 10px; height: 10px; border-radius: 50%; background: #ff5f57; }
.lights i:nth-child(2) { background: #febc2e; }
.lights i:nth-child(3) { background: #28c840; }
.rail .me { width: 26px; height: 26px; border-radius: 50%; background: #5b8db8; margin-bottom: 6px; }
.rail .ico { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 9px; color: var(--muted); }
.rail .ico.on { background: var(--line); color: var(--text); }
.rail svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.cols {
  position: relative; flex: 1; min-width: 0;
  display: flex; gap: 10px; padding: 10px;
  overflow-x: auto; overflow-y: hidden; scroll-behavior: smooth;
  scrollbar-width: none;
}
.cols::-webkit-scrollbar { display: none; }
.col {
  flex: none; width: var(--col); height: 100%;
  background: var(--paper); border-radius: 10px; border: 1px solid var(--line); box-shadow: var(--shadow);
  overflow: hidden; display: flex; flex-direction: column;
}
.col.enter { animation: enter 520ms var(--ease) both; }
@keyframes enter { from { opacity: 0; transform: translateX(48px); } }
.col.focus { border-color: color-mix(in srgb, var(--ink) 20%, transparent); }
.col header {
  height: 40px; flex: none; display: flex; align-items: center; gap: 6px;
  padding: 0 14px; font-size: 12.5px; font-weight: 600; border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--workspace) 35%, var(--paper));
}
.col header button { border: 0; background: none; color: var(--faint); font-size: 15px; line-height: 1; cursor: pointer; padding: 4px; }
.col header button:hover { color: var(--text); }
.post {
  display: grid; grid-template-columns: 30px 1fr; gap: 0 10px;
  padding: 12px 14px; font-size: 12px; line-height: 1.55;
  border-bottom: 1px solid var(--line); cursor: pointer;
  transition: background 160ms;
}
.post:hover { background: color-mix(in srgb, var(--workspace) 45%, transparent); }
.post .av { width: 30px; height: 30px; border-radius: 50%; }
.post b { font-weight: 600; }
.post .who { display: flex; justify-content: space-between; gap: 8px; }
.post .handle { color: var(--faint); font-size: 11px; }
.post .when { color: var(--faint); font-size: 10.5px; }
.post p { margin: 4px 0 0; }
.post .acts { display: flex; gap: 6px; margin: 4px 0 0 -6px; }
.post .acts button {
  display: inline-flex; align-items: center; gap: 4px; min-width: 26px; height: 24px; padding: 0 6px;
  border: 0; border-radius: 6px; background: none; color: var(--faint); font: 10.5px var(--sans); cursor: pointer;
  transition: color 160ms, background 160ms;
}
.post .acts button:hover { color: var(--text); background: var(--line); }
.post .acts button.end { margin-left: auto; }
.post .acts button.on { color: var(--ink); }
.post .acts button[data-act="like"].on { color: #ce3150; }
.post .acts button[data-act="like"].on svg { fill: currentColor; }
@media (prefers-color-scheme: dark) { .post .acts button[data-act="like"].on { color: #ff718b; } }
.post .acts button.pop svg { animation: pop 320ms var(--ease); }
@keyframes pop { 40% { transform: scale(1.35); } }
.post .acts svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.post.tint-reply { background: linear-gradient(100deg, color-mix(in srgb, var(--tint-reply) 9%, transparent), transparent 60%); }
.post.tint-boost { background: linear-gradient(100deg, color-mix(in srgb, var(--tint-boost) 9%, transparent), transparent 60%); }
.post.tint-warn { background: linear-gradient(100deg, color-mix(in srgb, var(--tint-warn) 10%, transparent), transparent 60%); }
.cw { margin-top: 6px; height: 34px; border-radius: 6px; background:
  repeating-linear-gradient(180deg, color-mix(in srgb, var(--faint) 40%, transparent) 0 5px, transparent 5px 11px) 10px 8px / calc(100% - 20px) 22px no-repeat,
  color-mix(in srgb, var(--workspace) 60%, transparent); filter: blur(1.4px); }
.profile-head { height: 64px; background: linear-gradient(120deg, #c8cdd6, #e9e4da); }
@media (prefers-color-scheme: dark) { .profile-head { background: linear-gradient(120deg, #33363d, #3c3832); } }
.profile-id { padding: 0 14px 12px; font-size: 12px; border-bottom: 1px solid var(--line); }
.profile-id .av { width: 44px; height: 44px; border-radius: 50%; border: 3px solid var(--paper); margin-top: -22px; }
.profile-id b { display: block; font-size: 14px; margin-top: 4px; }
.profile-id > span { color: var(--faint); font-size: 11px; }

.window .peek {
  position: absolute; bottom: 0; width: 132px; pointer-events: none;
  transform: translateY(22%);
  transition: transform 640ms var(--ease), opacity 300ms;
  z-index: 2;
}
.window .peek.away { transform: translateY(105%); }

.stage-foot { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 20px; margin-top: 18px; font-size: 13.5px; color: var(--faint); }

/* ---------- numbers ---------- */
.numbers { display: grid; grid-template-columns: repeat(3, 1fr); margin-top: 56px; border-top: 1px solid var(--line); }
.numbers > div { padding: 28px 28px 0 0; }
.numbers > div + div { padding-left: 28px; border-left: 1px solid var(--line); }
.num { font: 400 clamp(52px, 7vw, 88px)/1 var(--serif); letter-spacing: -0.04em; }
.num small { font-size: 0.36em; letter-spacing: 0; margin-left: 4px; color: var(--muted); }
.numbers p { margin: 14px 0 0; color: var(--muted); font-size: 15px; max-width: 22em; }
.numbers p b { color: var(--text); font-weight: 600; }
.footnote { margin-top: 28px; font-size: 12.5px; color: var(--faint); max-width: 60em; }

/* ---------- details ---------- */
.details { display: grid; grid-template-columns: 1fr 1fr; gap: 0 64px; margin-top: 48px; }
.details dl { margin: 0; }
.details div { padding: 18px 0; border-top: 1px solid var(--line); display: grid; grid-template-columns: 9.5em 1fr; gap: 16px; }
html[data-lang="zh"] .details div { grid-template-columns: 6.5em 1fr; }
.details dt { font-weight: 600; font-size: 15px; }
.details dd { margin: 0; color: var(--muted); font-size: 15px; }

/* ---------- end ---------- */
.end { position: relative; overflow: hidden; text-align: center; }
.end h2 { margin: 0 auto; }
.end .section-lede { margin-left: auto; margin-right: auto; }
.end .actions { justify-content: center; }
.end .platforms a { color: var(--muted); }
footer { border-top: 1px solid var(--line); background: var(--workspace); position: relative; z-index: 1; }
footer .wrap { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; padding-top: 22px; padding-bottom: 28px; font-size: 13px; color: var(--faint); }
footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--text); }

/* ---------- reveal ---------- */
.reveal { opacity: 0; transform: translateY(14px); transition: opacity 700ms var(--ease), transform 700ms var(--ease); }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .col.enter { animation: none; }
  .window .peek, .btn { transition: none; }
  .cols { scroll-behavior: auto; }
}
html:not(.js) .reveal { opacity: 1; transform: none; }

/* ---------- small screens ---------- */
@media (max-width: 860px) {
  .details { grid-template-columns: 1fr; }
  .window { --col: min(280px, calc(100vw - 2 * var(--gutter) - 84px)); height: 460px; }
}
@media (max-width: 700px) {
  .hero .peek { position: absolute; bottom: 0; right: var(--gutter); width: clamp(110px, 26vw, 170px); transform: translateY(22%); }
  .hero .lede, .hero .platforms { padding-right: clamp(120px, 29vw, 185px); }
}
@media (max-width: 640px) {
  .bar nav a.hide-sm { display: none; }
  .bar { padding-top: 16px; padding-bottom: 16px; }
  .hero h1 { margin-top: 36px; }
  .hero .lede { margin-top: 20px; }
  .hero .actions { margin-top: 26px; }
  #columns { padding-top: 48px; }
  .product-shot-scroll { overflow-x: auto; border-radius: 10px; }
  .product-shot img { width: 680px; max-width: none; border-radius: 10px; }
  .product-shot figcaption::after { content: " · ← →"; }
  .numbers { grid-template-columns: 1fr; }
  .numbers > div, .numbers > div + div { padding: 24px 0; border-left: 0; }
  .numbers > div + div { border-top: 1px solid var(--line); }
  .details div, html[data-lang="zh"] .details div { grid-template-columns: 1fr; gap: 4px; }
  .rail { width: 48px; }
  .rail .ico { width: 30px; height: 30px; }
  .lights { flex-direction: column; gap: 4px; margin-bottom: 8px; }
  .lights i { width: 8px; height: 8px; }
}
</style>
</head>
<body>
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
  <defs>
    <symbol id="i-house" viewBox="0 0 24 24"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></symbol>
    <symbol id="i-bell" viewBox="0 0 24 24"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></symbol>
    <symbol id="i-bookmark" viewBox="0 0 24 24"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/></symbol>
    <symbol id="i-heart" viewBox="0 0 24 24"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></symbol>
    <symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.34-4.34"/></symbol>
    <symbol id="i-reply" viewBox="0 0 24 24"><path d="M20 18v-2a4 4 0 0 0-4-4H4"/><path d="m9 17-5-5 5-5"/></symbol>
    <symbol id="i-repeat" viewBox="0 0 24 24"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></symbol>
    <symbol id="i-lock" viewBox="0 0 24 24"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
    <symbol id="i-download" viewBox="0 0 24 24"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></symbol>
    <symbol id="blob-body" viewBox="0 0 864 650"><path d="M39 450 L43 464 L46 479 L48 493 L48 508 L48 522 L46 538 L45 554 L42 570 L40 587 L38 605 L37 623 L37 641 L37 650 L721 650 L725 642 L732 629 L740 617 L749 606 L759 594 L770 582 L782 570 L793 558 L805 544 L817 530 L828 516 L838 500 L847 484 L854 467 L859 450 L862 432 L863 414 L862 397 L858 379 L852 362 L844 346 L834 331 L822 317 L810 303 L796 291 L782 280 L767 269 L753 259 L739 250 L726 240 L714 231 L702 221 L691 211 L682 201 L672 190 L664 177 L656 164 L648 150 L640 136 L632 121 L623 105 L614 90 L603 75 L592 60 L579 47 L565 35 L550 25 L534 16 L518 10 L500 6 L483 4 L465 4 L447 7 L429 12 L412 18 L395 26 L379 36 L364 46 L350 56 L336 67 L322 77 L310 86 L297 96 L284 104 L271 111 L258 117 L245 122 L230 126 L216 130 L200 133 L184 136 L167 139 L149 142 L132 147 L114 152 L97 159 L80 167 L65 176 L50 187 L37 200 L26 214 L17 229 L10 245 L5 262 L2 280 L1 298 L2 317 L4 335 L8 353 L13 371 L18 388 L24 404 L29 420 L35 435Z"/></symbol>
  </defs>
</svg>

<header class="wrap bar">
  <a class="brand" href="/"><img src="/icon.svg" alt="" width="28" height="28">Ceramic</a>
  <nav>
    <a class="hide-sm" href="#columns"><span lang="zh">横向展开</span><span lang="en">Columns</span></a>
    <a class="hide-sm" href="#native"><span lang="zh">原生</span><span lang="en">Native</span></a>
    <a href="https://mygo.egoist.dev/">MyGo</a>
    <a href="https://github.com/looechao/ceramic">GitHub</a>
    <button class="lang" type="button" data-switch-lang aria-label="Switch language / 切换语言"><span lang="zh">EN</span><span lang="en">中文</span></button>
  </nav>
</header>

<main>
  <div class="hero">
    <div class="wrap">
      <h1>
        <span lang="zh">给 Mastodon，<br><em>留一点空间。</em></span>
        <span lang="en">A little room<br><em>for Mastodon.</em></span>
      </h1>
    <svg class="blob peek" viewBox="0 0 864 650" role="img" aria-label="Ceramic mascot">
      <use href="#blob-body" class="body"/>
      <g class="eye" data-cx="382" data-cy="340"><circle class="ball" cx="382" cy="340" r="46"/></g>
      <g class="eye" data-cx="212" data-cy="310"><circle class="ball" cx="212" cy="310" r="46"/></g>
    </svg>
      <p class="lede">
        <span lang="zh">一款轻巧的 Mastodon 桌面客户端。连接你的账号，探索 Fediverse。</span>
        <span lang="en">A lightweight Mastodon client for your desktop. Connect your account and explore the Fediverse.</span>
      </p>
      <div class="actions">
        <a class="btn" data-download href="https://github.com/looechao/ceramic/releases/latest">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#i-download"/></svg>
          <span lang="zh" data-download-zh>下载 Ceramic</span><span lang="en" data-download-en>Download Ceramic</span>
        </a>
        <a class="quiet-link" href="https://github.com/looechao/ceramic"><span lang="zh">查看源码 ☆</span><span lang="en">View source ☆</span></a>
      </div>
      <p class="platforms"><span lang="zh">macOS · Windows · Linux · 免费开源</span><span lang="en">macOS · Windows · Linux · Free and open source</span></p>
    </div>
  </div>

  <section id="columns">
    <div class="wrap">
      <figure class="product-shot reveal">
        <div class="product-shot-scroll" tabindex="0" role="region" aria-label="Ceramic screenshot / Ceramic 截图">
          <img src="/ceramic-screenshot-1360.webp" srcset="/ceramic-screenshot-1360.webp 1360w, /ceramic-screenshot-2048.webp 2048w" sizes="(max-width: 640px) 680px, (max-width: 1120px) 90vw, 992px" decoding="async" alt="Ceramic 的真实桌面截图，显示并排打开的多个阅读列" width="5120" height="2830" loading="lazy">
        </div>
        <figcaption><span lang="zh">Ceramic 实际运行画面</span><span lang="en">Ceramic running on a desktop</span></figcaption>
      </figure>
      <p class="kicker reveal"><span lang="zh">横向展开</span><span lang="en">Sideways columns</span></p>
      <h2 class="reveal"><span lang="zh">打开一列，接着往下读。</span><span lang="en">Open a column. Keep reading.</span></h2>
      <p class="section-lede reveal">
        <span lang="zh">点开帖子或个人页，新的一列会出现在右边。之前的内容和阅读位置都留在原处，想回去时随时可见。</span>
        <span lang="en">Open a post or profile and a new column appears to the right. Your earlier columns stay in place, ready whenever you want to return.</span>
      </p>
      <div class="stage reveal">
        <div class="window" id="demo">
          <div class="rail" aria-hidden="true">
            <div class="lights"><i></i><i></i><i></i></div>
            <div class="me"></div>
            <div class="ico on"><svg><use href="#i-house"/></svg></div>
            <div class="ico"><svg><use href="#i-bell"/></svg></div>
            <div class="ico"><svg><use href="#i-bookmark"/></svg></div>
            <div class="ico"><svg><use href="#i-heart"/></svg></div>
            <div class="ico"><svg><use href="#i-search"/></svg></div>
          </div>
          <div class="cols" id="cols"></div>
          <svg class="blob peek away" id="demo-peek" viewBox="0 0 864 650" aria-hidden="true">
            <use href="#blob-body" class="body"/>
            <g class="eye" data-cx="382" data-cy="340"><circle class="ball" cx="382" cy="340" r="46"/></g>
            <g class="eye" data-cx="212" data-cy="310"><circle class="ball" cx="212" cy="310" r="46"/></g>
          </svg>
        </div>
        <div class="stage-foot">
          <span><span lang="zh">点帖子或头像打开新列，也可以点喜欢或书签。空间被列占满时，小人会缩回去。</span><span lang="en">Click a post or an avatar to open a column, or like and bookmark something. When columns fill the space, the little one ducks away.</span></span>
        </div>
      </div>
    </div>
  </section>

  <section id="native">
    <div class="wrap">
      <p class="kicker reveal"><span lang="zh">纯原生</span><span lang="en">Fully native</span></p>
      <h2 class="reveal"><span lang="zh">为桌面而做。</span><span lang="en">Made for the desktop.</span></h2>
      <p class="section-lede reveal">
        <span lang="zh">Ceramic 使用原生绘制的界面，没有内嵌网页。它轻巧，也能跟随系统切换浅色和深色外观。</span>
        <span lang="en">Ceramic draws its interface natively, without an embedded web page. It is lightweight and can follow your system’s light or dark appearance.</span>
      </p>
      <div class="numbers reveal">
        <div>
          <div class="num">4<small>MB</small></div>
          <p><span lang="zh"><b>macOS 安装包。</b>比一张手机照片还小。</span><span lang="en"><b>The macOS download.</b> Smaller than a phone photo.</span></p>
        </div>
        <div>
          <div class="num">0</div>
          <p><span lang="zh"><b>WebView、HTML 页面或 JS。</b>每个像素都是原生组件画的。</span><span lang="en"><b>WebViews, HTML pages or JS.</b> Every pixel comes from native components.</span></p>
        </div>
        <div>
          <div class="num">&lt;1<small>%</small></div>
          <p><span lang="zh"><b>空闲时的 CPU。</b>不看的时候，它就安静待着。</span><span lang="en"><b>CPU while idle.</b> When you look away, it sits still.</span></p>
        </div>
      </div>
      <p class="footnote reveal"><span lang="zh">测于 Apple Silicon Mac，Ceramic 0.1.0，时间线加载完成后静置。</span><span lang="en">Measured on an Apple Silicon Mac with Ceramic 0.1.0, idle after the timeline loaded.</span></p>
    </div>
  </section>

  <section class="end" id="download">
    <div class="wrap">
      <h2 class="reveal"><span lang="zh">带上你的 Mastodon 账号。</span><span lang="en">Bring your Mastodon account.</span></h2>
      <p class="section-lede reveal">
        <span lang="zh">连接你的 Mastodon 账号，探索 Fediverse。在浏览器中授权后即可开始。Ceramic 直接连接你的实例，访问令牌保存在你的电脑上。</span>
        <span lang="en">Connect your Mastodon account and explore the Fediverse. Authorize in your browser to get started. Ceramic connects directly to your instance and stores your access token on your computer.</span>
      </p>
      <div class="actions reveal">
        <a class="btn" data-download href="https://github.com/looechao/ceramic/releases/latest">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><use href="#i-download"/></svg>
          <span lang="zh" data-download-zh>下载最新版本</span><span lang="en" data-download-en>Download the latest release</span>
        </a>
      </div>
      <p class="platforms reveal"><a href="https://github.com/looechao/ceramic/releases/latest"><span lang="zh">查看所有下载</span><span lang="en">View all downloads</span></a><br>
        <span lang="zh">macOS（Apple Silicon 与 Intel）· Windows · Linux（需要 GTK 3）<br>尚未签名：macOS 首次打开需在“系统设置 › 隐私与安全性”中允许。</span>
        <span lang="en">macOS (Apple Silicon and Intel) · Windows · Linux (needs GTK 3)<br>Not yet signed: on first launch, allow it in System Settings › Privacy &amp; Security.</span>
      </p>
    </div>
  </section>
</main>

<footer>
  <div class="wrap">
    <span>Ceramic · MIT · Powered by <a href="https://mygo.egoist.dev/">MyGo</a></span>
    <span><a href="https://github.com/looechao/ceramic">GitHub</a> <a href="https://github.com/looechao/ceramic/releases">Releases</a></span>
  </div>
</footer>

<script>
(function () {
  var root = document.documentElement;
  root.classList.add("js");
  var reduce = window.matchMedia("(prefers-reduced-motion: reduce)");

  /* ---------- language ---------- */
  document.querySelectorAll("[data-switch-lang]").forEach(function (b) {
    b.addEventListener("click", function () {
      var next = root.dataset.lang === "zh" ? "en" : "zh";
      root.dataset.lang = next;
      root.lang = next === "zh" ? "zh-CN" : "en";
      try { localStorage.setItem("ceramic-lang", next); } catch (e) {}
    });
  });

  /* ---------- latest release for this device ---------- */
  (async function () {
    var ua = navigator.userAgent;
    if (/Android|iPhone|iPad|iPod/i.test(ua) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)) return;
    var os = /Windows/i.test(ua) ? "windows" : /Macintosh/i.test(ua) ? "darwin" : /Linux/i.test(ua) ? "linux" : "";
    if (!os) return;
    var arch = "";
    if (navigator.userAgentData && navigator.userAgentData.getHighEntropyValues) {
      try {
        var info = await navigator.userAgentData.getHighEntropyValues(["architecture", "bitness", "wow64"]);
        if (/arm/i.test(info.architecture) && info.bitness === "64") arch = "arm64";
        if (/^(x86|x86_64|amd64)$/i.test(info.architecture) && info.bitness === "64" && !info.wow64) arch = "amd64";
      } catch (e) {}
    }
    // Linux user agents can explicitly report their architecture. macOS user
    // agents may claim Intel on Apple Silicon, so they are not used for this.
    if (!arch && os === "linux") {
      if (/aarch64|arm64/i.test(ua)) arch = "arm64";
      else if (/x86_64|amd64/i.test(ua)) arch = "amd64";
    }
    if (!arch) return;
    try {
      var response = await fetch("https://api.github.com/repos/looechao/ceramic/releases/latest");
      if (!response.ok) return;
      var release = await response.json();
      if (!Array.isArray(release.assets)) return;
      var names = arch === "amd64" ? /(?:amd64|x86_64|x64)/i : /(?:arm64|aarch64)/i;
      var suffix = os === "darwin" ? /\.dmg$/i : os === "windows" ? /-setup-.*\.exe$/i : /\.deb$/i;
      var assets = release.assets.filter(function (a) {
        return typeof a.name === "string" && names.test(a.name) && suffix.test(a.name) &&
          typeof a.browser_download_url === "string" && a.browser_download_url.indexOf("https://github.com/looechao/ceramic/releases/download/") === 0;
      });
      if (assets.length !== 1) return;
      var platform = os === "darwin" ? "macOS" : os === "windows" ? "Windows" : "Linux";
      var chip = os === "darwin" ? (arch === "arm64" ? "Apple Silicon" : "Intel") : (arch === "arm64" ? "ARM64" : "x64");
      document.querySelectorAll("[data-download]").forEach(function (link) {
        link.href = assets[0].browser_download_url;
        link.dataset.platform = os;
        link.dataset.architecture = arch;
        link.querySelector("[data-download-zh]").textContent = "下载 " + platform + "（" + chip + "）";
        link.querySelector("[data-download-en]").textContent = "Download for " + platform + " (" + chip + ")";
      });
    } catch (e) {}
  })();

  /* ---------- eyes follow the pointer ---------- */
  var eyes = Array.prototype.slice.call(document.querySelectorAll(".blob .eye"));
  var pointer = null, queued = false;
  function look() {
    queued = false;
    if (!pointer) return;
    eyes.forEach(function (g) {
      var svg = g.ownerSVGElement, box = svg.getBoundingClientRect();
      if (!box.width || box.bottom < 0 || box.top > innerHeight) return;
      var unit = box.width / 864;
      var cx = box.left + g.dataset.cx * unit, cy = box.top + g.dataset.cy * unit;
      var vx = pointer.x - cx, vy = pointer.y - cy, dist = Math.hypot(vx, vy);
      var reach = dist > 1 ? 46 * 0.55 * Math.min(1, dist / 240) : 0;
      var dx = dist > 1 ? vx / dist * reach : 0, dy = dist > 1 ? vy / dist * reach : 0;
      g.setAttribute("transform", "translate(" + dx.toFixed(1) + " " + dy.toFixed(1) + ")");
    });
  }
  function track(x, y) {
    pointer = { x: x, y: y };
    if (!queued) { queued = true; requestAnimationFrame(look); }
  }
  if (!reduce.matches) {
    addEventListener("pointermove", function (e) { track(e.clientX, e.clientY); }, { passive: true });
    addEventListener("scroll", function () { if (pointer) track(pointer.x, pointer.y); }, { passive: true });
  }

  /* ---------- reveal on scroll ---------- */
  if ("IntersectionObserver" in window && !reduce.matches) {
    var io = new IntersectionObserver(function (entries) {
      entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } });
    }, { rootMargin: "0px 0px -8% 0px" });
    document.querySelectorAll(".reveal").forEach(function (el) { io.observe(el); });
  } else {
    document.querySelectorAll(".reveal").forEach(function (el) { el.classList.add("in"); });
  }

  /* ---------- the demo window ---------- */
  var cols = document.getElementById("cols");
  var peek = document.getElementById("demo-peek");
  var people = {
    forest: { zh: "林间散步", en: "Forest Walk", handle: "@forest@example.social", color: "#c46b55" },
    tide:   { zh: "潮汐", en: "Tidewater", handle: "@tide@example.social", color: "#4f89b7" },
    kiln:   { zh: "窑火", en: "Kiln", handle: "@kiln@example.art", color: "#6a9a6d" },
    moth:   { zh: "灯下飞蛾", en: "Moth", handle: "@moth@example.town", color: "#9263c7" }
  };
  var timeline = [
    { who: "forest", when: "2m", likes: 4, zh: "今天把桌面上的窗口都关了，只留下 Mastodon。屏幕突然变得很安静。", en: "Closed every window on my desk today except Mastodon. The screen went very quiet.", thread: true },
    { who: "kiln", when: "14m", tint: "boost", likes: 12, boosts: 3, zh: "新出窑的一批杯子，釉色比预想的还要沉一点。", en: "A new batch out of the kiln. The glaze came out a little deeper than planned." },
    { who: "tide", when: "31m", tint: "warn", cw: true, zh: "内容警告：剧透", en: "CW: spoilers" },
    { who: "moth", when: "1h", tint: "reply", zh: "同意，横着排列的讨论串比层层嵌套好读多了。", en: "Agreed. Threads laid out sideways read far better than nested replies." },
    { who: "forest", when: "2h", zh: "傍晚的光从西边窗户斜进来，正好落在键盘上。", en: "Evening light slants through the west window and lands right on the keyboard." }
  ];
  var thread = [
    { who: "forest", when: "2m", zh: "今天把桌面上的窗口都关了，只留下 Mastodon。屏幕突然变得很安静。", en: "Closed every window on my desk today except Mastodon. The screen went very quiet." },
    { who: "tide", when: "1m", tint: "reply", zh: "我也是，右边留白的时候还有个小家伙在看着我。", en: "Same here. When the right side is empty, a little someone watches me." },
    { who: "kiln", when: "now", tint: "reply", zh: "它眨眼的时候我笑出声了。", en: "I laughed out loud when it blinked." }
  ];
  function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]; }); }
  function both(o) { return '<span lang="zh">' + esc(o.zh) + '</span><span lang="en">' + esc(o.en) + "</span>"; }
  // Interactions are shared by every copy of a post, as in the app.
  var state = {};
  function id(p) { return p.who + "-" + p.when; }
  function acts(p) {
    var st = state[id(p)] || (state[id(p)] = { boost: false, like: false, mark: false, likes: p.likes || 0, boosts: p.boosts || 0 });
    function b(kind, icon, label, count, extra) {
      var on = st[kind];
      return '<button type="button" class="' + extra + (on ? " on" : "") + '" data-act="' + kind + '" aria-pressed="' + on + '" aria-label="' + label + '"><svg><use href="#' + icon + '"/></svg>' + (count ? "<span>" + count + "</span>" : "") + "</button>";
    }
    return '<div class="acts" data-id="' + id(p) + '">' +
      '<button type="button" data-act="reply" aria-label="Reply"><svg><use href="#i-reply"/></svg></button>' +
      b("boost", "i-repeat", "Boost", st.boosts + (st.boost ? 1 : 0), "") +
      b("like", "i-heart", "Like", st.likes + (st.like ? 1 : 0), "fill") +
      b("mark", "i-bookmark", "Bookmark", 0, "end") + "</div>";
  }
  function toggle(btn) {
    var row = btn.closest(".acts"), kind = btn.dataset.act, st = state[row.dataset.id];
    st[kind] = !st[kind];
    cols.querySelectorAll('.acts[data-id="' + row.dataset.id + '"]').forEach(function (r) {
      var p = r.closest(".post");
      r.outerHTML = acts({ who: p.dataset.who, when: p.dataset.when });
    });
    if (st[kind] && !reduce.matches) {
      cols.querySelectorAll('.acts[data-id="' + row.dataset.id + '"] [data-act="' + kind + '"]').forEach(function (b) { b.classList.add("pop"); });
    }
  }
  function post(p, i) {
    var u = people[p.who];
    return '<div class="post' + (p.tint ? " tint-" + p.tint : "") + '" data-i="' + i + '" data-who="' + p.who + '" data-when="' + p.when + '">' +
      '<div class="av" data-who="' + p.who + '" style="background:' + u.color + '"></div><div>' +
      '<div class="who"><span><b>' + both(u) + '</b><br><span class="handle">' + u.handle + '</span></span><span class="when">' + p.when + "</span></div>" +
      (p.cw ? "<p><b>" + both(p) + '</b></p><div class="cw"></div>' : "<p>" + both(p) + "</p>") + acts(p) + "</div></div>";
  }
  function column(key, title, body, closable) {
    var el = document.createElement("div");
    el.className = "col";
    el.dataset.key = key;
    el.innerHTML = "<header>" + (closable ? '<button type="button" aria-label="Close">×</button>' : "") + "<span>" + title + "</span></header>" + body;
    return el;
  }
  function home() {
    cols.innerHTML = "";
    cols.appendChild(column("home", both({ zh: "首页", en: "Home" }), timeline.map(post).join(""), false));
    layout();
  }
  function open(key, title, body) {
    var existing = cols.querySelector('[data-key="' + key + '"]');
    if (existing) { focus(existing); existing.scrollIntoView({ behavior: reduce.matches ? "auto" : "smooth", inline: "nearest", block: "nearest" }); return; }
    var el = column(key, title, body, true);
    el.classList.add("enter");
    cols.appendChild(el);
    focus(el);
    layout();
    requestAnimationFrame(function () { cols.scrollLeft = cols.scrollWidth; });
  }
  function focus(el) {
    cols.querySelectorAll(".col").forEach(function (c) { c.classList.toggle("focus", c === el && cols.children.length > 1); });
  }
  function profile(who) {
    var u = people[who];
    var mine = timeline.filter(function (p) { return p.who === who; }).concat(thread.filter(function (p) { return p.who === who; })).slice(0, 2);
    return '<div class="profile-head"></div><div class="profile-id"><div class="av" style="background:' + u.color + '"></div><b>' + both(u) + "</b><span>" + u.handle + "</span></div>" + mine.map(post).join("");
  }
  // Show the blob only while enough of the window is free, as the app does.
  function layout() {
    var last = cols.lastElementChild;
    if (!last) return;
    var used = last.getBoundingClientRect().right - cols.getBoundingClientRect().left + 10;
    var free = cols.clientWidth - used;
    var w = peek.getBoundingClientRect().width || 132;
    var room = free >= w + 60;
    peek.classList.toggle("away", !room);
    if (room) {
      var left = cols.offsetLeft + used + Math.min(free * 0.75, free - w / 2 - 12) - w / 2;
      peek.style.left = left + "px";
    }
  }
  cols.addEventListener("click", function (e) {
    var close = e.target.closest("header button");
    if (close) { close.closest(".col").remove(); focus(cols.lastElementChild); layout(); return; }
    var act = e.target.closest(".acts button");
    if (act) {
      if (act.dataset.act === "reply") open("thread", both({ zh: "讨论串", en: "Thread" }), thread.map(post).join(""));
      else toggle(act);
      return;
    }
    var av = e.target.closest(".av[data-who]");
    if (av) { var who = av.dataset.who; open("p-" + who, both(people[who]), profile(who)); return; }
    var p = e.target.closest(".post");
    if (p) open("thread", both({ zh: "讨论串", en: "Thread" }), thread.map(post).join(""));
  });
  cols.addEventListener("scroll", layout, { passive: true });
  addEventListener("resize", layout);
  home();

  // Play the idea once when the window first comes into view.
  if ("IntersectionObserver" in window && !reduce.matches) {
    var played = false;
    var demoIO = new IntersectionObserver(function (entries) {
      if (played || !entries[0].isIntersecting) return;
      played = true;
      demoIO.disconnect();
      setTimeout(function () { var like = cols.querySelector('.post [data-act="like"]'); if (like && cols.children.length === 1 && !like.classList.contains("on")) like.click(); }, 900);
      setTimeout(function () { if (cols.children.length === 1) cols.querySelector(".post p").click(); }, 2000);
      setTimeout(function () { if (cols.children.length === 2) cols.querySelector('.col[data-key="thread"] .post:nth-of-type(2) .av').click(); }, 3800);
    }, { threshold: 0.6 });
    demoIO.observe(document.getElementById("demo"));
  }
})();
</script>
</body>
</html>
