/* ninon.dev: Smartisan's objects in a phone of now.
   Old: the icons and keys, drawn the early Smartisan way and lit from above.
   New: the room and the apps. A liquid-glass island, white app windows with continuous corners,
   today's type and spacing, and motion on springs. */

@property --flift { syntax: "<number>"; inherits: true; initial-value: 0; }

:root {
  --ground: #ebebee;
  --ink: #1d1d1f;
  --ink-2: #48484d;
  --label: #2b2b2e;
  --cap: #fff;
  --cap-lo: #efeff2;
  --skirt: #c6c6cc;
  --focus: #b51a12;
  --icon: 112px;
  --cell: 136px;
  --pad: clamp(16px, 4vw, 48px);
  --spring: linear(0, 0.056, 0.188, 0.355, 0.525, 0.681, 0.811, 0.911, 0.983, 1.03, 1.056, 1.066, 1.066, 1.059, 1.048, 1.037, 1.026, 1.016, 1.009, 1.003, 0.999, 0.997, 0.996, 0.995, 0.996, 0.996, 0.997, 0.998, 0.999, 0.999, 1, 1, 1, 1, 1, 1, 1);
  --spring-soft: linear(0, 0.039, 0.128, 0.241, 0.36, 0.473, 0.575, 0.663, 0.738, 0.799, 0.848, 0.887, 0.918, 0.941, 0.958, 0.971, 0.981, 0.988, 0.992, 0.996, 0.998, 0.999, 1, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1, 1, 1, 1, 1, 1, 1);
  --spring-fly: linear(0, 0.048, 0.16, 0.302, 0.449, 0.587, 0.706, 0.805, 0.882, 0.939, 0.978, 1.005, 1.02, 1.028, 1.03, 1.029, 1.025, 1.021, 1.017, 1.013, 1.009, 1.006, 1.004, 1.002, 1.001, 1, 0.999, 0.999, 0.999, 0.999, 0.999, 0.999, 0.999, 1, 1, 1, 1);
  color-scheme: light;
}

* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 72px;
  scrollbar-color: #a9a9ae var(--ground);
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ground);
  color: var(--ink);
  font: 400 17px/1.55 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "Segoe UI", Roboto, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Light from above the screen. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(140% 85% at 50% -25%, rgb(255 255 255 / 0.8), transparent 62%);
}

::selection { background: rgb(219 48 38 / 0.2); }

a { color: inherit; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 6px; }
[tabindex="-1"]:focus { outline: none; }

.defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* The island: the one piece of glass. Your name, and the app you are in.
   Chromium bends the page under it (home.js draws the lens); other browsers frost it a little. */
.island {
  position: fixed;
  top: 14px;
  left: 50%;
  z-index: 30;
  translate: -50% 0;
  display: flex;
  align-items: center;
  height: 42px;
  padding: 0 18px;
  border-radius: 21px;
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.01em;
  white-space: nowrap;
  background: rgb(255 255 255 / 0.5);
  -webkit-backdrop-filter: blur(10px) saturate(1.8);
  backdrop-filter: blur(10px) saturate(1.8);
  box-shadow:
    0 0 0 0.5px rgb(0 0 0 / 0.07),
    0 1px 2px rgb(0 0 0 / 0.1),
    0 12px 28px -12px rgb(20 20 30 / 0.34);
  view-transition-name: island;
  cursor: pointer;
}
.island.is-liquid {
  background: rgb(255 255 255 / 0.42);
  backdrop-filter: url(#lg);
}
/* Rim light: bright where the light from the upper left hits the curve, faint on the far side. */
.island::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(160deg, rgb(255 255 255 / 0.95), rgb(255 255 255 / 0.18) 38%, rgb(255 255 255 / 0.06) 62%, rgb(255 255 255 / 0.55));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}
/* The island takes a touch a little beyond its edge too, under its name so the link still gets the tap. */
.island::after { content: ""; position: absolute; inset: -3px -2px; z-index: -1; }
.island h1 { margin: 0; font: inherit; }
.island h1 a { text-decoration: none; }
.island-app {
  display: inline-flex;
  max-width: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-width 0.6s var(--spring), opacity 0.25s;
}
.island-app-in {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 7px;
  padding-left: 12px;
  color: var(--ink-2);
}
.island-app-in::before { content: "/"; margin-right: 5px; color: #6e6e73; font-weight: 400; }
.island-app .mini { width: 20px; height: 20px; filter: drop-shadow(0 0.5px 0.5px rgb(0 0 0 / 0.3)); }
.island.has-app .island-app { max-width: var(--w, 0px); opacity: 1; }

/* Home: your words on the left, the works as apps on the right. It stops short of the screen's foot,
   so the top of the Zaotai sheet, its clip, keys and title, is already in the first screen. */
.home {
  position: relative;
  z-index: 1;
  max-width: 1240px;
  min-height: min(calc(100svh - 160px), 1000px);
  margin: 0 auto;
  padding: 108px var(--pad) 72px;
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr);
  gap: clamp(40px, 6vw, 96px);
  align-items: center;
}

