/* UI redesign draft, 2026-10-28 — per Obsidian
   projects/video-editing-agent/2026-10-27-ui-redesign-design.md.
   Layered on top of upload.css, only loaded on pages past login (_shell())
   — and also linked directly on /login and /signup (_auth_page()) so the
   :root variables below are shared everywhere.

   2026-10-28 (glass pass) — Vlad locked in a visual direction from two
   Pinterest references: frosted translucent glass cards, blurred
   backgrounds, blue/indigo→teal gradient accents, pill-shaped buttons,
   soft rounded corners, a thin light border to sell the "glass edge".
   Explicitly NOT the neon-purple/pink cyberpunk style also shown and
   rejected. Pure visual pass — no structure/route/JS behavior changes. */

:root{
  --glass-bg:rgba(32,44,62,.46);
  --glass-bg-strong:rgba(28,40,56,.68);
  --glass-bg-soft:rgba(255,255,255,.06);
  --glass-border:rgba(255,255,255,.16);
  --glass-border-soft:rgba(255,255,255,.09);
  --glass-blur:blur(18px) saturate(160%);
  --glass-blur-sm:blur(10px) saturate(150%);
  --glass-shadow:0 10px 34px rgba(0,0,0,.38);
  --accent-a:#5b8def;   /* indigo-blue — evolves the app's existing #4a90d9 */
  --accent-b:#3ad6c0;   /* teal/mint — nods to the premium dark-glass ref */
  --accent-grad:linear-gradient(135deg,var(--accent-a),var(--accent-b));
  --accent-glow:0 4px 20px rgba(58,214,192,.28),0 2px 8px rgba(91,141,239,.22);
  --text-primary:#f1f5f9;
  --text-secondary:#9db0c4;
  --radius-lg:22px;
  --radius-md:18px;
  --radius-sm:12px;
  --radius-pill:999px;
  --ease:cubic-bezier(.22,1,.36,1);
}

