/* ============================================================
   PROMPTLOG — deep-space black / hairline craft / acid accent
   Layers: page < lattice < panel < raised < overlay
   ============================================================ */

@font-face{font-family:MiSans;font-weight:300;font-style:normal;font-display:swap;
  src:url("../fonts/MiSans-Light.woff2") format("woff2")}
@font-face{font-family:MiSans;font-weight:400;font-style:normal;font-display:swap;
  src:url("../fonts/MiSans-Regular.woff2") format("woff2")}
@font-face{font-family:MiSans;font-weight:600;font-style:normal;font-display:swap;
  src:url("../fonts/MiSans-Semibold.woff2") format("woff2")}

:root{
  /* warm charcoal — the neutrals carry a brown-amber cast, not the old violet.
     Text is cream rather than blue-white. */
  --bg:#0a0908; --bg-1:#100e0c;
  --panel:#181512; --panel-2:#1e1b17; --panel-3:#27231d;
  --line:rgba(var(--hair-rgb),.078); --line-2:rgba(var(--hair-rgb),.135); --line-3:rgba(var(--hair-rgb),.21);
  --hi:inset 0 1px 0 rgba(var(--hair-rgb),.06);
  --fg:#f8f3ec; --fg-2:#bdb3a5; --fg-3:#857c6e;
  --acc:#ffb03a; --acc-2:#ffd489; --acc-dim:rgba(var(--acc-rgb),.15);
  --dgr:#ff4d4d; --dgr-dim:rgba(var(--dgr-rgb),.14);
  /* Same colours as --acc / --dgr, as bare r,g,b. Every translucent tint in this
     file goes through these, so a palette change is these lines and nothing
     else — tools/sync_test.js asserts each pair stays in step and that no
     non-neutral rgba() literal is left behind. */
  --acc-rgb:255,176,58; --dgr-rgb:255,77,77;
  /* hairline tint: warm so the borders read as part of the same light as the
     text, rather than as neutral grey lines laid over it */
  --hair-rgb:255,240,220;
  /* translucent surfaces — scrims, veils, blurred bars. Each mirrors the solid
     token named after it, so a veil is always the same hue as the page under it.
     --ink-rgb sits just below --bg for the darkest veils. */
  --ink-rgb:5,4,3; --bg-rgb:10,9,8; --bg-1-rgb:16,14,12; --panel-3-rgb:39,35,29;
  --r:9px; --r-lg:14px; --r-xl:18px;
  --ease:cubic-bezier(.16,1,.3,1); --ease-2:cubic-bezier(.4,0,.2,1);
  --font:MiSans,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --top:58px; --gap:14px; --card-min:266px;
}

*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--fg);
  font-family:var(--font);font-weight:400;font-size:14px;line-height:1.6;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden;font-variant-numeric:tabular-nums;
}
/* ── background: lattice + overhead light (kills the flat-black void) ── */
body::before{
  content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
  background-image:radial-gradient(circle at 1px 1px,rgba(255,255,255,.06) 1px,transparent 0);
  background-size:26px 26px;
  -webkit-mask-image:radial-gradient(125% 85% at 50% -4%,#000 0%,rgba(0,0,0,.35) 46%,transparent 74%);
  mask-image:radial-gradient(125% 85% at 50% -4%,#000 0%,rgba(0,0,0,.35) 46%,transparent 74%);
}
body::after{
  content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
  background:radial-gradient(88% 46% at 50% -9%,rgba(255,255,255,.07),transparent 64%);
}
h1,h2,h3,p,dl,dd,dt{margin:0}
button,input,textarea,select{font:inherit;color:inherit}
button{background:none;border:0;padding:0;cursor:pointer}
pre{margin:0}
svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;flex:none}
kbd{font-family:var(--font);font-size:10px;line-height:1;padding:3px 5px;
  border:1px solid var(--line-2);border-radius:4px;color:var(--fg-3);background:var(--panel-2)}
::selection{background:var(--acc);color:#0a0a0a}

*{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.14) transparent}
::-webkit-scrollbar{width:9px;height:9px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(255,255,255,.13);border-radius:99px;
  border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.24);background-clip:padding-box}

/* ── touch hygiene ─────────────────────────────────────── */
/* removes the default grey tap flash so :active states are the only press
   feedback, and drops the 300ms double-tap-zoom wait on controls */
*{-webkit-tap-highlight-color:transparent}
.btn,.ibtn,.opt,.tag,.cp,.lnk,.fab,.card,.search__x,.sw__box{touch-action:manipulation}
/* scrolling inside an overlay must not chain to the page behind it */
.rail__inner,.drawer__body,.editor__body{overscroll-behavior:contain}
/* an unlocked session lives only in memory — a stray pull-to-refresh would
   reload and force the password again */
html{overscroll-behavior-y:contain}

.grain{position:fixed;inset:0;z-index:1;pointer-events:none;opacity:.055;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E")}
.glow{position:fixed;top:0;left:0;width:680px;height:680px;margin:-340px 0 0 -340px;
  z-index:0;pointer-events:none;opacity:0;transition:opacity .6s var(--ease-2);
  background:radial-gradient(circle,rgba(var(--acc-rgb),.09),rgba(var(--acc-rgb),.03) 40%,transparent 66%);
  will-change:transform}
@media (hover:hover) and (pointer:fine){ .glow{opacity:1} }

/* ── primitives ────────────────────────────────────────── */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;
  height:34px;padding:0 14px;border-radius:var(--r);font-size:13px;font-weight:600;
  letter-spacing:.01em;border:1px solid transparent;white-space:nowrap;
  transition:background .18s var(--ease-2),color .18s var(--ease-2),
    border-color .18s var(--ease-2),transform .12s var(--ease-2),box-shadow .18s}
.btn:active{transform:scale(.975)}
/* The primary action is a LIGHT NEUTRAL fill, never an accent fill, and it
   carries no coloured halo. A saturated fill wrapped in a saturated glow on the
   loudest control is what makes a dark UI read as a template; a light fill on a
   dark page reads as a tool. --acc is punctuation only — stars, focus rings,
   the sync dot, the brand mark. Measured, the accent covers ~0.2% of the page,
   so its HUE was never what made the UI look cheap: the amount of pigment was. */
.btn--primary{background:var(--fg);color:#0a0908;
  box-shadow:0 1px 2px rgba(0,0,0,.5),inset 0 -1px 0 rgba(0,0,0,.14)}