.note { margin: 0; max-width: 34rem; }
.note p { margin: 0; }
.note .first {
  font-size: clamp(31px, 1.1rem + 2.3vw, 50px);
  line-height: 1.07;
  font-weight: 650;
  letter-spacing: -0.028em;
  text-wrap: balance;
}
.note .second {
  margin-top: 0.85em;
  font-size: clamp(19px, 0.95rem + 0.65vw, 24px);
  line-height: 1.3;
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--ink-2);
  text-wrap: pretty;
}
.note .bio {
  margin-top: 1.5em;
  max-width: 60ch;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* Launcher: three across, nine cells, like the phone. */
.launcher {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, var(--cell));
  justify-content: center;
  row-gap: clamp(18px, 3vh, 30px);
}

.app-icon {
  --lift: 0;
  --press: 0;
  --tx: 0;
  --ty: 0;
  --shine: 0;
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 10px;
  padding: 4px 0;
  color: var(--label);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  transition: --flift 0.5s var(--spring);
}
.app-icon:is(:hover, :focus-visible) { --flift: 1; }
.app-icon:active { --press: 1; }
/* Picked up (home.js): lifted off the glass and above the others. */
.app-icon.held { --flift: 1; --press: 0; z-index: 3; cursor: grabbing; }
.app-icon:focus-visible { outline: none; }

.app-icon .tile {
  --up: max(var(--lift), var(--flift));
  position: relative;
  width: var(--icon);
  aspect-ratio: 1;
  transform:
    perspective(640px)
    translateY(calc(var(--up) * -7px + var(--press) * 1.5px))
    rotateX(calc(var(--ty) * 8deg))
    rotateY(calc(var(--tx) * -8deg))
    scale(calc(1 + var(--up) * 0.075 - var(--press) * 0.045));
  will-change: transform;
}
/* The light on the glaze. It sits at the top; on the slope of the bump it slides to the raised side. */
.app-icon .tile::before {
  content: "";
  position: absolute;
  inset: 0;
  clip-path: url(#tile-box);
  background: radial-gradient(circle at calc(50% + var(--tx) * 34%) calc(4% + var(--ty) * 30%),
    rgb(255 255 255 / calc(0.12 + var(--shine) * 0.3)), rgb(255 255 255 / 0) 60%);
  pointer-events: none;
}
.app-icon .tile svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  filter:
    drop-shadow(0 1px 1px rgb(0 0 0 / 0.34))
    drop-shadow(0 calc(4px + var(--up) * 8px) calc(6px + var(--up) * 9px) rgb(25 25 35 / calc(0.18 + var(--up) * 0.08)))
    brightness(calc(1 - var(--press) * 0.18));
}
.app-icon:focus-visible .tile::after {
  content: "";
  position: absolute;
  inset: -5px;
  border: 2px solid var(--focus);
  border-radius: 30%;
}

/* The About icon is a bookshelf. The book under the pointer comes up off the shelf (home.js picks it),
   and with the keyboard on the icon, the ochre one does. The gap it leaves is the dark back of the shelf. */