/* --- base: dark navy/teal canvas behind every glass surface ------------ */
body{background:
  radial-gradient(circle at 18% -8%,rgba(58,214,192,.10),transparent 42%),
  radial-gradient(circle at 85% 0%,rgba(91,141,239,.10),transparent 38%),
  linear-gradient(170deg,#0a0f17,#080d13 55%,#070c11);
  color:var(--text-primary)}
a{color:var(--accent-b)}

/* --- office background layer ---------------------------------------- */
/* Spec calls for a seedance video loop here (office.bg-video). No video
   has been generated yet (costs money, needs Vlad's go-ahead first) — this
   is a CSS-only placeholder so the layout/readability work doesn't block
   on that. Swapping in a real <video poster=...><source src=...></video>
   later is a one-line change, see _shell() in footage_app.py.
   2026-10-28 glass pass: gradient hues moved from blue/green to the locked
   indigo→teal accent pair so the placeholder stops clashing with the rest
   of the new palette. */
.office-bg{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
background:#080d13}
.office-bg-placeholder{position:absolute;inset:-10%;
background:linear-gradient(120deg,#132a42,#0a0f17 45%,#0d2d2a 80%,#0a0f17);
background-size:200% 200%;animation:office-bg-drift 18s ease-in-out infinite;
filter:blur(42px) saturate(1.15);opacity:.85}
@keyframes office-bg-drift{
  0%{background-position:0% 50%}
  50%{background-position:100% 50%}
  100%{background-position:0% 50%}
}
/* 2026-10-28 glass-polish pass: Vlad's feedback — the placeholder backdrop
   "reads as boring even as a placeholder", wanted something with visible
   movement ("может даже шевелящееся") without distracting from the glass
   cards on top. Three soft blurred glow blobs, pure CSS radial-gradients +
   transform-only keyframes (cheap to animate, no layout thrash), each
   drifting on its own slow/offset loop so the motion doesn't read as one
   obvious repeating beat. Still a budget placeholder for the eventual paid
   video loop — not touching that gating, no image/video generation here. */
.office-bg-blob{position:absolute;border-radius:50%;mix-blend-mode:screen;
will-change:transform}
.office-bg-blob--a{width:46vmax;height:46vmax;top:-18vmax;left:-14vmax;
background:radial-gradient(circle,rgba(91,141,239,.30),transparent 68%);
filter:blur(6vmax);animation:office-blob-a 22s ease-in-out infinite}
.office-bg-blob--b{width:40vmax;height:40vmax;bottom:-16vmax;right:-12vmax;
background:radial-gradient(circle,rgba(58,214,192,.26),transparent 68%);
filter:blur(6vmax);animation:office-blob-b 26s ease-in-out infinite}
.office-bg-blob--c{width:30vmax;height:30vmax;top:38%;left:58%;
background:radial-gradient(circle,rgba(91,141,239,.18),transparent 70%);
filter:blur(5vmax);animation:office-blob-c 19s ease-in-out infinite}
@keyframes office-blob-a{
  0%,100%{transform:translate(0,0) scale(1)}
  50%{transform:translate(6vmax,8vmax) scale(1.14)}
}
@keyframes office-blob-b{
  0%,100%{transform:translate(0,0) scale(1)}
  50%{transform:translate(-5vmax,-7vmax) scale(1.1)}
}
@keyframes office-blob-c{
  0%,100%{transform:translate(-50%,-50%) scale(1)}
  50%{transform:translate(-56%,-42%) scale(1.2)}
}
.office-bg-overlay{position:absolute;inset:0;
background:linear-gradient(180deg,rgba(8,13,19,.5),rgba(8,13,19,.86) 70%,#080d13)}
@media (prefers-reduced-motion: reduce){
  .office-bg-placeholder,.office-bg-blob{animation:none}
}
@media (max-width:480px){
  /* mobile: darker/blurrier so widgets stay readable on a small screen */
  .office-bg-placeholder{filter:blur(56px) saturate(1.1);opacity:.65}
  .office-bg-blob{opacity:.8}
}

/* --- shared glass card + button system ----------------------------------
   Generic `.box`/`button` win over upload.css's plain dark-card/flat-blue
   rules via normal cascade (this stylesheet loads after upload.css on
   every shell page, and after the inline auth-page <style> reads these
   custom properties too). Anything with its own class (nav items, cabinet
   trigger, snake/pw-toggle ghost buttons, logout) already carries higher
   specificity and keeps its own look, see per-component rules below. */
.box{background:var(--glass-bg);-webkit-backdrop-filter:var(--glass-blur);
backdrop-filter:var(--glass-blur);border:1px solid var(--glass-border);
border-radius:var(--radius-lg);box-shadow:var(--glass-shadow)}
h2{color:var(--text-primary)}
.hint{color:var(--text-secondary)}

button{background:var(--accent-grad);color:#07131a;font-weight:600;
border:none;border-radius:var(--radius-pill);padding:12px 22px;font-size:15px;
cursor:pointer;box-shadow:var(--accent-glow);
transition:transform .14s var(--ease),box-shadow .18s var(--ease),filter .14s var(--ease)}
button:hover{transform:translateY(-1px);filter:brightness(1.06)}
/* 2026-10-28 glass-polish pass: Vlad re-emphasized press feedback twice —
   bump the scale-down further (.96→.93) and add a tighter glow-pulse so a
   press reads clearly even on a quick tap, not just hover. */
button:active{transform:translateY(0) scale(.93);filter:brightness(.95);
box-shadow:0 2px 14px rgba(58,214,192,.5),0 1px 4px rgba(91,141,239,.4)}
button:disabled{opacity:.5;cursor:default;transform:none;filter:none;box-shadow:none}

input,select,textarea{background:var(--glass-bg-soft);color:var(--text-primary);
border:1px solid var(--glass-border-soft);border-radius:var(--radius-sm);
transition:border-color .16s var(--ease),background .16s var(--ease)}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent-b);
background:rgba(255,255,255,.1)}
input[type=color]{padding:2px;border-radius:8px}

/* topbar/page-content sit above the office-bg layer */
/* 2026-10-28 glass-polish pass: this used to also list .topbar and
   .bottom-nav with position:relative — that silently stomped upload.css's
   position:sticky/position:fixed on those two (same specificity, this file
   loads later), so the bottom nav was never actually pinned to the
   viewport bottom, it just sat wherever it fell in normal document flow.
   That's the real reason it "didn't look reskinned" — the glass background
   rules below were always correct, the bar was just floating mid-page.
   Scoped to .page-content only now; topbar/bottom-nav keep upload.css's
   position+z-index and get their own glass rules below. */
.page-content{position:relative;z-index:1}
.topbar{background:var(--glass-bg-strong);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);border-bottom:1px solid var(--glass-border-soft)}
.topbar-title{color:var(--text-primary)}
.topbar-user{color:var(--text-secondary)}

/* --- bottom nav: glass bar, glowing active tab, ghost items ------------- */
.bottom-nav{background:var(--glass-bg-strong);-webkit-backdrop-filter:var(--glass-blur);
backdrop-filter:var(--glass-blur);border-top:1px solid var(--glass-border-soft);
box-shadow:0 -10px 30px rgba(0,0,0,.35);padding:7px 8px calc(7px + env(safe-area-inset-bottom));
gap:4px}
.nav-item,.nav-item--logout button{position:relative;background:none!important;
box-shadow:none!important;border-radius:var(--radius-md);margin:0 2px;
color:var(--text-secondary);font-weight:400;padding:7px 4px 8px;
transition:color .18s var(--ease),transform .14s var(--ease),background .2s var(--ease)}
.nav-item:hover{background:var(--glass-bg-soft)!important}
.nav-item:active,.nav-item--logout button:active{transform:scale(.88);filter:none;
background:var(--glass-bg-soft)!important}
.nav-item .nav-icon{display:inline-block;transition:filter .2s var(--ease),transform .2s var(--ease)}
/* 2026-10-28 icon pass: emoji glyphs replaced with inline SVG line-icons
   (stroke="currentColor" in footage_app.py's ICON_* constants) - just needs
   sizing here, color/glow/transform all already come from the rules above
   via .nav-icon/.nav-item.active since currentColor inherits them. */
.nav-icon svg{width:20px;height:20px;display:block}
.nav-item.active{color:var(--text-primary);background:var(--glass-bg-soft)!important;
box-shadow:inset 0 0 0 1px var(--glass-border-soft)!important}
.nav-item.active .nav-icon{filter:drop-shadow(0 0 7px rgba(58,214,192,.6));transform:translateY(-1px)}
.nav-item.active::after{content:"";position:absolute;bottom:1px;left:50%;
transform:translateX(-50%);width:16px;height:3px;border-radius:var(--radius-pill);
background:var(--accent-grad);box-shadow:var(--accent-glow)}

/* --- balance widget --------------------------------------------------- */
.balance-widget{position:fixed;top:12px;right:12px;z-index:11;display:flex;
align-items:center;gap:8px;padding:6px 8px 6px 14px;border-radius:var(--radius-pill);
background:var(--glass-bg-strong);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);border:1px solid var(--glass-border);
font-size:13px;color:var(--text-primary);box-shadow:var(--glass-shadow)}
.balance-widget button{padding:6px 14px;font-size:12px}
.balance-divider{width:1px;height:18px;background:var(--glass-border-soft);flex:none}

/* --- personal-cabinet trigger: avatar + label, lives next to balance --- */
/* 2026-10-28 round-2 feedback: was a standalone ambiguous arrow (".cabinet-tab")
   floating on the right edge — moved here, next to "Пополнить", with an
   explicit "Мой кабинет" label so it reads as a nav destination. */
.cabinet-avatar-btn{display:flex;align-items:center;gap:6px;padding:4px 10px 4px 4px;
border-radius:var(--radius-pill);background:var(--glass-bg-soft);
border:1px solid var(--glass-border-soft);color:var(--text-primary);font-size:12px;
font-weight:400;box-shadow:none;cursor:pointer;
transition:background .16s var(--ease),transform .16s var(--ease)}
.cabinet-avatar-btn:hover{transform:translateY(-1px);background:rgba(255,255,255,.1)}
.cabinet-avatar-btn:active{transform:translateY(0) scale(.95);filter:none}
.cabinet-avatar{width:22px;height:22px;border-radius:50%;
background:var(--accent-grad);display:flex;align-items:center;justify-content:center;
font-size:12px;flex:none}
.cabinet-avatar-label{white-space:nowrap}
@media (max-width:480px){
  /* mobile: keep the pill compact, label still visible per the brief
     ("explicit labeled tab/button", not just an icon) */
  .cabinet-avatar-label{font-size:11px}
}

/* --- toast -------------------------------------------------------------- */
.toast{position:fixed;left:50%;bottom:84px;transform:translate(-50%,12px);
z-index:30;background:var(--glass-bg-strong);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);color:var(--text-primary);padding:10px 18px;
border-radius:var(--radius-pill);border:1px solid var(--glass-border);font-size:13px;
box-shadow:var(--glass-shadow);opacity:0;
transition:opacity .25s var(--ease),transform .25s var(--ease);pointer-events:none;max-width:88vw;
text-align:center}
.toast.show{opacity:1;transform:translate(-50%,0)}