.btn--primary:hover{background:#fff}
.btn--ghost{border-color:var(--line-2);color:var(--fg-2)}
.btn--ghost:hover{color:var(--fg);border-color:var(--line-3);background:rgba(255,255,255,.04)}
/* outlined, not filled: a solid red block would become the loudest object on an
   otherwise all-neutral page */
.btn--danger{background:transparent;border-color:rgba(var(--dgr-rgb),.45);color:var(--dgr)}
.btn--danger:hover{background:var(--dgr-dim);border-color:rgba(var(--dgr-rgb),.62);color:var(--dgr)}
.btn--block{width:100%;height:42px}
.btn--sm{height:28px;font-size:12px;padding:0 11px}
.btn:focus-visible,.ibtn:focus-visible,.lnk:focus-visible,.card:focus-visible,
.opt:focus-visible,.chip:focus-visible,.cp:focus-visible,.tag:focus-visible{
  outline:1px solid var(--acc);outline-offset:2px}

.ibtn{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 9px;
  border-radius:7px;color:var(--fg-3);font-size:12.5px;transition:color .16s,background .16s}
.ibtn:hover{color:var(--fg);background:rgba(255,255,255,.06)}
.ibtn--danger:hover{color:var(--dgr);background:var(--dgr-dim)}
.ibtn--on{color:var(--acc);background:var(--acc-dim)}
.ibtn__t{display:none}
/* Deliberately the same 1080px boundary as the toolbar collapse below. Two
   separate breakpoints left a dead zone: 1081-1239px showed six unlabelled
   icons, then the row abruptly grew 155px of text at 1240px. Measured cost of
   aligning them (tools/_band.js, forced-labels pass): #search pays 51px at
   1081px, 32px at 1100px, 12px at 1120px and nothing at or above 1140px. It is
   that cheap because .search{max-width:460px} means the field only shrinks when
   the row is genuinely out of room, and 409px still fits this placeholder three
   times over. No width overflowed at any size. */
@media (min-width:1081px){ .ibtn__t{display:inline} }
/* declared here with the other show/hide rules, NOT next to .msheet: the
   ≤1080px block further down needs to switch it on, and a base rule sitting
   after that block would win on source order at equal specificity */
#moreBtn{display:none}

.lnk{color:var(--fg-3);font-size:12px;border-bottom:1px solid transparent;
  transition:color .16s}
.lnk:hover{color:var(--fg)}
.lnk--sm{font-size:11.5px}

.chip{display:inline-flex;align-items:center;height:21px;padding:0 8px;border-radius:5px;
  font-size:10.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  background:rgba(255,255,255,.055);border:1px solid var(--line);color:var(--fg-2);white-space:nowrap}
.chip--acc{background:var(--acc-dim);border-color:rgba(var(--acc-rgb),.3);color:var(--acc)}

.tag{display:inline-flex;align-items:center;gap:0;height:24px;padding:0 9px;border-radius:99px;
  font-size:11.5px;color:var(--fg-2);background:rgba(255,255,255,.05);
  border:1px solid var(--line);transition:all .16s var(--ease-2);cursor:pointer}
.tag:hover{color:var(--fg);border-color:var(--line-2)}
.tag.is-on{background:var(--acc-dim);border-color:rgba(var(--acc-rgb),.32);color:var(--acc)}
.tag__n{font-size:10px;color:var(--fg-3);font-variant-numeric:tabular-nums;
  margin-left:6px;padding-left:6px;border-left:1px solid var(--line-2)}
.tag.is-on .tag__n{border-color:rgba(var(--acc-rgb),.3);color:var(--acc);opacity:.75}
mark{background:rgba(var(--acc-rgb),.22);color:var(--acc);border-radius:2px;padding:0 1px}
.code .p-arg,.card__prompt .p-arg{color:var(--acc)}

/* ============================================================
   GATE
   ============================================================ */
.gate{position:fixed;inset:0;z-index:60;display:grid;place-items:center;padding:24px;
  background:transparent}
/* pool of light so the panel sits inside the scene instead of on a black void */
.gate::before{content:"";position:absolute;width:min(760px,124vw);height:min(760px,124vw);
  left:50%;top:50%;transform:translate(-50%,-52%);pointer-events:none;border-radius:50%;
  background:
    radial-gradient(closest-side,rgba(255,255,255,.055),transparent 62%),
    radial-gradient(closest-side,rgba(var(--acc-rgb),.13),rgba(var(--acc-rgb),.035) 48%,transparent 76%)}
.gate__panel{width:100%;max-width:408px;position:relative;z-index:2;
  padding:28px 28px 22px;border-radius:var(--r-xl);
  border:1px solid var(--line-2);
  background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.012) 34%,rgba(0,0,0,.26));
  box-shadow:var(--hi),0 40px 90px -40px rgba(0,0,0,.95),0 0 0 1px rgba(0,0,0,.5);
  animation:gateIn .8s var(--ease) both}
/* no blur() here: this panel is 408px wide and blurs on first paint, where the
   deferred decode already costs a frame. opacity + transform only. */
@keyframes gateIn{from{opacity:0;transform:translateY(15px) scale(.986)}
  to{opacity:1;transform:none}}
.gate__panel::before{content:"";position:absolute;top:-1px;left:14%;right:14%;height:1px;
  background:linear-gradient(90deg,transparent,var(--acc),transparent);
  opacity:.26;animation:charge 6s var(--ease-2) infinite}
@keyframes charge{0%,100%{transform:scaleX(.25);opacity:.08}
  50%{transform:scaleX(1);opacity:.34}}

.gate__brand{display:flex;align-items:center;gap:11px}
/* six-blade iris shutter — reads as "capture", and the motion is mechanical
   (60° steps with a hold) rather than an ambient spin */
.gate__mark{width:32px;height:32px;color:var(--acc);flex:none}
.gate__mark svg{width:32px;height:32px;stroke-width:1.5;overflow:visible;
  animation:markIn .9s var(--ease) both}
.gm-ring{opacity:.26;stroke-width:1.1}
.gm-blades{transform-box:view-box;transform-origin:24px 24px;
  animation:irisTurn 16s var(--ease-2) infinite}
.gm-core{fill:currentColor;stroke:none;
  transform-box:view-box;transform-origin:24px 24px;
  animation:irisPulse 3.4s var(--ease-2) infinite}
@keyframes markIn{from{opacity:0;transform:scale(.55) rotate(-38deg)}
  to{opacity:1;transform:none}}
