/* ── code-server 登录页 · 六位数字密码门 ────────────────────────────
   照搬 itsuki 个人站（pj.tomoshibi.cc）的四位密码门，格子加到六位。
   原门在 ~/dev/pj_site/build/gate-templates/gate.html。

   ⚠️ 这里的样式必须全部写在本文件里，不能写进 login.html 的 <style> 标签。
   login.html 头部的 CSP（内容安全策略）写着 style-src 'self'，
   意思是「只准加载同源的样式文件，禁止页面内联样式」——
   内联 <style> 和 style="..." 属性都会被浏览器直接丢弃，页面会变成裸表单。

   ⚠️ code-server 升级会覆盖本文件。定制版另存在
   /opt/code-server-custom/，升级后重新覆盖回去即可。 */

:root {
  color-scheme: dark;
  --canvas: #0e0e11;
  --ink: #f2f2f4;
  --ink-body: #d2d2d8;
  --ink-muted: #8d8d97;
  --accent: #8b76ff;
  --accent-deep: #16112e;
  --danger: #f0716f;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* {
  box-sizing: border-box;
}

html {
  min-width: 280px;
  min-height: 100%;
  background: var(--canvas);
}

body {
  position: relative;
  isolation: isolate;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  overflow: hidden;
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  padding: max(20px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
           max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
  background: var(--canvas);
  color: var(--ink-body);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ── 背景：WebGL 流体着色器 ──────────────────────────────────
   着色器本体 "Ether" 原作者 nimitz (@stormoid) 2014，
   来源 https://www.shadertoy.com/view/MsjSW3
   授权 CC BY-NC-SA 3.0（署名 · 非商业 · 相同方式共享）。
   本站是 itsuki 的私人非商业站点，署名保留在此。

   .ambient 是它跑不起来时的兜底（老设备 / 关了硬件加速 / WebGL 被禁），
   永远画在最底下；着色器跑起来后 canvas 盖在它上面。别删。 */
.ambient {
  position: fixed;
  z-index: -3;
  inset: -24%;
  overflow: hidden;
  background:
    radial-gradient(circle at 17% 22%, rgba(139, 118, 255, .28) 0 8%, transparent 31%),
    radial-gradient(circle at 83% 18%, rgba(92, 132, 196, .18) 0 7%, transparent 27%),
    radial-gradient(circle at 68% 76%, rgba(139, 118, 255, .22) 0 9%, transparent 32%),
    radial-gradient(circle at 24% 81%, rgba(76, 111, 174, .14) 0 6%, transparent 25%),
    radial-gradient(circle at 52% 45%, rgba(139, 118, 255, .12) 0 5%, transparent 24%);
  filter: blur(78px) saturate(1.08);
  transform: translate3d(-2%, -1%, 0) scale(1.04);
  animation: ambient-drift 44s ease-in-out infinite alternate;
  will-change: transform;
}

@keyframes ambient-drift {
  to { transform: translate3d(3%, 2%, 0) scale(1.1) rotate(2deg); }
}

.shader {
  position: fixed;
  z-index: -2;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity .9s ease;
}

/* 第一帧画完才淡入，避免着色器编译期间闪一下黑屏 */
.shader.is-live {
  opacity: 1;
}

/* 中间那圈细描边。只是装饰，去掉它密码格照样能认出来。

   ⚠️ 它必须挂在 .code-entry（格子那一格）里，不能挂在 .card 上。
   .card 底下还有一行 .status 占着位置，圆按 .card 居中会比格子中心低十几像素，
   肉眼一眼就看得出来歪。 */
.ring {
  position: absolute;
  z-index: -1;
  top: 50%;
  left: 50%;
  width: min(78vmin, 620px);
  aspect-ratio: 1;
  border: 1px solid rgba(242, 242, 244, .09);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(14, 14, 17, .28) 0%, rgba(14, 14, 17, .10) 62%, transparent 72%);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.stage {
  grid-row: 2;
  width: 100%;
  display: grid;
  place-items: center;
}

/* 卡片本身是「不存在」的：没有底、没有边框、没有阴影。
   六个格子直接浮在模糊背景上。
   这个容器只留下来当定位用的壳，删掉它会牵动下面一串选择器。 */
.card {
  position: relative;
  width: min(100%, 560px);
  text-align: center;
  animation: card-rise .46s cubic-bezier(.22, 1, .36, 1) both;
}

@keyframes card-rise {
  from { opacity: 0; transform: translateY(9px) scale(.99); }
}

.gate-form {
  margin: 0;
}

/* 六格比原版四格多两个，手机竖屏放不下原来的尺寸
   （原版 clamp(50px, 14.5vw, 64px) × 6 会超出 390px 宽的屏幕），
   所以格子和间距都按比例收窄了。 */
.code-entry {
  --slot-width: clamp(38px, 11.5vw, 56px);
  --slot-height: clamp(46px, 14vw, 66px);
  --slot-gap: clamp(6px, 2vw, 12px);
  position: relative;
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  border-radius: 16px;
}

/* 六个格子背后那团固定的暗晕。
   着色器里的「中心压暗」只压得住正中心一小块，手机竖屏时最外侧那两个格子
   会落在亮区上 —— 实测流体最亮处能到纯白，那一瞬间边框和圆点就糊了。
   这一层跟着色器无关、亮度固定，所以不管流体动到哪一帧，格子都读得清。 */
.code-entry::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -52% -12%;
  border-radius: 50%;
  background: radial-gradient(
    ellipse at center,
    rgba(14, 14, 17, .82) 0%,
    rgba(14, 14, 17, .62) 45%,
    rgba(14, 14, 17, .22) 72%,
    transparent 100%
  );
  pointer-events: none;
}

/* 真正接收键盘输入的框：铺满六个格子、完全透明。
   人看到的圆点是下面 .slot 画出来的，光标也是 CSS 画的。 */
.code-input {
  position: absolute;
  z-index: 2;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  border: 0;
  border-radius: inherit;
  background: transparent;
  color: transparent;
  caret-color: transparent;
  cursor: text;
  /* 16px 以下 iOS Safari 会在聚焦时自动放大整个页面 */
  font-size: 16px;
}

.code-input:disabled {
  cursor: not-allowed;
}

/* code-server 表单自带的用户名输入框，藏起来（只是给密码管理器认的） */
.user-field {
  display: none;
}

.slots {
  display: grid;
  grid-template-columns: repeat(6, var(--slot-width));
  gap: var(--slot-gap);
}

.slot {
  position: relative;
  width: var(--slot-width);
  height: var(--slot-height);
  display: grid;
  place-items: center;
  overflow: hidden;
  /* 浮在流体背景上，所以是半透明磨砂玻璃，不是实心色块。
     backdrop-filter 不支持时退回这层半透明底色，仍然看得清。 */
  background: rgba(30, 30, 38, .62);
  -webkit-backdrop-filter: blur(16px) saturate(1.25);
  backdrop-filter: blur(16px) saturate(1.25);
  /* 边框别再调淡了：着色器把圆心压得很暗，.14 那一档实测几乎看不见格子在哪 */
  border: 1px solid rgba(242, 242, 244, .3);
  border-radius: clamp(10px, 3.2vw, 13px);
  color: var(--ink);
  box-shadow:
    inset 0 1px 0 rgba(242, 242, 244, .06),
    0 8px 24px rgba(0, 0, 0, .28);
  font-size: clamp(20px, 6vw, 26px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  transition:
    border-color .16s ease,
    background-color .16s ease,
    box-shadow .16s ease,
    color .16s ease,
    opacity .16s ease;
}

.slot.is-filled {
  background: rgba(22, 17, 46, .78);
  border-color: rgba(139, 118, 255, .62);
  color: var(--accent);
}

.slot.is-current {
  border-color: var(--accent);
  box-shadow:
    0 0 0 3px rgba(139, 118, 255, .14),
    0 0 24px rgba(139, 118, 255, .22),
    inset 0 1px 0 rgba(242, 242, 244, .06);
}

.slot.is-current:not(.is-filled)::after {
  content: "";
  width: 2px;
  height: clamp(20px, 6vw, 26px);
  border-radius: 999px;
  background: var(--accent);
  box-shadow: 0 0 9px rgba(139, 118, 255, .72);
  animation: caret-blink 1s steps(1, end) infinite;
}

@keyframes caret-blink {
  50% { opacity: .14; }
}

.code-entry.is-submitting .slot {
  opacity: .76;
}

.code-entry.is-error .slots {
  animation: code-shake .42s cubic-bezier(.36, .07, .19, .97) both;
}

.code-entry.is-error .slot {
  background: rgba(240, 113, 111, .08);
  border-color: var(--danger);
  color: var(--danger);
  box-shadow: 0 0 0 2px rgba(240, 113, 111, .08);
}

@keyframes code-shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-6px); }
  40%, 60% { transform: translateX(6px); }
}