/* --- personal cabinet swipe panel --------------------------------------- */
/* Trigger is now the avatar pill next to the balance widget (see above) —
   swipe-from-right-edge still opens the panel too (office.js), this layer
   is just the panel/backdrop itself. */
.cabinet-backdrop{position:fixed;inset:0;z-index:19;background:rgba(5,8,12,.5);
-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
opacity:0;pointer-events:none;transition:opacity .25s var(--ease)}
.cabinet-backdrop.open{opacity:1;pointer-events:auto}
.cabinet-panel{position:fixed;top:0;right:0;bottom:0;z-index:20;width:82vw;
max-width:360px;background:var(--glass-bg-strong);-webkit-backdrop-filter:var(--glass-blur);
backdrop-filter:var(--glass-blur);border-left:1px solid var(--glass-border);
box-shadow:-10px 0 36px rgba(0,0,0,.4);
transform:translateX(100%);transition:transform .32s var(--ease);
display:flex;flex-direction:column;padding:18px;box-sizing:border-box;overflow-y:auto}
.cabinet-panel.open{transform:translateX(0)}
.cabinet-panel h3{margin:4px 0 14px;color:var(--text-primary);font-size:16px}
.cabinet-panel .cabinet-close{align-self:flex-end;background:none!important;
box-shadow:none!important;color:var(--text-secondary);font-size:20px;padding:4px 8px;
font-weight:400;transition:color .16s var(--ease)}
.cabinet-panel .cabinet-close:hover{color:var(--text-primary)}
.cabinet-panel ul{list-style:none;padding:0;margin:0}