@keyframes irisTurn{
  0%{transform:rotate(0deg)}
  34%{transform:rotate(60deg)}
  50%{transform:rotate(60deg)}
  84%{transform:rotate(120deg)}
  100%{transform:rotate(120deg)}}
@keyframes irisPulse{0%,100%{transform:scale(1);opacity:.95}
  46%{transform:scale(1.55);opacity:.42}}
.gate__title{font-size:19px;font-weight:600;letter-spacing:.02em;line-height:1.1}
.gate__sub{margin-top:3px;font-size:11.5px;color:var(--fg-3)}
.gate__form{margin-top:24px;display:flex;flex-direction:column;gap:11px}
.gate__hint{font-size:11.5px;line-height:1.65;color:var(--fg-3);margin:1px 0 0}
.gate__err{min-height:17px;margin-top:10px;font-size:12px;color:var(--dgr);
  opacity:0;transform:translateY(-3px);transition:opacity .22s,transform .22s}
.gate__err.is-on{opacity:1;transform:none}
.gate__foot{margin-top:18px;padding-top:14px;border-top:1px solid var(--line);
  display:flex;align-items:center;justify-content:space-between;gap:12px}
.gate__meta{font-size:11px;color:var(--fg-3)}

.field{display:block}
.field__k{display:block;font-size:10px;font-weight:600;letter-spacing:.13em;
  color:var(--fg-3);margin-bottom:6px}
.field__in{width:100%;height:41px;padding:0 12px;border-radius:var(--r);
  background:rgba(0,0,0,.34);border:1px solid var(--line);color:var(--fg);
  font-size:14px;letter-spacing:.05em;transition:border-color .18s,background .18s,box-shadow .18s}
.field__in::placeholder{color:var(--fg-3)}
.field__in:focus{outline:0;border-color:rgba(var(--acc-rgb),.45);background:rgba(0,0,0,.5);
  box-shadow:0 0 0 3px rgba(var(--acc-rgb),.09)}

.gate.is-leaving{animation:gateOut .6s var(--ease) forwards}
/* a 20px blur on a full-viewport layer was the most expensive frame in the
   whole unlock sequence. A crisp dissolve + slight drift reads better anyway. */
@keyframes gateOut{
  0%{opacity:1;transform:none}
  52%{opacity:.42;transform:scale(1.014)}
  100%{opacity:0;transform:scale(1.042)}}
.gate__panel.is-shake{animation:shake .42s cubic-bezier(.36,.07,.19,.97)}
@keyframes shake{
  10%,90%{transform:translateX(-1.5px)} 20%,80%{transform:translateX(3px)}
  30%,50%,70%{transform:translateX(-6px)} 40%,60%{transform:translateX(6px)}}

/* unlock scan — swept with transform, NOT top. Animating `top` relayouts the
   band on every frame, right when the app is also doing its entrance. */
.scan{position:fixed;inset:0;z-index:65;pointer-events:none;opacity:0;overflow:hidden}
.scan.is-on{animation:scanFade 1.2s var(--ease-2) forwards}
.scan::before{content:"";position:absolute;left:0;right:0;top:0;height:190px;
  transform:translate3d(0,-100%,0);
  background:linear-gradient(180deg,transparent,rgba(var(--acc-rgb),.10) 58%,
    rgba(var(--acc-rgb),.34) 88%,var(--acc-2));
  box-shadow:0 0 80px 4px rgba(var(--acc-rgb),.3)}
.scan.is-on::before{animation:scanSlide 1.2s var(--ease-2) forwards;
  will-change:transform}
.scan::after{content:"";position:absolute;inset:0;opacity:0;
  background:radial-gradient(120% 60% at 50% 50%,rgba(var(--acc-rgb),.06),transparent 72%)}
.scan.is-on::after{animation:scanWash 1.2s var(--ease-2) forwards}
@keyframes scanSlide{from{transform:translate3d(0,-100%,0)}
  to{transform:translate3d(0,calc(100vh + 190px),0)}}
@keyframes scanWash{0%,100%{opacity:0}34%{opacity:1}72%{opacity:.35}}
@keyframes scanFade{0%{opacity:0}8%{opacity:1}80%{opacity:1}100%{opacity:0}}

body[data-phase="boot"] .app{opacity:0}
.app{opacity:1}
body[data-phase="unlocked"] .app{animation:appIn .7s var(--ease) both}
/* CRITICAL: a filled animation that touches transform turns .app into a
   containing block for position:fixed descendants, which breaks the mobile
   bottom sheet. Drop the animation once it has played. (filter:blur() used to
   be here too — that blurred the whole viewport for 700ms; it is gone.) */
body[data-phase="unlocked"] .app.is-settled{animation:none}
@keyframes appIn{from{opacity:0;transform:scale(.992) translateY(7px)}
  to{opacity:1;transform:none}}

/* ============================================================
   TOPBAR
   ============================================================ */
.app{position:relative;z-index:2;min-height:100dvh}
.topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:16px;
  height:var(--top);padding:0 22px;
  background:rgba(var(--bg-rgb),.7);backdrop-filter:blur(18px) saturate(150%);
  -webkit-backdrop-filter:blur(18px) saturate(150%);
  border-bottom:1px solid var(--line);
  box-shadow:0 1px 0 rgba(255,255,255,.02) inset,0 14px 30px -26px rgba(0,0,0,1)}
.topbar__l{display:flex;align-items:center;gap:13px;min-width:0}
.topbar__c{flex:1;display:flex;justify-content:center;min-width:0}
.topbar__r{display:flex;align-items:center;gap:2px;margin-left:auto}

.brand{display:flex;align-items:center;gap:8px;color:var(--fg)}
.brand__mark{width:17px;height:17px;color:var(--acc);stroke-width:1.4}
.brand__txt{font-size:13px;font-weight:600;letter-spacing:.15em}
.counter{font-size:11.5px;color:var(--fg-3);padding-left:13px;border-left:1px solid var(--line);
  display:flex;align-items:baseline;gap:3px}
.counter b{font-size:14px;font-weight:600;color:var(--fg-2);font-variant-numeric:tabular-nums}
.counter i{font-style:normal}

.search{position:relative;width:100%;max-width:460px;display:flex;align-items:center}
.search__ico{position:absolute;left:11px;color:var(--fg-3);pointer-events:none}
.search__in{width:100%;height:34px;padding:0 62px 0 34px;border-radius:var(--r);
  background:rgba(0,0,0,.32);border:1px solid var(--line);color:var(--fg);font-size:13px;
  transition:border-color .18s,background .18s,box-shadow .18s}