/* 密码对了以后六个格子从左到右依次跳一下。
   原版用内联 style="--slot-index: N" 传序号，这里被 CSP 禁掉了，
   所以改成按第几个孩子直接写死延迟。 */
.code-entry.is-success .slot {
  color: var(--accent);
  animation: success-relay .34s cubic-bezier(.22, 1, .36, 1) both;
}

.code-entry.is-success .slot:nth-child(1) { animation-delay: 0ms; }
.code-entry.is-success .slot:nth-child(2) { animation-delay: 70ms; }
.code-entry.is-success .slot:nth-child(3) { animation-delay: 140ms; }
.code-entry.is-success .slot:nth-child(4) { animation-delay: 210ms; }
.code-entry.is-success .slot:nth-child(5) { animation-delay: 280ms; }
.code-entry.is-success .slot:nth-child(6) { animation-delay: 350ms; }

@keyframes success-relay {
  0% { transform: translateY(0); }
  48% {
    transform: translateY(-5px);
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-deep);
    box-shadow: 0 0 26px rgba(139, 118, 255, .36);
  }
  100% {
    transform: translateY(0);
    background: var(--accent-deep);
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: 0 0 18px rgba(139, 118, 255, .2);
  }
}

.code-entry.is-locked .slot {
  background: rgba(23, 23, 27, .62);
  border-color: rgba(242, 242, 244, .07);
  color: var(--ink-muted);
  box-shadow: none;
  opacity: .48;
}