/* --- job list / job cards (_jobs_list_html, shared by /jobs + cabinet) -- */
.job-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px}
.job-list-empty{color:var(--text-secondary);text-align:center;padding:18px 0}
.job-card{display:flex;align-items:center;gap:12px;padding:10px 16px;border-radius:var(--radius-md);
background:var(--glass-bg);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);border:1px solid var(--glass-border-soft);
color:var(--text-primary);text-decoration:none;font-size:14px;
box-shadow:0 4px 16px rgba(0,0,0,.22);
transition:transform .16s var(--ease),background .16s var(--ease),border-color .16s var(--ease)}
.job-card:hover{transform:translateY(-2px);background:var(--glass-bg-strong);
border-color:var(--glass-border)}
.job-card:active{transform:translateY(0) scale(.985)}
/* 2026-10-31: readable-history pass (date label + thumbnail instead of a
   raw job_id hex string) */
.job-thumb{width:64px;height:48px;object-fit:cover;border-radius:10px;
flex-shrink:0;background:rgba(255,255,255,.06)}
.job-card-date{font-size:14px;color:var(--text-primary)}

/* --- auth page: Telegram stub + collapsible password form -------------- */
.tg-cta{display:flex;align-items:center;justify-content:center;gap:10px;
width:100%;padding:13px 16px;font-size:15px;font-weight:600;border-radius:var(--radius-pill);
border:none;background:linear-gradient(135deg,#2aabee,#1c8dd4);color:#fff;cursor:pointer;
box-sizing:border-box;margin-bottom:14px;box-shadow:0 4px 18px rgba(42,171,238,.3);
transition:transform .16s var(--ease),filter .16s var(--ease)}
.tg-cta:hover{transform:translateY(-1px);filter:brightness(1.05)}
.tg-cta:active{transform:translateY(0) scale(.97)}
.tg-cta svg{width:20px;height:20px;flex:none}
/* 2026-10-29: real widget container — Telegram's iframe button drops in
   here, this just keeps it centered/spaced the same as the old stub button
   it replaced so the surrounding divider/toggle layout doesn't shift. */
.tg-widget-wrap{display:flex;justify-content:center;margin-bottom:14px}
.auth-divider{display:flex;align-items:center;gap:10px;color:var(--text-secondary);
font-size:12px;margin:14px 0}
.auth-divider::before,.auth-divider::after{content:"";flex:1;height:1px;
background:var(--glass-border-soft)}
.pw-toggle{background:none!important;box-shadow:none!important;color:var(--accent-b);
font-size:13px;padding:4px 0;font-weight:400;text-decoration:underline;cursor:pointer}
.pw-toggle:hover{transform:none;filter:none;opacity:.85}
.pw-form{display:none;margin-top:10px;opacity:0;transition:opacity .2s var(--ease)}
.pw-form.open{display:block;opacity:1}

/* --- render progress card ------------------------------------------------ */
.progress-card{background:var(--glass-bg);-webkit-backdrop-filter:var(--glass-blur);
backdrop-filter:var(--glass-blur);border:1px solid var(--glass-border);
border-radius:var(--radius-lg);box-shadow:var(--glass-shadow);padding:18px;max-width:420px}
.progress-track{background:rgba(255,255,255,.08);border-radius:var(--radius-pill);
height:10px;overflow:hidden}
.progress-fill{background:var(--accent-grad);height:100%;width:0%;border-radius:var(--radius-pill);
transition:width .6s var(--ease)}
.progress-phase{font-size:13px;color:var(--text-secondary);margin:10px 0 0}
.progress-row{display:flex;align-items:center;gap:10px;margin-bottom:8px}

/* --- office render scene: dominant visual on the wait screen ------------ */
/* 2026-10-28 round-2 feedback: push the "office with mini-agents" concept
   (ref: pixel-art agent tools like pixel-agents-hq/pixel-agents — style/
   interaction language only, no code/assets borrowed) to the front of the
   waiting screen, ahead of the progress bar and the snake game. Still a
   CSS-only placeholder (no sprite art yet, same as .office-bg-placeholder)
   — characters are plain divs, not images.
   2026-10-28 glass pass: scene backdrop + speech bubble moved to the same
   indigo/teal + frosted-glass language as the rest of the app. */
.office-scene{position:relative;height:150px;border-radius:var(--radius-md);overflow:hidden;
margin-bottom:14px;
background:linear-gradient(160deg,#16283f,#0c1620 60%,#0e2a26);
border:1px solid var(--glass-border-soft)}
.office-desk{position:absolute;bottom:14px;width:46px;height:20px;border-radius:6px;
background:rgba(255,255,255,.08);box-shadow:0 0 0 1px var(--glass-border-soft)}
.office-desk--1{left:30px}
.office-desk--2{left:50%;transform:translateX(6px)}
.office-char{position:absolute;bottom:32px;width:30px;text-align:center;
animation:office-char-bob 2.4s ease-in-out infinite}
.office-char--1{left:28px;animation-delay:0s}
.office-char--2{left:50%;margin-left:4px;animation-delay:.7s}
.char-head{width:15px;height:15px;border-radius:50%;background:#e8c39e;margin:0 auto}
.char-body{width:26px;height:24px;border-radius:9px 9px 4px 4px;margin:2px auto 0}
.office-char--1 .char-body{background:var(--accent-a)}
.office-char--2 .char-body{background:var(--accent-b)}
@keyframes office-char-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
.speech-bubble{position:absolute;top:10px;left:50%;transform:translateX(-50%) scale(.94);
background:rgba(255,255,255,.88);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
color:#101820;font-size:12px;font-weight:600;
padding:6px 12px;border-radius:14px;border:1px solid rgba(255,255,255,.5);
max-width:86%;white-space:nowrap;
overflow:hidden;text-overflow:ellipsis;opacity:0;
transition:opacity .35s var(--ease),transform .25s var(--ease);box-shadow:0 3px 12px rgba(0,0,0,.35)}
.speech-bubble::after{content:"";position:absolute;bottom:-6px;left:50%;
transform:translateX(-50%);border-width:6px 6px 0;border-style:solid;
border-color:rgba(255,255,255,.88) transparent transparent}
.speech-bubble.show{opacity:1;transform:translateX(-50%) scale(1)}
.speech-bubble.pulse{animation:bubble-pulse .4s var(--ease)}
@keyframes bubble-pulse{
  0%{transform:translateX(-50%) scale(1)}
  50%{transform:translateX(-50%) scale(1.08)}
  100%{transform:translateX(-50%) scale(1)}
}
@media (prefers-reduced-motion:reduce){
  .office-char{animation:none}
  .speech-bubble.pulse{animation:none}
}

/* Snake CTA: deliberately muted/secondary per Vlad's "это, наверное,
   лишнее, или просто по желанию" — kept working, not the focal point. */
.snake-cta{margin-top:14px;background:none!important;box-shadow:none!important;
color:var(--text-secondary);font-size:12px;font-weight:400;
border:1px solid var(--glass-border-soft);padding:7px 14px;border-radius:var(--radius-pill)}
.snake-cta:hover{transform:none;filter:none;border-color:var(--glass-border)}
.snake-cta:active{transform:scale(.96)}

/* --- snake game ----------------------------------------------------------- */
.snake-wrap{display:none;margin-top:14px;text-align:center;opacity:0;
transition:opacity .2s var(--ease)}
.snake-wrap.open{display:block;opacity:1}
#snake-canvas{background:#080d13;border-radius:var(--radius-sm);
border:1px solid var(--glass-border-soft);max-width:100%}
.snake-score{color:var(--text-secondary);font-size:12px;margin-top:6px}

/* --- finished-job view gate ------------------------------------------------ */
/* 2026-10-28 round-2 feedback: don't auto-load/play the result inline —
   show this button once the job is done, reveal the existing video+download
   block only on click (same page/route, no new view built). */
.view-gate-btn{margin-top:10px}
.result-view{display:none;margin-top:14px;opacity:0;transition:opacity .25s var(--ease)}
.result-view.open{display:block;opacity:1}

/* --- upload page: clip list + captions box, same glass language -------- */
#clip-list .card{background:var(--glass-bg);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);border:1px solid var(--glass-border-soft);
border-radius:var(--radius-sm)}
#clip-list .ctrl button{padding:5px 10px;font-size:13px;border-radius:var(--radius-pill)}
.captions-box{background:var(--glass-bg);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);border:1px solid var(--glass-border-soft);
border-radius:var(--radius-md);padding:12px 16px;margin-bottom:16px}