.search__in::placeholder{color:var(--fg-3)}
.search__in:focus{outline:0;border-color:rgba(var(--acc-rgb),.4);background:rgba(0,0,0,.48);
  box-shadow:0 0 0 3px rgba(var(--acc-rgb),.08)}
.search__in::-webkit-search-cancel-button{display:none}
.search__kbd{position:absolute;right:10px;pointer-events:none}
.search__x{position:absolute;right:8px;width:20px;height:20px;display:grid;place-items:center;
  border-radius:4px;color:var(--fg-3)}
.search__x:hover{color:var(--fg);background:rgba(255,255,255,.09)}
.search__x svg{width:12px;height:12px}
.btn--new{height:32px;margin-left:8px;padding:0 12px}
#btnFilter{display:none}
/* 新建 lives in the topbar on desktop; on phones it becomes a thumb-anchored
   FAB instead (see the 720px block) */
.fab{display:none}

/* ============================================================
   SHELL
   ============================================================ */
.shell{display:grid;grid-template-columns:238px minmax(0,1fr);gap:26px;
  max-width:1620px;margin:0 auto;padding:0 22px}

.rail{position:sticky;top:calc(var(--top) + 18px);align-self:start;
  max-height:calc(100dvh - var(--top) - 36px);overflow-y:auto;overflow-x:hidden;
  margin:18px 0 40px;padding:16px 15px 18px;
  border:1px solid var(--line);border-radius:var(--r-lg);
  background:linear-gradient(180deg,rgba(255,255,255,.038),rgba(255,255,255,.008) 26%,transparent 62%),
    var(--panel);
  box-shadow:var(--hi)}
.rail__head{display:none}
.rail__inner{display:flex;flex-direction:column;gap:22px;min-height:100%}
.rail__k{font-size:10px;font-weight:600;letter-spacing:.13em;color:var(--fg-3);margin-bottom:9px}
.rail__list{display:flex;flex-direction:column;gap:1px}
.rail__grp--end{margin-top:auto;padding-top:16px;border-top:1px solid var(--line)}
.opt{display:flex;align-items:center;gap:8px;width:100%;padding:6px 9px;border-radius:7px;
  font-size:12.5px;color:var(--fg-2);text-align:left;transition:background .15s,color .15s}
.opt:hover{background:rgba(255,255,255,.055);color:var(--fg)}
.opt.is-on{background:rgba(255,255,255,.075);color:var(--fg);font-weight:600}
.opt.is-on::before{content:"";width:3px;height:3px;border-radius:99px;background:var(--acc);flex:none}
.opt--quiet{color:var(--fg-3)}
.opt--quiet.is-on{color:var(--acc);background:var(--acc-dim)}
.opt--quiet.is-on::before{display:none}
.opt svg{width:13px;height:13px}
.opt__n{margin-left:auto;font-size:10.5px;color:var(--fg-3);font-variant-numeric:tabular-nums}
.rail__tags{display:flex;flex-wrap:wrap;gap:5px}
.rail__tags .tag{height:23px;font-size:11px}
.rail__none{font-size:11.5px;color:var(--fg-3)}

.main{min-width:0;padding:22px 0 90px}
.bar{display:flex;align-items:center;gap:10px;min-height:26px;margin-bottom:16px}
/* with nothing filtered out, .bar is 42px of empty row — on a phone that is 5%
   of the first screen spent on nothing */
.bar:not(:has(.chip)){display:none}
.bar__chips{display:flex;flex-wrap:wrap;gap:6px}

/* ============================================================
   MASONRY
   ============================================================ */
.grid{display:flex;gap:var(--gap);align-items:flex-start}
.gcol{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:var(--gap)}

.card{position:relative;display:flex;flex-direction:column;gap:9px;
  padding:15px 16px 14px;border-radius:var(--r-lg);
  background:linear-gradient(180deg,var(--panel-2),var(--bg-1));
  border:1px solid var(--line);cursor:pointer;
  box-shadow:var(--hi),0 14px 30px -26px rgba(0,0,0,1);
  transition:border-color .22s var(--ease-2),transform .22s var(--ease),
    box-shadow .28s var(--ease-2),background .22s}
.card::before{content:"";position:absolute;top:0;left:12px;right:12px;height:1px;
  background:linear-gradient(90deg,transparent,var(--acc),transparent);
  opacity:0;transform:scaleX(.3);transition:opacity .3s var(--ease),transform .45s var(--ease)}
.card:hover{border-color:var(--line-2);transform:translateY(-2px);
  box-shadow:var(--hi),0 26px 50px -30px rgba(0,0,0,1)}
.card:hover::before{opacity:.6;transform:scaleX(1)}
.card.is-pin{background:linear-gradient(180deg,rgba(var(--acc-rgb),.05),var(--bg-1))}
.card.is-flash{animation:cardFlash 1.15s var(--ease)}
@keyframes cardFlash{0%{box-shadow:0 0 0 1px var(--acc),0 0 30px rgba(var(--acc-rgb),.3)}
  100%{box-shadow:0 0 0 1px transparent,0 0 0 rgba(var(--acc-rgb),0)}}
.card.enter{animation:cardIn .62s var(--ease) both}
/* opacity + transform only. A blur() here meant every entering card was
   re-rasterised per frame (filters are not compositor-driven), which is what
   produced the >33ms frames when 24 cards enter together. */
@keyframes cardIn{from{opacity:0;transform:translateY(15px) scale(.986)}
  to{opacity:1;transform:none}}

.card__top{display:flex;align-items:center;justify-content:space-between;gap:10px}
.card__date{font-size:10.5px;color:var(--fg-3);letter-spacing:.06em}
.card__title{font-size:16.5px;font-weight:600;letter-spacing:-.015em;line-height:1.34;
  overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;
  -webkit-box-orient:vertical;word-break:break-word}
