/* TwinBuild. A quiet page: the app icons, one line, then one row per app.
   It takes the system's light or dark appearance; the button in the bar overrides it
   (data-theme on <html>). */

:root {
  --bg: #ffffff;
  --card: #f5f6f8;
  --card-hover: #eef0f3;
  --raised: #ffffff;
  --ink: #1d1d1f;
  --ink-2: #6e6e73;
  --line: rgba(0, 0, 0, .08);
  --link: #0066cc;
  --bar: rgba(255, 255, 255, .76);
  --menu: #ffffff;
  --ground: rgba(0, 0, 0, .16);
  --knob: #1b1f2a;
  --ease: cubic-bezier(.22, 1, .36, 1);
  color-scheme: light;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #000000;
    --card: #161617;
    --card-hover: #1e1e20;
    --raised: #3a3a3c;
    --ink: #f5f5f7;
    --ink-2: #a1a1a6;
    --line: rgba(255, 255, 255, .12);
    --link: #2997ff;
    --bar: rgba(0, 0, 0, .7);
  --menu: #2c2c2e;
    --ground: rgba(255, 255, 255, .1);
    --knob: #f5f5f7;
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --bg: #000000;
  --card: #161617;
  --card-hover: #1e1e20;
  --raised: #3a3a3c;
  --ink: #f5f5f7;
  --ink-2: #a1a1a6;
  --line: rgba(255, 255, 255, .12);
  --link: #2997ff;
  --bar: rgba(0, 0, 0, .7);
  --menu: #2c2c2e;
  --ground: rgba(255, 255, 255, .1);
  --knob: #f5f5f7;
  color-scheme: dark;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 5rem; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 100%/1.5 -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang TC", "Noto Sans TC", "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Simplified Chinese takes the mainland forms of the shared characters. */
:lang(zh-Hans) body { font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
h1, h2, p { margin: 0; }
:focus-visible { outline: 3px solid var(--link); outline-offset: 3px; border-radius: 10px; }

.wrap { width: min(100% - 2rem, 72rem); margin-inline: auto; }
.skip { position: absolute; left: 1rem; top: -4rem; z-index: 20; padding: .5rem .9rem; border-radius: 999px; background: var(--ink); color: var(--bg); }
.skip:focus { top: .6rem; }

/* The bar: a translucent layer, the page scrolls under it. */
.nav {
  position: sticky; top: 0; z-index: 10;
  background: var(--bar);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  padding-top: env(safe-area-inset-top);
}
.nav-in { width: min(100% - 2rem, 72rem); margin-inline: auto; height: 4rem; display: flex; align-items: center; gap: .75rem; }
.brand { display: flex; align-items: center; gap: .5rem; font-weight: 700; font-size: 1.25rem; letter-spacing: -.02em; }
/* The mark is wider than tall (the brim). Its outline is near-black and goes unseen on a dark
   bar, where the white face carries it; the button on the cap would vanish, so it turns light. */
.logo { display: block; width: 2.5rem; height: auto; aspect-ratio: 1000 / 746; }
.knob { fill: var(--knob); }
/* The smallest phones: the bar has no room to spare, the mark gives some back. */
@media (max-width: 22.49rem) { .brand .logo { width: 1.75rem; } }
.tag { display: none; color: var(--ink-2); font-size: .9375rem; }
.tag::before { content: "·"; margin-right: .75rem; }
.nav-tools { display: flex; align-items: center; gap: .5rem; margin-left: auto; }
.tool {
  min-width: 2.5rem; height: 2.5rem; padding: 0 1.1rem;
  border: 0; border-radius: 999px; background: var(--card); color: var(--ink);
  font: inherit; font-size: .9375rem; font-weight: 500; cursor: pointer;
  display: grid; place-items: center;
  transition: transform .1s ease-out, background-color .15s;
}
.tool:hover { background: var(--card-hover); }
.tool:active { transform: scale(.95); }
.tool.icon { padding: 0; }
.tool svg { width: 1.125rem; height: 1.125rem; grid-area: 1 / 1; }
.nav .contact-link { display: none; }
.moon { display: none; }
:root[data-theme="dark"] .sun { display: none; }
:root[data-theme="dark"] .moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sun { display: none; }
  :root:not([data-theme="light"]) .moon { display: block; }
}
/* The languages: a plain menu under its button, the current one ticked. */
.langs { position: relative; }
.langs summary { display: flex; gap: .4rem; padding: 0 .85rem; list-style: none; -webkit-user-select: none; user-select: none; }
.langs summary::-webkit-details-marker { display: none; }
.langs summary svg { grid-area: auto; flex: none; }
.langs .chev { width: .625rem; height: .375rem; }
.langs[open] .chev { rotate: 180deg; }
.langs nav {
  position: absolute; right: 0; top: calc(100% + .375rem); z-index: 20; min-width: 10rem; padding: .25rem;
  border-radius: .875rem; background: var(--menu);
  box-shadow: 0 0 0 1px var(--line), 0 12px 28px -10px rgba(0, 0, 0, .3);
}
.langs a {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  min-height: 2.5rem; padding: 0 .75rem; border-radius: .625rem; font-size: .9375rem;
}
.langs a:hover, .langs a:focus-visible { background: var(--card); }
.langs a[aria-current="page"] { font-weight: 600; }
.langs a[aria-current="page"]::after { content: ""; width: .7rem; height: .4rem; border: solid currentColor; border-width: 0 0 2px 2px; rotate: -45deg; translate: 0 -2px; }
/* On a narrow phone the button is the globe alone. */
@media (max-width: 26rem) { .langs summary span { display: none; } }
@media (min-width: 48rem) {
  .nav-in { height: 5.5rem; }
  .tag { display: block; }
  .nav .contact-link { display: grid; }
}

/* Hero: the icons of what we make, with the mark as one of them, then one line. The dots are
   flat and in the mark's own blue and red (the first ones were shaded balls and a star). */
.hero { text-align: center; padding: clamp(1.25rem, 3vw, 2rem) 0 clamp(3rem, 8vw, 6rem); }
.cluster { --s: clamp(6.75rem, 20vw, 10rem); position: relative; width: calc(var(--s) * 2.7); height: calc(var(--s) * 1.95); margin: 0 auto clamp(1.75rem, 4vw, 2.75rem); }
.ic {
  position: absolute; width: var(--s); height: var(--s); border-radius: 22.5%;
  box-shadow: 0 0 0 1px var(--line), 0 calc(var(--s) * .14) calc(var(--s) * .22) calc(var(--s) * -.1) rgba(0, 0, 0, .4);
}
.ic-tb { left: calc(50% - var(--s) / 2); top: 0; rotate: 3deg; }
.ic-bb { left: 6%; top: 37%; rotate: -9deg; }
.ic-tc { right: 6%; top: 33%; rotate: 8deg; }
.ic .knob { fill: #1b1f2a; }
.ground { position: absolute; left: 12%; right: 12%; bottom: 0; height: 8%; border-radius: 50%; background: radial-gradient(closest-side, var(--ground), transparent); }
.dot { position: absolute; border-radius: 50%; aspect-ratio: 1; }
/* With the flight (app.js): the hero fills the first screen so there is a scroll to fly over,
   the rows wait without their icons, and the cluster lets clicks through to the row under it. */
.fly .hero { min-height: calc(100svh - 5.5rem); display: flex; flex-direction: column; justify-content: center; }
.fly .cluster { z-index: 2; pointer-events: none; }
.fly [data-land] { visibility: hidden; }
.fly .row.reveal { transform: none; }
.d1 { width: 5.5%; left: 3%; top: 20%; background: #2b4fc7; }
.d2 { width: 4%; right: 5%; top: 14%; background: #e0483a; }
.display {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang TC", "Noto Sans TC", "Helvetica Neue", Arial, sans-serif;
  font-size: clamp(1.875rem, 4.6vw, 3rem);
  line-height: 1.15; letter-spacing: -.022em; font-weight: 600;
  text-wrap: balance; max-width: 18em; margin-inline: auto;
}
:lang(zh-Hans) .display { font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; }
:lang(zh) .display { letter-spacing: -.005em; line-height: 1.25; }
/* Chinese breaks only at punctuation and spaces: a phrase is not cut in two. */
:lang(zh) :is(.display, .title, .row h2) { word-break: keep-all; overflow-wrap: anywhere; }
.lede { margin: .75rem auto 0; max-width: 32em; font-size: clamp(1rem, 2vw, 1.1875rem); color: var(--ink-2); text-wrap: balance; }
/* Who we are: beside the name in the bar where there is room, under the line on a phone. */
.who { margin-top: 1.25rem; color: var(--ink-2); font-size: .875rem; }
@media (min-width: 48rem) { .who { display: none; } }

/* The apps: one row each. The whole row opens the app's website. */
.apps { display: grid; gap: .625rem; }
.row {
  position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.25rem;
  padding: 1.25rem; border-radius: 1.75rem; background: var(--card);
  transition: background-color .2s;
}
.row:hover { background: var(--card-hover); }
.row-icon { flex: none; width: 4rem; height: 4rem; border-radius: 22.5%; box-shadow: 0 0 0 1px var(--line), 0 8px 18px -10px rgba(0, 0, 0, .5); }
.row-text { flex: 1; min-width: 0; }
.row h2 { font-size: 1.1875rem; line-height: 1.25; letter-spacing: -.012em; font-weight: 700; }
.row-text p { margin-top: .2rem; color: var(--ink-2); font-size: .9375rem; line-height: 1.45; text-wrap: pretty; }
/* The gap is the least there is: the English button and link just fit a 390 px phone. */
.row-actions { flex-basis: 100%; display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
/* A row with the website link alone keeps it beside the text, also on a phone. */
.row-actions:not(:has(.get)) { flex-basis: auto; }
.get {
  position: relative; z-index: 1; display: inline-flex; align-items: center; min-height: 2.5rem; padding: 0 1.15rem;
  border-radius: 999px; background: var(--ink); color: var(--bg); font-size: .9375rem; font-weight: 600; white-space: nowrap;
  transition: transform .1s ease-out;
}
.get:active { transform: scale(.96); }
.more { display: inline-flex; align-items: center; gap: .5rem; min-height: 2.5rem; margin-left: auto; color: var(--ink-2); font-size: .9375rem; }
.more::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.more svg { width: .5rem; height: .75rem; transition: transform .25s var(--ease); }
.row:hover .more { color: var(--ink); }
.row:hover .more svg { transform: translateX(3px); }
.divider { padding: 1.5rem 0 1rem; text-align: center; color: var(--ink-2); font-size: .9375rem; }
.divider::before { content: "·"; margin-right: .5rem; }
@media (min-width: 48rem) {
  .row { flex-wrap: nowrap; gap: 1.5rem; padding: 1.75rem 2.25rem 1.75rem 1.75rem; border-radius: 2rem; }
  .row-icon { width: 5rem; height: 5rem; }
  .row h2 { font-size: 1.375rem; }
  .row-text p { font-size: 1rem; }
  .row-actions { flex-basis: auto; gap: 1.75rem; }
}

/* Contact */
.contact { padding: clamp(2.5rem, 6vw, 4.5rem) 0 clamp(2.5rem, 6vw, 4rem); }
.contact-card { text-align: center; padding: clamp(2.5rem, 6vw, 4rem) 1.5rem; border-radius: 2rem; background: var(--card); }
.title { font-size: clamp(1.5rem, 3.4vw, 2rem); line-height: 1.15; letter-spacing: -.018em; font-weight: 700; }
.contact-card p { margin: .6rem auto 0; max-width: 28em; color: var(--ink-2); text-wrap: balance; }
.button {
  display: inline-flex; align-items: center; margin-top: 1.5rem; min-height: 2.75rem; padding: 0 1.5rem;
  border-radius: 999px; background: var(--ink); color: var(--bg); font-weight: 600; font-size: .9375rem;
  transition: transform .1s ease-out;
}
.button:active { transform: scale(.97); }

.foot { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .625rem; padding-bottom: calc(2rem + env(safe-area-inset-bottom)); font-size: .875rem; }
.pill { display: flex; align-items: center; gap: 1.25rem; min-height: 2.5rem; padding: 0 1.1rem; border-radius: 999px; background: var(--card); }
.legal { flex-basis: 100%; margin-top: .5rem; color: var(--ink-2); font-size: .75rem; }

/* Motion: the page comes in once, the rest as it is scrolled to. */
/* The entrance, once app.js has set `enter` on <html> (until then its parts are not shown):
   the bar comes down, the icons pop up one after the other with a little spring, the dots
   after them, then the line comes into focus. Each keyframe names only where a part comes
   from; it ends as it is laid out. */
.js:not(.enter):not(.calm) :is(.arrive, .ic, .dot, .rise) { opacity: 0; }
.js:not(.enter):not(.calm) .ground { scale: 0 1; }
@keyframes arrive { from { opacity: 0; translate: 0 -14px; } }
@keyframes pop { from { opacity: 0; scale: .35; translate: 0 34px; } }
@keyframes dot { from { opacity: 0; scale: 0; } }
@keyframes spread { from { scale: 0 1; } }
@keyframes rise { from { opacity: 0; translate: 0 18px; filter: blur(10px); } }
.enter .arrive { animation: arrive .7s var(--ease) backwards; }
.enter .ic { animation: pop .85s cubic-bezier(.3, 1.45, .5, 1) backwards; }
.enter .ic-tb { animation-delay: .1s; }
.enter .ic-bb { animation-delay: .2s; }
.enter .ic-tc { animation-delay: .3s; }
.enter .ground { animation: spread .7s var(--ease) .25s backwards; }
.enter .dot { animation: dot .6s cubic-bezier(.3, 1.6, .5, 1) .7s backwards; }
.enter .d2 { animation-delay: .8s; }
.enter .rise { animation: rise .9s var(--ease) .55s backwards; }
.enter .lede.rise { animation-delay: .68s; }
.enter .who.rise { animation-delay: .8s; }
.js .reveal { opacity: 0; transform: translateY(20px); transition: opacity .8s var(--ease), transform .8s var(--ease), background-color .2s; }
.js .reveal.in { opacity: 1; transform: none; }

/* From the language switch: nothing enters again, and the two pages cross-fade. */
.js.calm :is(.arrive, .ic, .dot, .ground, .rise) { animation: none; }
.js.calm .reveal { opacity: 1; transform: none; transition: background-color .2s; }
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .35s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js :is(.arrive, .ic, .dot, .ground, .rise) { animation: none; opacity: 1; scale: none; }
  .js .reveal { transform: none; transition: opacity .2s ease; }
  .more svg, .tool, .get, .button { transition: none; }
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .nav { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  :root { --ink-2: var(--ink); }
  .nav { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .row, .contact-card, .pill, .tool, .langs { box-shadow: inset 0 0 0 1px currentColor; }
}