/* 2026-10-29: transition picker, same glass card as .captions-box — the
   <select> itself already gets glassed via the generic input/select/
   textarea rule above, this just adds the card wrapper + label. */
.transition-box{background:var(--glass-bg);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);border:1px solid var(--glass-border-soft);
border-radius:var(--radius-md);padding:12px 16px;margin-bottom:16px}
.transition-box label{display:block;font-size:13px;color:var(--text-secondary);margin-bottom:6px}
.transition-box select{width:100%;box-sizing:border-box;padding:10px 14px}

/* 2026-10-29: template picker — accent-tinted so it visually reads as
   "pick this first", distinct from the individual advanced pickers below
   it which it can override (see upload.js for the disable-on-select
   behaviour). */
.template-box{background:var(--glass-bg);-webkit-backdrop-filter:var(--glass-blur-sm);
backdrop-filter:var(--glass-blur-sm);border:1px solid var(--accent-b);
border-radius:var(--radius-md);padding:12px 16px;margin-bottom:16px}
.template-box label{display:block;font-size:13px;color:var(--text-secondary);margin-bottom:6px}
.template-box select{width:100%;box-sizing:border-box;padding:10px 14px}
#style-pickers.template-active,#transition-picker.template-active,#grade-picker.template-active{
opacity:.45;pointer-events:none}