.card__title.is-empty{color:var(--fg-3);font-weight:400;font-style:italic}
.card__prompt{font-family:var(--mono);font-size:12.5px;line-height:1.72;color:var(--fg-2);
  white-space:pre-wrap;word-break:break-word;overflow:hidden;
  max-height:calc(1.72em * 5 + .35em);
  -webkit-mask-image:linear-gradient(180deg,#000 82%,transparent 100%);
  mask-image:linear-gradient(180deg,#000 82%,transparent 100%)}
.card__prompt.is-empty{color:var(--fg-3);font-style:italic}
.card__foot{display:flex;align-items:center;justify-content:space-between;gap:8px;
  margin-top:auto;padding-top:4px}
.card__tags{display:flex;gap:5px;overflow:hidden;min-width:0}
.card__tags .tag{height:20px;padding:0 7px;font-size:10.5px;cursor:default}
.card__tags .tag:hover{color:var(--fg-2);border-color:var(--line)}
.card__more{font-size:10.5px;color:var(--fg-3);flex:none}
.card__copy{position:absolute;top:11px;right:11px;width:28px;height:28px;display:grid;
  place-items:center;border-radius:7px;color:var(--fg-3);
  background:rgba(255,255,255,.06);border:1px solid var(--line);
  opacity:0;transform:translateY(-4px) scale(.94);
  transition:opacity .2s var(--ease),transform .2s var(--ease),color .16s,background .16s}
.card:hover .card__copy,.card:focus-within .card__copy{opacity:1;transform:none}
.card__copy:hover{color:var(--acc);background:var(--acc-dim);border-color:rgba(var(--acc-rgb),.32)}
.card__copy svg{width:13px;height:13px}
.card__copy.is-done{color:var(--acc)}
.card__copy.is-done::after{content:"";position:absolute;inset:-1px;border-radius:7px;
  border:1px solid var(--acc);animation:ring .65s var(--ease) forwards}
@keyframes ring{from{opacity:.95;transform:scale(1)}to{opacity:0;transform:scale(1.6)}}
.card__pin{position:absolute;top:13px;right:45px;color:var(--acc);opacity:.85}
.card__pin svg{width:12px;height:12px;fill:currentColor;stroke:none}

.stars{display:inline-flex;gap:2px;color:var(--fg-3);flex:none}
.stars svg{width:12px;height:12px;stroke-width:1.4}
.stars svg.on{color:var(--acc);fill:var(--acc);stroke:none}
.stars--edit svg{width:20px;height:20px;cursor:pointer;
  transition:transform .18s var(--ease),color .18s}
.stars--edit svg:hover{transform:scale(1.18)}

.empty{padding:96px 20px;text-align:center;color:var(--fg-3);width:100%}
.empty__art{width:184px;height:110px;stroke:var(--fg-3);stroke-width:1.1;opacity:.4;
  fill:none;margin-bottom:24px}
.empty__t{font-size:14.5px;color:var(--fg-2);font-weight:600}
.empty__s{margin-top:6px;font-size:12px;color:var(--fg-3)}

/* ============================================================
   DRAWER
   ============================================================ */
/* The drawer's 30fps came mostly from this layer: a full-viewport
   backdrop-filter whose opacity animates, stacked on the topbar's own
   backdrop-filter — two chained backdrop reads per frame. Dropped saturate(),
   softened the radius, and the topbar's filter is turned off entirely while
   an overlay is up (the scrim covers it, so nothing is lost visually). */
.scrim{position:fixed;inset:0;z-index:40;background:rgba(var(--ink-rgb),.86);
  backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);
  opacity:0;transition:opacity .26s var(--ease-2)}
.scrim.is-on{opacity:1}
body:has(.scrim.is-on) .topbar,
body:has(.rail.is-open) .topbar{
  backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(var(--bg-rgb),.94)}
.drawer{position:fixed;top:0;right:0;z-index:50;width:min(560px,100vw);height:100dvh;
  display:flex;flex-direction:column;
  background:linear-gradient(180deg,var(--panel-2),var(--bg-1) 42%);
  border-left:1px solid var(--line-2);
  box-shadow:-40px 0 80px -50px rgba(0,0,0,1);
  transform:translateX(100%);transition:transform .44s var(--ease)}
.drawer.is-on{transform:none}
.drawer:focus{outline:none}
.drawer__top{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 18px;border-bottom:1px solid var(--line);flex:none}
.drawer__meta{display:flex;align-items:center;gap:9px;min-width:0}
.drawer__date{font-size:11px;color:var(--fg-3)}
.drawer__acts{display:flex;align-items:center;gap:1px}
.drawer__acts .ibtn{width:30px;justify-content:center;padding:0}
.drawer__body{flex:1;overflow-y:auto;overflow-x:hidden;padding:24px 26px 64px}
.drawer__title{font-size:24px;font-weight:600;letter-spacing:-.022em;line-height:1.28;
  word-break:break-word}
.drawer__stars{margin-top:10px}
.drawer.is-on .drawer__top,.drawer.is-on .drawer__title,.drawer.is-on .drawer__stars,
.drawer.is-on .blk,.drawer.is-on .drawer__tags,.drawer.is-on .facts{
  animation:layIn .5s var(--ease) both}
@keyframes layIn{from{opacity:0;transform:translateY(11px)}to{opacity:1;transform:none}}
.drawer.is-on .drawer__top{animation-delay:.05s}
.drawer.is-on .drawer__title{animation-delay:.1s}
.drawer.is-on .drawer__stars{animation-delay:.13s}
.drawer.is-on .blk:nth-of-type(1){animation-delay:.16s}
.drawer.is-on .blk:nth-of-type(2){animation-delay:.19s}
.drawer.is-on .blk:nth-of-type(3){animation-delay:.22s}
.drawer.is-on .drawer__tags{animation-delay:.25s}
.drawer.is-on .facts{animation-delay:.28s}

.blk{margin-top:26px}
.blk__h{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding-bottom:8px;margin-bottom:11px;border-bottom:1px solid var(--line)}
.blk__k{font-size:10px;font-weight:600;letter-spacing:.13em;color:var(--fg-3)}
.cp{display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 8px;border-radius:6px;
  font-size:11.5px;color:var(--fg-3);border:1px solid var(--line);transition:all .16s}
.cp:hover{color:var(--acc);border-color:rgba(var(--acc-rgb),.34);background:var(--acc-dim)}
.cp.is-done{color:var(--acc);border-color:rgba(var(--acc-rgb),.34)}
.cp svg{width:12px;height:12px}
.code{font-family:var(--mono);font-size:13px;line-height:1.8;color:var(--fg);
  white-space:pre-wrap;word-break:break-word;padding:14px 15px;border-radius:10px;
  background:rgba(0,0,0,.36);border:1px solid var(--line);box-shadow:var(--hi)}
.code--neg{color:var(--fg-2)}
.note{font-size:13.5px;line-height:1.78;color:var(--fg-2);white-space:pre-wrap;word-break:break-word}
.note.is-empty{color:var(--fg-3);font-style:italic}
.drawer__tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:26px}
.facts{display:flex;flex-wrap:wrap;gap:24px;margin-top:30px;padding-top:18px;
  border-top:1px solid var(--line)}
.facts dt{font-size:10px;font-weight:600;letter-spacing:.11em;color:var(--fg-3);margin-bottom:3px}
.facts dd{font-size:12px;color:var(--fg-2)}

/* ============================================================
   EDITOR
   ============================================================ */
.editor{position:fixed;inset:0;z-index:70;display:grid;place-items:center;padding:24px;
  background:rgba(var(--ink-rgb),.74);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  opacity:0;transition:opacity .26s var(--ease-2)}
.editor.is-on{opacity:1}
.editor__panel{width:100%;max-width:640px;max-height:calc(100dvh - 48px);
  display:flex;flex-direction:column;
  background:linear-gradient(180deg,var(--panel-2),var(--bg-1));
  border:1px solid var(--line-2);border-radius:var(--r-xl);overflow:hidden;
  box-shadow:0 50px 110px -40px rgba(0,0,0,.9),var(--hi);
  transform:translateY(14px) scale(.985);transition:transform .4s var(--ease)}
.editor.is-on .editor__panel{transform:none}
.editor__top{display:flex;align-items:center;justify-content:space-between;
  padding:15px 18px;border-bottom:1px solid var(--line);flex:none}
.editor__head{font-size:14.5px;font-weight:600;letter-spacing:-.012em}
.editor__body{padding:18px;overflow-y:auto;overflow-x:hidden;
  display:flex;flex-direction:column;gap:16px}
.editor__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:13px 18px;border-top:1px solid var(--line);flex:none;background:rgba(0,0,0,.22)}
.editor__hint{font-size:11px;color:var(--fg-3);display:flex;align-items:center;gap:4px}
.editor__btns{display:flex;gap:8px}
.fld{display:block;min-width:0}
.fld--row{display:flex;align-items:flex-end;justify-content:space-between;gap:20px}
.fld__k{display:block;font-size:10px;font-weight:600;letter-spacing:.13em;
  color:var(--fg-3);margin-bottom:7px}