#ic-about .slot { fill: #0a0402; opacity: 0; transition: opacity 0s 0.5s; }
#ic-about .spine { transition: transform 0.45s var(--spring-soft); }
#ic-about .book.up .slot,
.app-icon:focus-visible #ic-about .book:nth-of-type(4) .slot { opacity: 1; transition: none; }
#ic-about .book.up .spine,
.app-icon:focus-visible #ic-about .book:nth-of-type(4) .spine { transform: translateY(-6px); transition: transform 0.5s var(--spring); }
/* Picked up to move, every book is set back, the focused one too. */
.app-icon.held #ic-about .book:nth-of-type(n) .spine { transform: none; transition: transform 0.45s var(--spring-soft); }
.app-icon.held #ic-about .book:nth-of-type(n) .slot { opacity: 0; transition: opacity 0s 0.5s; }

.app-icon .label {
  max-width: 100%;
  font-size: 13px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0.005em;
  text-align: center;
  overflow-wrap: anywhere;
}

/* Apps: each work opens as a window of a phone of now. White, its corners continuous and concentric
   with the keys inside them (22px in plus a 10px key corner makes 32px), a hairline edge and a soft
   lift from the one light. The keys are the window's own, in its corners; the content stands in from
   them by a clear step, and where the window is a phone's width it lines up with them. */
.app {
  position: relative;
  z-index: 1;
  max-width: 1240px;
  margin: 0 auto 44px;
  padding: 0 var(--pad);
}
.window {
  --inset: clamp(48px, 5vw, 72px);
  position: relative;
  border-radius: 32px;
  background: #fff;
  box-shadow:
    0 0 0 0.5px rgb(0 0 0 / 0.08),
    0 1px 2px rgb(0 0 0 / 0.05),
    0 12px 28px -14px rgb(25 25 35 / 0.16),
    0 44px 80px -40px rgb(25 25 35 / 0.3);
}
@supports (corner-shape: squircle) { .window { corner-shape: squircle; border-radius: 48px; } }
.bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 22px 24px 0;
}
/* The title starts the content, so it sits nearer its first line than the keys above it. */
.app-title {
  display: flex;
  align-items: center;
  gap: 18px;
  margin: clamp(28px, 2.5vw, 36px) 0 0;
  padding: 0 var(--inset);
  font-size: clamp(34px, 1.3rem + 2.1vw, 50px);
  line-height: 1.05;
  font-weight: 700;
  letter-spacing: -0.032em;
}
.app-title .mini { width: 60px; height: 60px; }
.mini { flex: none; filter: drop-shadow(0 1px 1px rgb(0 0 0 / 0.3)) drop-shadow(0 3px 5px rgb(25 25 35 / 0.16)); }

.body { padding: 16px var(--inset) 64px; }
.body-zaotai {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(220px, 23vw, 290px);
  gap: 44px clamp(32px, 5vw, 80px);
  align-items: start;
}
.text p { max-width: 66ch; margin: 0 0 1.1em; font-size: 18px; line-height: 1.62; color: var(--ink-2); }
.text p:last-child { margin-bottom: 0; }
.text .lede {
  max-width: 30em;
  margin-bottom: 0.8em;
  font-size: clamp(21px, 1rem + 0.75vw, 26px);
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: pretty;
}
/* Staff quotes: the owner's English, the staff's own Chinese under it. Type only, no rule or panel.
   The marks are in the text (放进网站.py puts them round the English, outside any note of the owner's),
   and the opening one hangs in the margin. .en stays at the specificity of .bb-gone, which comes later. */
.text .quotes { max-width: 66ch; margin: 1.5em 0 1.6em; padding: 0; list-style: none; }
.text .quotes li + li { margin-top: 1em; }
.text .quotes p { margin: 0; }
.quotes .en { text-indent: -0.4em; color: var(--ink); }
.text .quotes .zh { margin-top: 2px; text-indent: -1em; font-size: 14px; line-height: 1.5; }