/* 2026-10-31: visual card pickers (template/transition/color-grade) —
   replaces the plain text <select> list Vlad complained read as "just
   names, no idea what it looks like". Each card is a <label> wrapping a
   visually-hidden radio + a preview (video for transitions, img for
   grades/templates) + a text caption; :has(:checked) highlights the
   selected card, no JS needed for the visual state. */
.style-picker{display:flex;flex-wrap:wrap;gap:10px}
.style-card{position:relative;display:flex;flex-direction:column;align-items:center;
gap:6px;padding:8px;width:92px;border-radius:12px;cursor:pointer;
border:2px solid transparent;transition:border-color .16s var(--ease),transform .16s var(--ease)}
.style-card:active{transform:scale(.97)}
.style-card:has(input:checked){border-color:var(--accent-b);background:var(--glass-bg-strong)}
.style-card input{position:absolute;opacity:0;pointer-events:none}
.style-card-preview{width:76px;height:56px;object-fit:cover;border-radius:8px;
background:rgba(255,255,255,.06)}
.style-card-custom{width:76px;height:56px;border-radius:8px;display:flex;
align-items:center;justify-content:center;background:rgba(255,255,255,.06);
font-size:12px;color:var(--text-secondary)}
.style-card-label{font-size:11px;color:var(--text-primary);text-align:center;line-height:1.25}