.fld__opt{font-style:normal;font-weight:400;letter-spacing:.02em;opacity:.72}
.fld__in{width:100%;padding:10px 12px;border-radius:var(--r);background:rgba(0,0,0,.32);
  border:1px solid var(--line);color:var(--fg);font-size:13.5px;resize:vertical;
  transition:border-color .18s,background .18s,box-shadow .18s}
.fld__in::placeholder{color:var(--fg-3)}
.fld__in:focus{outline:0;border-color:rgba(var(--acc-rgb),.42);background:rgba(0,0,0,.48);
  box-shadow:0 0 0 3px rgba(var(--acc-rgb),.08)}
.fld__in--code{font-family:var(--mono);font-size:12.5px;line-height:1.74}
.seg{display:flex;flex-wrap:wrap;gap:5px}
.seg .tag{height:27px;padding:0 11px}
.sw{display:inline-flex;align-items:center;gap:8px;cursor:pointer;user-select:none}
.sw input{position:absolute;opacity:0;width:0;height:0}
.sw__box{width:34px;height:20px;border-radius:99px;background:var(--panel-3);
  border:1px solid var(--line-2);position:relative;transition:background .2s,border-color .2s}
.sw__box::after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;
  border-radius:99px;background:var(--fg-3);transition:transform .24s var(--ease),background .2s}
.sw input:checked + .sw__box{background:var(--acc-dim);border-color:rgba(var(--acc-rgb),.42)}
.sw input:checked + .sw__box::after{transform:translateX(14px);background:var(--acc)}
.sw input:focus-visible + .sw__box{outline:1px solid var(--acc);outline-offset:2px}
.sw__t{font-size:12.5px;color:var(--fg-2)}

/* ============================================================
   CONFIRM / TOAST
   ============================================================ */
.confirm{position:fixed;inset:0;z-index:90;display:grid;place-items:center;padding:24px;
  background:rgba(var(--ink-rgb),.76);backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
  opacity:0;transition:opacity .2s var(--ease-2)}
.confirm.is-on{opacity:1}
.confirm__box{width:100%;max-width:352px;padding:22px;border-radius:var(--r-xl);
  background:linear-gradient(180deg,var(--panel-2),var(--bg-1));
  border:1px solid var(--line-2);box-shadow:0 40px 90px -40px rgba(0,0,0,.9),var(--hi);
  transform:scale(.96);transition:transform .3s var(--ease)}
.confirm.is-on .confirm__box{transform:none}
.confirm__ico{width:26px;height:26px;color:var(--dgr);stroke-width:1.4;margin-bottom:12px}
.confirm__t{font-size:14.5px;font-weight:600;letter-spacing:-.012em}
.confirm__s{margin-top:6px;font-size:12.5px;line-height:1.62;color:var(--fg-2)}
.confirm__in{display:block;margin-top:14px}
.confirm__btns{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}

.toasts{position:fixed;left:50%;bottom:26px;z-index:100;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none}
.toast{display:flex;align-items:center;gap:8px;padding:9px 15px;border-radius:99px;
  font-size:12.5px;color:var(--fg);background:rgba(var(--panel-3-rgb),.95);
  border:1px solid var(--line-2);backdrop-filter:blur(10px);
  box-shadow:0 14px 38px rgba(0,0,0,.6);animation:toastIn .4s var(--ease) both}
.toast svg{width:13px;height:13px}
.toast--ok svg{color:var(--acc)}
.toast--err svg{color:var(--dgr)}
.toast.is-out{animation:toastOut .3s var(--ease-2) forwards}
@keyframes toastIn{from{opacity:0;transform:translateY(14px) scale(.96)}to{opacity:1;transform:none}}
@keyframes toastOut{to{opacity:0;transform:translateY(8px) scale(.97)}}

[hidden]{display:none !important}

/* ============================================================
   RESPONSIVE — rail becomes a bottom sheet under 1080px
   ============================================================ */
/* scrim + sheet both live inside .app's stacking context */
.sheet-scrim{position:fixed;inset:0;z-index:35;background:rgba(var(--ink-rgb),.66);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  opacity:0;transition:opacity .3s var(--ease-2)}
.sheet-scrim.is-on{opacity:1}