/* Keys: keycaps. The cap stands on its skirt; held down, it sinks onto it and springs back. */
.key, .key-back {
  --sink: 0;
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 34px;
  padding: 0 15px;
  border-radius: 10px;
  background: linear-gradient(var(--cap), var(--cap-lo));
  box-shadow:
    inset 0 1px 0 #fff,
    0 0 0 0.5px rgb(0 0 0 / 0.16),
    0 calc(4px - var(--sink) * 3px) 0 -0.5px var(--skirt),
    0 calc(6px - var(--sink) * 4px) calc(9px - var(--sink) * 6px) -2px rgb(25 25 35 / 0.26);
  transform: translateY(calc(var(--sink) * 3px));
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: transform 0.45s var(--spring), box-shadow 0.45s var(--spring);
}
.key:active, .key-back:active { --sink: 1; transition-duration: 0.05s; }
/* A key takes a touch a little beyond its cap, so a finger has 44px to hit. */
.key::after, .key-back::after { content: ""; position: absolute; inset: -5px -4px; }
button.key { border: 0; font-family: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.keys { display: flex; gap: 12px; }
.bb-key { display: none; }
.bb-ready .bb-key { display: inline-flex; }

/* Big Bang (bigbang.js). Held text leaves the page as bare type, flies, and lands as keys on a dimmed
   page. A picked key is pressed down onto its skirt and takes the red of selected text. */
html.bb-on { overflow: hidden; scrollbar-gutter: stable; }
/* The words leave; the icon in a title stays. */
:is(.note, .app) .bb-gone { color: transparent; }
.bb {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  padding: 24px 16px 48px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.bb::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: rgb(28 28 34 / 0.38);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  animation: fade 0.3s ease-out both;
}
.bb-closing::before { animation: bb-out 0.46s ease-in both; }
@keyframes bb-out { to { opacity: 0; } }
.bb-sheet { width: min(720px, 100%); margin: auto; }
.bb-bar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  padding-bottom: 16px;
  animation: fade 0.25s ease-out 0.25s both;
}
.bb-closing .bb-bar { animation: bb-out 0.12s ease-in both; }
.bb-words { display: flex; flex-wrap: wrap; gap: 10px 6px; }
.bb-chip {
  --sink: 0;
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  padding: 0 11px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 16px;
  font-weight: 500;
  white-space: nowrap;
  translate: 0 calc(var(--sink) * 2px);
  transition: translate 0.35s var(--spring), color 0.12s;
  cursor: pointer;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
/* Words that run past the screen scroll under a finger that sets off up or down (bigbang.js). */
.bb-long .bb-chip { touch-action: pan-y; }
.bb-chip::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: linear-gradient(var(--cap), var(--cap-lo));
  box-shadow:
    inset 0 1px 0 #fff,
    0 0 0 0.5px rgb(0 0 0 / 0.18),
    0 3px 0 -0.5px var(--skirt),
    0 5px 8px -2px rgb(0 0 0 / 0.32);
}
/* The gaps between keys belong to the keys, so a near miss still picks and never closes. */
.bb-chip::after { content: ""; position: absolute; inset: -5px -3px; }
.bb-chip[aria-pressed="true"] { --sink: 1; color: #fff; }
.bb-chip[aria-pressed="true"]::before {
  background: linear-gradient(#d63a2a, #b51a12);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.3),
    0 0 0 0.5px rgb(100 10 5 / 0.45),
    0 1px 0 -0.5px #8f160e,
    0 1px 2px -1px rgb(0 0 0 / 0.35);
}
/* On the dimmed page a red ring is faint, and it is the red of a picked key, so here the ring is ink. */
.bb :focus-visible { outline-color: var(--ink); }
/* Heard, not seen: "Copied" for screen readers, and the box a fallback copy goes through. */
.bb-said { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* A phone of now around the screen: thin black glass, a metal edge, no brand. */
.screen {
  margin: 0;
  padding: 7px;
  border-radius: 46px;
  background: #0b0b0c;
  box-shadow:
    inset 0 0 0 1px #3a3a3c,
    0 0 0 1.5px #c4c4c9,
    0 0 0 2.5px #77777d,
    0 30px 50px -24px rgb(0 0 0 / 0.45);
}
.screen img { display: block; width: 100%; height: auto; border-radius: 39px; }
.shot { margin: 0; }
.shot figcaption { margin-top: 22px; font-size: 14px; line-height: 1.5; color: var(--ink-2); }

/* Waking: the island drops in, the words settle, the icons arrive on springs. Once, on load. */
@media (prefers-reduced-motion: no-preference) {
  .js .island { animation: island-in 0.9s var(--spring) both; }
  .js .note > * { animation: settle 0.9s var(--spring-soft) both; }
  .js .note > :nth-child(2) { animation-delay: 70ms; }
  .js .note > :nth-child(3) { animation-delay: 140ms; }
  .js .app-icon { animation: wake 0.95s var(--spring) both, fade 0.35s ease-out both; animation-delay: calc(160ms + var(--i, 0) * 45ms); }
}
@keyframes island-in { from { translate: -50% -60px; } }
@keyframes settle { from { opacity: 0; transform: translateY(14px); } }
@keyframes wake { from { transform: translateY(26px) scale(0.8); } }
@keyframes fade { from { opacity: 0; } }

/* Opening an app: the home screen zooms past and is gone before the app arrives, so the two never
   sit on top of each other; the icon flies to the app's title. Home (.vt-back) runs it the other way. */
::view-transition-old(root) { animation: vt-out 0.16s cubic-bezier(0.4, 0, 1, 1) both; }
::view-transition-new(root) { animation: vt-fade 0.24s cubic-bezier(0, 0, 0.2, 1) 0.13s both, vt-in 0.9s var(--spring-soft) both; }
.vt-back::view-transition-old(root) { animation-name: vt-out-back; }
.vt-back::view-transition-new(root) { animation-name: vt-fade, vt-in-back; }
::view-transition-group(app) { animation-duration: 0.9s; animation-timing-function: var(--spring-fly); }
::view-transition-group(island) { animation-duration: 0.3s; }
@keyframes vt-out { to { opacity: 0; transform: scale(1.04); filter: blur(3px); } }
@keyframes vt-out-back { to { opacity: 0; transform: scale(0.96); } }
@keyframes vt-fade { from { opacity: 0; } }
@keyframes vt-in { from { transform: scale(0.96); } }
@keyframes vt-in-back { from { transform: scale(1.04); } }

@media (max-width: 860px) {
  :root { --icon: 76px; --cell: 100px; }
  .island { top: 10px; height: 38px; padding: 0 15px; font-size: 13px; }
  .home { grid-template-columns: minmax(0, 1fr); min-height: 0; padding-top: 78px; gap: 36px; }
  .launcher { row-gap: 18px; }
  .app-icon .label { font-size: 12px; }
  .window { --inset: 24px; border-radius: 28px; }
  .text p { font-size: 17px; }
  .text .lede { font-size: 21px; }
  .body-zaotai { grid-template-columns: minmax(0, 1fr); }
  .shot { width: min(250px, 72%); justify-self: center; }
  .bb-words { gap: 9px 5px; }
  .bb-chip { height: 30px; padding: 0 10px; font-size: 15px; }
}

/* On a touchscreen, holding text is Big Bang, as on the phone, not the system's selection. And its keys
   are sized for a finger: 44px to touch, the rows a little further apart, the narrow keys widened. */
@media (pointer: coarse) {
  .note p, .text p, .app-title, .shot figcaption { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
  :is(.note, .text) p a { -webkit-touch-callout: default; }
  .bb-words { row-gap: 14px; }
  .bb-chip { min-width: 38px; }
  .bb-chip::after { inset: -7px -3px; }
}

@media (prefers-reduced-transparency: reduce) {
  .island, .island.is-liquid { background: rgb(250 250 252 / 0.97); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .bb::before { background: rgb(28 28 34 / 0.72); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .app-icon, .app-icon *, .island-app { transition: none !important; animation: none !important; }
  .app-icon .book .spine { transform: none !important; }
  .app-icon .book .slot { opacity: 0 !important; }
  /* WebKit ignores filter functions on the parts of an SVG, but draws an SVG filter. */
  .app-icon:not(.held) .book.up .spine, .app-icon:focus-visible .book:nth-of-type(4) .spine { filter: url(#bk-lit); }
  .bb { animation: fade 0.2s ease-out both; }
  .bb-closing { animation: bb-out 0.2s ease-in both; }
}