/* --- upload dropzone (2026-10-28, Task 2) -------------------------------
   Replaces the bare native <input type=file> "Choose Files/No file chosen"
   control. The real <input> stays functional and in the DOM - just made
   invisible (opacity:0) and stretched to cover the whole card
   (position:absolute;inset:0), so every native behavior (click-to-browse,
   OS drag-and-drop onto the control, the `required` validation bubble)
   keeps working exactly as before, anchored to this card instead of a tiny
   unstyled button. All the visible children are pointer-events:none so
   clicks always fall through to the input underneath. */
.dropzone{position:relative;display:flex;flex-direction:column;align-items:center;
justify-content:center;gap:6px;padding:30px 20px;margin-bottom:16px;text-align:center;
border-radius:var(--radius-lg);border:1.5px dashed var(--glass-border);
background:var(--glass-bg-soft);overflow:hidden;cursor:pointer;
transition:border-color .2s var(--ease),background .2s var(--ease),transform .16s var(--ease)}
.dropzone:hover{border-color:var(--accent-b);background:rgba(58,214,192,.07)}
.dropzone.dragover{border-color:var(--accent-b);background:rgba(58,214,192,.13);
box-shadow:var(--accent-glow);transform:scale(1.01)}
.dropzone-input{position:absolute;inset:0;width:100%;height:100%;margin:0;
opacity:0;cursor:pointer;z-index:2}
.dropzone-icon{width:32px;height:32px;color:var(--accent-b);pointer-events:none}
.dropzone-icon svg{width:100%;height:100%}
.dropzone-text{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--text-primary);
pointer-events:none}
.dropzone-hint{margin:0;font-size:12px;color:var(--text-secondary);pointer-events:none}
.dropzone.has-files{padding:16px 20px}
.dropzone.has-files .dropzone-character{display:none}

/* --- pixel-art helper character inside the dropzone (Task 3) -----------
   Same silhouette/technique Vlad approved comparing three mockups earlier
   today (compare.html option (a) "Pixel-art") - reused verbatim as a
   placeholder "waiting for your file" helper, not new illustration work.
   Idle animation: gentle vertical bob + a slow eye-blink via opacity, both
   pure CSS keyframes, no JS. Hidden once files are chosen (.has-files
   above) so it doesn't compete with the clip-card list for attention. */
.dropzone-character{pointer-events:none;margin-bottom:2px}
.char-pixel{width:46px;height:74px;image-rendering:pixelated;
animation:char-bob 2.6s ease-in-out infinite}
@keyframes char-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
.char-pixel-eye{animation:char-blink 4.2s ease-in-out infinite}
@keyframes char-blink{0%,90%,100%{opacity:1}93%,96%{opacity:.1}}
@media (prefers-reduced-motion:reduce){
  .char-pixel,.char-pixel-eye{animation:none}
}