@media (max-width:1080px){
  #btnFilter{display:inline-flex}
  /* The toolbar collapses together with the rail: once the rail becomes a
     bottom sheet, six always-visible header icons stop being justified. At
     768px they left the search field 215px wide (measured via tools/shots.js)
     — too narrow for its own placeholder. The four month-frequency actions
     move into the same ⋯ sheet the phone uses, giving the field back ~150px. */
  #btnSync,#btnExport,#btnImport,#btnPass,#btnLock{display:none}
  #moreBtn{display:inline-flex}
  .shell{grid-template-columns:1fr;gap:0;padding:0 18px}
  .rail{position:fixed;left:0;right:0;bottom:0;top:auto;z-index:40;
    max-height:78dvh;margin:0;padding:0;
    border:1px solid var(--line-2);border-bottom:0;border-radius:20px 20px 0 0;
    background:linear-gradient(180deg,var(--panel-3),var(--panel) 26%);
    box-shadow:0 -30px 70px -30px rgba(0,0,0,1);
    transform:translateY(101%);transition:transform .44s var(--ease);
    overflow:hidden;display:block}
  .rail.is-open{transform:none}
  .rail__head{display:block;flex:none;padding:10px 18px 12px;border-bottom:1px solid var(--line)}
  .rail__grab{display:block;width:38px;height:4px;border-radius:99px;
    background:var(--line-3);margin:0 auto 12px}
  .rail__htop{display:flex;align-items:center;justify-content:space-between}
  .rail__k2{font-size:14px;font-weight:600;letter-spacing:-.01em}
  .rail__inner{display:flex;flex-direction:column;gap:22px;overflow-y:auto;overflow-x:hidden;
    max-height:calc(78dvh - 62px);padding:18px 18px calc(24px + env(safe-area-inset-bottom))}
  .rail__list{flex-direction:row;flex-wrap:wrap;gap:5px}
  .rail__grp--end{margin-top:0;padding-top:16px;border-top:0}
  .opt{width:auto;padding:7px 11px;border:1px solid var(--line);border-radius:99px}
  .opt.is-on{background:var(--acc-dim);border-color:rgba(var(--acc-rgb),.32);color:var(--acc)}
  .opt.is-on::before{display:none}
  .opt__n{margin-left:6px}
  .main{padding:18px 0 100px}
}
/* ── sync / backup ─────────────────────────────────────── */
#btnSync{position:relative}
.sdot{position:absolute;top:5px;right:5px;width:5px;height:5px;border-radius:50%;
  background:var(--acc)}
.sdot--err{background:var(--dgr)}
.sdot--busy{animation:dotPulse 1.05s ease-in-out infinite}
@keyframes dotPulse{0%,100%{opacity:1}50%{opacity:.22}}
#btnSync.ibtn--err{color:var(--dgr)}
#btnSync.ibtn--err:hover{color:var(--dgr);background:var(--dgr-dim)}

/* ── "more" sheet (≤1080px) ──────────────────────────────
   Holds the five month-frequency actions that get collapsed out of the header
   once the rail becomes a bottom sheet. Same surface language as the filter
   sheet; each row only proxies a click to the original (now hidden) button, so
   no action logic moved. Shown only while #moreBtn is — see that rule, up
   with the other show/hide rules in the primitives block. */
.sdot--flat{position:static;margin-left:auto}
.msheet{position:fixed;left:0;right:0;bottom:0;z-index:40;
  max-height:78dvh;border:1px solid var(--line-2);border-bottom:0;
  border-radius:20px 20px 0 0;
  background:linear-gradient(180deg,var(--panel-3),var(--panel) 26%);
  box-shadow:0 -30px 70px -30px rgba(0,0,0,1);
  transform:translateY(101%);transition:transform .44s var(--ease);
  overflow:hidden}
.msheet.is-open{transform:none}
.msheet__head{padding:10px 18px 12px;border-bottom:1px solid var(--line)}
.msheet__grab{display:block;width:38px;height:4px;border-radius:99px;
  background:var(--line-3);margin:0 auto 12px}
.msheet__htop{display:flex;align-items:center;justify-content:space-between}
.msheet__k{font-size:14px;font-weight:600;letter-spacing:-.01em}
.msheet__list{padding:6px 0 calc(14px + env(safe-area-inset-bottom))}
.mopt{display:flex;align-items:center;gap:13px;width:100%;min-height:52px;
  padding:0 18px;color:var(--fg-2);font-size:14.5px;text-align:left;
  transition:background .16s,color .16s}
.mopt svg{width:17px;height:17px;color:var(--fg-3)}
.mopt:hover{color:var(--fg);background:rgba(255,255,255,.045)}
.mopt + .mopt{border-top:1px solid var(--line)}
/* separates "session" from "data": lock is not the same kind of act as export */
.mopt--sep{border-top-color:var(--line-2)}
/* the sheet is unreachable above 720px ( #moreBtn is hidden there ), so make
   sure a resize with it open cannot strand it on a desktop */
@media (min-width:1081px){ .msheet{display:none} }

.gate__links{display:inline-flex;gap:14px;align-items:center}

/* the sync dialog reuses .confirm__box so it inherits the panel treatment;
   only the accent icon and the button row differ */
#sync .confirm__ico{color:var(--acc)}
#sync .confirm__btns{flex-wrap:wrap}
#sync .btn{display:inline-flex;align-items:center}
#sync .btn svg{width:13px;height:13px;margin-right:6px}
.grow{flex:1 1 auto;min-width:0}
.tokrow{display:flex;gap:8px;align-items:center}
.tokrow .field__in{flex:1 1 auto;min-width:0;font-size:12.5px;letter-spacing:0;
  text-overflow:ellipsis}
.tokrow .ibtn{flex:0 0 auto;width:38px;height:38px;justify-content:center;
  border:1px solid var(--line);background:rgba(0,0,0,.34)}
.tokrow .ibtn svg{width:15px;height:15px}