.status {
  min-height: 1.6em;
  margin: 16px 0 0;
  color: var(--ink-body);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  /* 背景是会动的流体，纯色小字压不住 → 加一层暗描边保可读 */
  text-shadow: 0 1px 6px rgba(14, 14, 17, .9);
}

.status[data-kind="error"] {
  color: var(--danger);
}

.status[data-kind="success"] {
  color: var(--accent);
}

/* JavaScript 没跑起来时（浏览器禁用了脚本 / 脚本报错）的降级：
   格子是纯装饰、点不动，所以露出一个普通的密码输入框和提交按钮，
   走传统表单提交。不做这层的话，页面会变成六个永远输不进字的方块。 */
.fallback {
  display: none;
  margin: 20px auto 0;
  max-width: 320px;
}

.no-js .fallback {
  display: block;
}

.no-js .code-entry {
  display: none;
}

.fallback-input {
  width: 100%;
  padding: 14px 16px;
  border: 1px solid rgba(242, 242, 244, .3);
  border-radius: 12px;
  background: rgba(30, 30, 38, .72);
  color: var(--ink);
  font-family: inherit;
  font-size: 16px;
  text-align: center;
  letter-spacing: .3em;
}

.fallback-submit {
  width: 100%;
  margin-top: 12px;
  padding: 13px 16px;
  border: 0;
  border-radius: 12px;
  background: var(--accent);
  color: var(--accent-deep);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

/* 服务端渲染的报错（整页刷新那条路才会出现，例如降级表单提交失败） */
.error {
  margin: 14px 0 0;
  color: var(--danger);
  font-size: 13px;
  text-shadow: 0 1px 6px rgba(14, 14, 17, .9);
}

@media (max-height: 560px) {
  body {
    overflow-y: auto;
    grid-template-rows: auto 1fr auto;
  }

  .stage {
    grid-row: 2;
    padding-block: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ambient,
  .card,
  .slot.is-current::after,
  .code-entry.is-error .slots,
  .code-entry.is-success .slot {
    animation: none;
  }

  .slot {
    transition: none;
  }
}