/* --- page-transition overlay + enter/leave choreography -----------------
   2026-10-28 glass-polish pass. The app is a traditional server-rendered
   multi-page site (every nav/form submit is a real page load, no SPA
   router) so a real network round-trip can't be hidden — but it can be
   made to feel like app navigation instead of a flat browser reload:
     - On EVERY page load this overlay (glass-blurred full-screen veil +
       spinner) is visible by default and auto-fades via a CSS keyframe —
       pure CSS, no JS dependency, so it still fires even if office.js is
       slow to attach. This is the "крутится-вертится" moment Vlad asked
       for between click and rendered content.
     - office.js (initPageTransitions) intercepts same-origin link clicks
       and form submits, re-shows this overlay + fades/slides the current
       .page-content (or the auth-page .box) out, THEN lets navigation
       proceed after a short delay — the "exit" half of the transition.
       See office.js for the ~190ms delay, matched to the brief below.
   Kept snappy on purpose: ~190ms exit delay + ~360ms entrance veil, well
   under the point where navigation starts to feel sluggish. */
.page-transition{position:fixed;inset:0;z-index:60;display:flex;
align-items:center;justify-content:center;pointer-events:none;
background:rgba(7,11,16,.55);-webkit-backdrop-filter:blur(14px) saturate(140%);
backdrop-filter:blur(14px) saturate(140%);
animation:pt-enter-hide .36s var(--ease) forwards}
.page-transition.exiting{animation:none;opacity:1;visibility:visible;
pointer-events:auto}
@keyframes pt-enter-hide{
  0%{opacity:1;visibility:visible}
  40%{opacity:1;visibility:visible}
  100%{opacity:0;visibility:hidden}
}
.pt-spinner{width:40px;height:40px;border-radius:50%;
border:3px solid var(--glass-border);border-top-color:var(--accent-b);
border-right-color:var(--accent-a);box-shadow:var(--accent-glow);
animation:pt-spin .55s linear infinite}
@keyframes pt-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .page-transition{animation:none;opacity:0;visibility:hidden}
  .page-transition.exiting{opacity:1;visibility:visible}
  .pt-spinner{animation-duration:1.1s}
}

/* Entrance fade/slide for the main content block, released back to plain
   transitions after the keyframe finishes (office.js clears
   element.style.animation on "animationend") so the later .leaving class
   below can drive its own transition without an active "forwards" fill
   blocking it. */
.page-content{animation:page-enter .42s var(--ease) both;
transition:opacity .18s var(--ease),transform .18s var(--ease)}
.page-content.leaving{opacity:0;transform:translateY(-10px) scale(.985)}
body>.box{animation:page-enter .42s var(--ease) both;
transition:opacity .18s var(--ease),transform .18s var(--ease)}
body>.box.leaving{opacity:0;transform:translateY(-10px) scale(.985)}
@keyframes page-enter{
  from{opacity:0;transform:translateY(14px) scale(.98)}
  to{opacity:1;transform:translateY(0) scale(1)}
}
@media (prefers-reduced-motion:reduce){
  .page-content,body>.box{animation:none}
  .page-content.leaving,body>.box.leaving{transition:opacity .12s linear;transform:none}
}

/* --- 2026-10-28 contrast fix: clip-card reorder/delete controls -------
   upload.css's #clip-list .ctrl button{background:#333} was unreadable
   against the near-black glass card background (Vlad flagged "серого
   цвета, их не видно" from a screenshot). office.css loads after
   upload.css so these rules win on equal specificity. */
#clip-list .card{background:var(--glass-bg);border:1px solid var(--glass-border-soft);
border-radius:var(--radius-sm);backdrop-filter:var(--glass-blur-sm);
-webkit-backdrop-filter:var(--glass-blur-sm)}
#clip-list .ctrl button{background:var(--glass-bg-soft);color:var(--text-primary);
border:1px solid var(--glass-border);border-radius:var(--radius-pill);
padding:4px 10px;font-size:14px;transition:background .15s var(--ease),transform .12s var(--ease)}
#clip-list .ctrl button:hover{background:var(--glass-bg-strong);border-color:var(--accent-b)}
#clip-list .ctrl button:active{transform:scale(.9)}
#clip-list .ctrl button[data-act="del"]{color:#ff8080;border-color:rgba(255,128,128,.35)}
#clip-list .ctrl button[data-act="del"]:hover{background:rgba(255,80,80,.14);border-color:#ff8080}