/* ── touch devices: there is no hover, so anything hover-only is dead UI ── */
@media (hover:none){
  /* the copy button used to be hover-revealed — unreachable on a phone */
  .card__copy{opacity:1;transform:none;width:38px;height:38px;
    background:rgba(var(--bg-1-rgb),.76)}
  .card__copy svg{width:15px;height:15px}
  .card__pin{right:58px}
  /* the copy button is now permanently in the top-right corner, so grow the
     meta row to its height and keep the date clear of it — otherwise the date
     renders underneath the button (visible in .shots/07-mobile-grid.png) */
  .card__top{padding-right:44px;min-height:38px}
  /* sticky :hover would leave a card stuck in its lifted state after a tap */
  .card:hover{transform:none;border-color:var(--line);
    box-shadow:var(--hi),0 14px 30px -26px rgba(0,0,0,1)}
  .card:hover::before{opacity:0;transform:scaleX(.3)}
  .card:active{border-color:var(--line-2);
    background:linear-gradient(180deg,var(--panel-3),var(--bg-1))}
  .stars--edit svg:hover{transform:none}
  /* a tap still sets :hover and it sticks until something else is tapped, so the
     hover background must never repaint the button. This used to force
     background:var(--acc) on EVERY .btn — harmless while the primary action was
     itself accent-filled, but once the accent was demoted to punctuation it made
     取消 (ghost) and 删除 (danger) flash amber on tap, and turned the now-light
     primary button amber too. :active already carries the press. */
  .btn:hover{background:transparent}
  .btn--primary:hover{background:var(--fg)}
}

@media (max-width:720px){
  /* --top is deliberately NOT overridden here any more. The phone header is
     height:auto (it used to be a fixed 53px two-row bar) and the rail is
     position:fixed at this width, so nothing reads it — a 53px value would
     only be a lie about a header that measures 59px. */
  :root{--gap:11px}
  /* ONE row: [iris] [search] [filter] [⋯]. This used to wrap to a second line,
     which put six unlabelled 1.4px-stroke glyphs — four of them month-frequency
     settings — into the thumb zone and consumed 145px of every scroll, 17% of
     the viewport. Those five actions now live in #moreSheet; the FAB already
     covers "new". */
  .topbar{gap:7px;height:auto;flex-wrap:nowrap;
    padding:calc(7px + env(safe-area-inset-top)) 12px 7px}
  .topbar__l{flex:0 0 auto;gap:6px}
  .topbar__c{flex:1 1 auto;min-width:0;order:0}
  .topbar__r{flex:0 0 auto;gap:2px;margin-left:0}
  .brand{min-width:40px;min-height:40px;justify-content:center}
  .brand__txt{font-size:12px;letter-spacing:.12em}
  /* the running count is the first thing to go when the row gets tight */
  .counter{display:none}
  /* the four month-frequency actions and #moreBtn were already collapsed by
     the ≤1080px block — the phone only tightens what is left. The .ibtn__t
     display:none that used to sit here is gone: 720px is inside the ≤1080px
     range, so the base rule at the top of the file already covers it. */
  .shell{padding:0 12px}
  .main{padding-bottom:118px}
  .card__prompt{max-height:calc(1.72em * 4 + .35em)}

  /* 44-ish px targets — these are thumbs, not cursors */
  .ibtn{min-width:42px;height:42px;justify-content:center;padding:0}
  .btn{height:44px;padding:0 18px}
  .btn--sm{height:38px;padding:0 14px}
  .btn--block{height:48px}
  .search__in{height:44px;padding:0 42px 0 36px}
  .search__ico{left:12px}
  #searchClear{right:10px;width:26px;height:26px}
  .field__in{height:48px;padding:0 14px}
  .cp{height:38px;padding:0 13px;font-size:12px}
  .cp svg{width:14px;height:14px}
  .rail__tags{gap:7px}
  .rail__tags .tag{height:38px;padding:0 14px;font-size:12px}
  .opt{min-height:42px;padding:10px 15px}
  .stars--edit{gap:4px}
  .stars--edit svg{width:28px;height:28px}
  .sw{padding:6px 0}
  .sw__box{width:44px;height:26px}
  .sw__box::after{width:20px;height:20px}
  .sw input:checked + .sw__box::after{transform:translateX(18px)}
  /* iOS zooms the viewport on focus for any field under 16px */
  .search__in,.field__in,.fld__in,.fld__in--code{font-size:16px}
  .fld__in{padding:12px 14px}

  /* primary create action moves to the thumb zone */
  .fab{display:grid;place-items:center;position:fixed;
    right:16px;bottom:calc(18px + env(safe-area-inset-bottom));
    z-index:20;width:54px;height:54px;border-radius:50%;
    background:var(--fg);color:#0a0908;
    box-shadow:0 10px 26px -10px rgba(0,0,0,.9),
      0 1px 0 rgba(255,255,255,.55) inset;
    transition:transform .16s var(--ease),box-shadow .2s var(--ease-2)}
  .fab svg{width:22px;height:22px;stroke-width:2.1}
  .fab:active{transform:scale(.9)}
  .fab:focus-visible{outline:2px solid var(--fg);outline-offset:3px}
  .btn--new{display:none}

  .drawer{width:100vw;border-left:0}
  .drawer__top{height:60px;padding:0 14px}
  .drawer__acts .ibtn{width:44px;height:44px}
  .drawer__body{padding:20px 16px calc(72px + env(safe-area-inset-bottom))}
  .drawer__title{font-size:21px}
  .code{font-size:13.5px;padding:13px 14px}

  .editor{padding:0}
  .editor__panel{max-width:100%;max-height:100dvh;height:100dvh;border:0;border-radius:0}
  .editor__top{height:58px;padding:0 14px}
  .editor__foot{padding:11px 14px calc(11px + env(safe-area-inset-bottom))}
  .fld--row{flex-direction:column;align-items:stretch;gap:16px}
  .facts{gap:16px}
  .search__kbd{display:none}

  .toasts{bottom:calc(84px + env(safe-area-inset-bottom));width:100%;padding:0 14px}
  .rail__inner{max-height:calc(78dvh - 68px);
    padding:16px 16px calc(22px + env(safe-area-inset-bottom))}
  .gate{padding:calc(16px + env(safe-area-inset-top)) 16px
    calc(16px + env(safe-area-inset-bottom))}
  .gate__panel{padding:22px 20px 18px}
  .gate__foot{flex-wrap:wrap}
  .gate__links{width:100%}

  /* the spacer that right-aligns 关闭/立即同步 would push the destructive
     "解除配对" onto its own line at this width — let the row wrap naturally */
  .grow{display:none}
  #sync .confirm__btns{gap:7px}
  #sync .confirm__btns .btn{min-height:42px}
  .tokrow .ibtn{width:44px;height:44px}
}
/* below 560px the wordmark costs ~93px, which is the difference between a
   readable search field and a clipped one on a 360px phone */
@media (max-width:560px){
  .brand__txt{display:none}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;
    animation-iteration-count:1 !important;transition-duration:.01ms !important}
  .glow{display:none}
  .scan{display:none}
}
