diff --git a/src/gateway/routes-music.ts b/src/gateway/routes-music.ts index 1ba4e34..961c49b 100644 --- a/src/gateway/routes-music.ts +++ b/src/gateway/routes-music.ts @@ -248,7 +248,7 @@ app.get('/api/music/workspace-file/:dir/:file', (req, res) => { res.status(403).json({ error: 'Forbidden' }); return; } if (!fs.existsSync(filePath)) { res.status(404).json({ error: 'Not found' }); return; } - const safeName = Buffer.from(req.params.file).toString('ascii').replace(/[^\x20-\x7E]/g, '_'); + const safeName = req.params.file.replace(/[^\x20-\x7E]/g, '_').replace(/["\\]/g, '_'); res.setHeader('Content-Type', 'audio/midi'); res.setHeader('Content-Disposition', `attachment; filename="${safeName}"; filename*=UTF-8''${encodeURIComponent(req.params.file)}`); const stream = fs.createReadStream(filePath); diff --git a/web-ui/accountant-app.html b/web-ui/accountant-app.html index 1ccfbbe..872e3f8 100644 --- a/web-ui/accountant-app.html +++ b/web-ui/accountant-app.html @@ -106,6 +106,35 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis /* ── Scrollbars ── */ .ac-files::-webkit-scrollbar,.ac-msgs::-webkit-scrollbar{width:3px;} .ac-files::-webkit-scrollbar-thumb,.ac-msgs::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px;} + +/* ── Mobile ── */ +@media(max-width:640px){ + .ac-resize{display:none;} + .ac-sb{ + position:fixed;top:0;left:0;bottom:0;z-index:500; + width:80vw;max-width:300px; + transform:translateX(-100%);transition:transform .25s ease; + box-shadow:4px 0 24px rgba(0,0,0,.4); + } + .ac-sb.mob-open{transform:translateX(0);} + .ac-chat{ + position:fixed;top:0;right:0;bottom:0;z-index:500; + width:92vw;max-width:380px; + transform:translateX(100%);transition:transform .25s ease; + box-shadow:-4px 0 24px rgba(0,0,0,.4); + } + .ac-chat.mob-open{transform:translateX(0);} + #mob-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:499;} + #mob-backdrop.show{display:block;} + .ac-center{width:100%;} + .ac-center-body{padding-bottom:64px;} + #mob-tabbar{display:flex !important;position:fixed;bottom:0;left:0;right:0;z-index:400;background:var(--panel);border-top:1px solid var(--line);height:52px;} + .ac-textarea,input,textarea{font-size:16px !important;} +} +@media(min-width:641px){ + #mob-tabbar{display:none !important;} + #mob-backdrop{display:none !important;} +} @@ -119,6 +148,23 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis +
+ + +
@@ -194,6 +240,26 @@ async function checkAuth(){ catch{location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;} } +// ── Mobile overlay ──────────────────────────────────────────────────────────── +function mobCloseAll(){ + document.querySelector('.ac-sb').classList.remove('mob-open'); + document.getElementById('chat-panel').classList.remove('mob-open'); + document.getElementById('mob-backdrop').classList.remove('show'); + ['files','chat'].forEach(t=>document.getElementById('mobtab-'+t)?.classList.remove('active')); + document.getElementById('mobtab-home').style.cssText='color:var(--brand,#22c55e);border-top:2px solid var(--brand,#22c55e)'; +} +function mobToggle(which){ + const sb=document.querySelector('.ac-sb'); + const cp=document.getElementById('chat-panel'); + const bd=document.getElementById('mob-backdrop'); + if(which==='home'){mobCloseAll();return;} + const sbOpen=sb.classList.contains('mob-open'); + const cpOpen=cp.classList.contains('mob-open'); + mobCloseAll(); + if(which==='files'&&!sbOpen){sb.classList.add('mob-open');bd.classList.add('show');document.getElementById('mobtab-files').classList.add('active');document.getElementById('mobtab-home').style.cssText='';} + if(which==='chat'&&!cpOpen){cp.classList.add('mob-open');bd.classList.add('show');document.getElementById('mobtab-chat').classList.add('active');document.getElementById('mobtab-home').style.cssText='';} +} + // ── Theme ───────────────────────────────────────────────────────────────────── function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}} @@ -369,11 +435,16 @@ async function sendMessage(){ const msg=input.value.trim(); if(!msg||document.getElementById('send-btn').disabled)return; input.value='';autoResize(input); + if(window.innerWidth<=640) mobToggle('chat'); addMsg('user',escHtml(msg)); await callChat(msg); } -function sendQuick(msg){document.getElementById('chat-input').value=msg;sendMessage();} +function sendQuick(msg){ + if(window.innerWidth<=640) mobCloseAll(); + document.getElementById('chat-input').value=msg; + sendMessage(); +} async function callChat(message){ const btn=document.getElementById('send-btn'); diff --git a/web-ui/dental-agent.html b/web-ui/dental-agent.html index 3099a4b..f82f1b9 100644 --- a/web-ui/dental-agent.html +++ b/web-ui/dental-agent.html @@ -631,6 +631,49 @@ button.danger:hover { background: #fecaca; } .theme-dot:hover { transform: scale(1.2); } .theme-dot.active { border-color: var(--text); } +/* ── Mobile ── */ +@media(max-width:640px){ + .header{flex-wrap:wrap;padding:6px 10px;gap:6px;} + .header h1{font-size:13px;} + .theme-picker{display:none;} + .header-actions{gap:5px;} + .header-actions button{padding:4px 8px;font-size:11px;} + + /* Side panels → overlays */ + .left-panel{ + position:fixed;top:0;left:0;bottom:0;z-index:500; + width:82vw;max-width:300px; + transform:translateX(-100%);transition:transform .25s ease; + box-shadow:4px 0 28px rgba(0,0,0,.5); + resize:none; + } + .left-panel.mob-open{transform:translateX(0);} + .right-panel{ + position:fixed;top:0;right:0;bottom:0;z-index:500; + width:92vw;max-width:380px; + transform:translateX(100%);transition:transform .25s ease; + box-shadow:-4px 0 28px rgba(0,0,0,.5); + } + .right-panel.mob-open{transform:translateX(0);} + + #mob-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:499;} + #mob-backdrop.show{display:block;} + + /* Bottom tabbar */ + #mob-tabbar{display:flex !important;} + + /* Center: leave bottom room for tabbar */ + .main{padding-bottom:52px;} + .center-panel{border-right:none;} + + /* iOS zoom */ + input,textarea,select{font-size:16px !important;} +} +@media(min-width:641px){ + #mob-backdrop{display:none !important;} + #mob-tabbar{display:none !important;} +} + /* Image lightbox */ #lightbox { display: none; @@ -651,6 +694,19 @@ button.danger:hover { background: #fecaca; } +
+ +

🦷 치과 사전 이미지 에이전트

@@ -676,6 +732,10 @@ button.danger:hover { background: #fecaca; }
+ +

전체 현황

@@ -867,6 +927,10 @@ button.danger:hover { background: #fecaca; }
+ +
🖼 갤러리
🔍 검색
@@ -1560,6 +1624,49 @@ function appendChatMsg(text, role) { return el; } +// ── Mobile ──────────────────────────────────────────── +function mobCloseAll(){ + document.querySelector('.left-panel').classList.remove('mob-open'); + document.querySelector('.right-panel').classList.remove('mob-open'); + document.getElementById('mob-backdrop').classList.remove('show'); + document.querySelectorAll('#mob-tabbar .mob-tab').forEach(t=>t.style.color='var(--muted)'); + const logTab=document.getElementById('mobtab-log'); + if(logTab)logTab.style.color='var(--accent)'; + // hide close buttons + const lc=document.getElementById('left-mob-close'); + const rc=document.getElementById('right-mob-close'); + if(lc)lc.style.display='none'; + if(rc)rc.style.display='none'; +} +function mobToggle(side){ + const isMob=window.innerWidth<=640; + if(!isMob)return; + const leftOpen=document.querySelector('.left-panel').classList.contains('mob-open'); + const rightOpen=document.querySelector('.right-panel').classList.contains('mob-open'); + if(side==='left'&&leftOpen){mobCloseAll();return;} + if(side==='right'&&rightOpen){mobCloseAll();return;} + mobCloseAll(); + if(side==='left'){ + document.querySelector('.left-panel').classList.add('mob-open'); + document.getElementById('mob-backdrop').classList.add('show'); + const lc=document.getElementById('left-mob-close'); + if(lc)lc.style.display='block'; + const t=document.getElementById('mobtab-ctrl'); + if(t)t.style.color='var(--accent)'; + const lt=document.getElementById('mobtab-log'); + if(lt)lt.style.color='var(--muted)'; + } else { + document.querySelector('.right-panel').classList.add('mob-open'); + document.getElementById('mob-backdrop').classList.add('show'); + const rc=document.getElementById('right-mob-close'); + if(rc)rc.style.display='block'; + const t=document.getElementById('mobtab-panel'); + if(t)t.style.color='var(--accent)'; + const lt=document.getElementById('mobtab-log'); + if(lt)lt.style.color='var(--muted)'; + } +} + // ── Init ────────────────────────────────────────────── (async () => { await Promise.all([refreshStats(), loadRecent(), loadModelInfo(), loadVerifyModels()]); diff --git a/web-ui/investor-app.html b/web-ui/investor-app.html index fa140a8..142ed3f 100644 --- a/web-ui/investor-app.html +++ b/web-ui/investor-app.html @@ -131,6 +131,34 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis .iv-send{background:#14532d;border:none;border-radius:8px;padding:7px 13px;font-size:12px;color:#fff;cursor:pointer;font-family:inherit;font-weight:700;transition:.15s;flex-shrink:0;} .iv-send:hover:not(:disabled){background:#166534;} .iv-send:disabled{opacity:.5;cursor:not-allowed;} + +/* ── Mobile ── */ +@media(max-width:640px){ + .iv-resize{display:none;} + .iv-sb{ + position:fixed;top:0;left:0;bottom:0;z-index:500; + width:80vw;max-width:300px; + transform:translateX(-100%);transition:transform .25s ease; + box-shadow:4px 0 24px rgba(0,0,0,.4); + } + .iv-sb.mob-open{transform:translateX(0);} + .iv-chat{ + position:fixed;top:0;right:0;bottom:0;z-index:500; + width:92vw;max-width:380px; + transform:translateX(100%);transition:transform .25s ease; + box-shadow:-4px 0 24px rgba(0,0,0,.4); + } + .iv-chat.mob-open{transform:translateX(0);} + #mob-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:499;} + #mob-backdrop.show{display:block;} + .iv-center{width:100%;} + #mob-tabbar{display:flex !important;position:fixed;bottom:0;left:0;right:0;z-index:400;background:var(--panel);border-top:1px solid var(--line);height:52px;} + .iv-textarea,input,textarea{font-size:16px !important;} +} +@media(min-width:641px){ + #mob-tabbar{display:none !important;} + #mob-backdrop{display:none !important;} +} @@ -143,6 +171,23 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis +
+ + +
@@ -420,6 +465,26 @@ async function checkAuth(){ // ── Theme ───────────────────────────────────────────────────────────────────── function toggleTheme(){const d=document.documentElement;const n=d.getAttribute('data-theme')==='dark'?'light':'dark';d.setAttribute('data-theme',n);try{localStorage.setItem('cherryclaw_theme',n);}catch{}} +// ── Mobile overlay ──────────────────────────────────────────────────────────── +function mobCloseAll(){ + document.querySelector('.iv-sb').classList.remove('mob-open'); + document.getElementById('chat-panel').classList.remove('mob-open'); + document.getElementById('mob-backdrop').classList.remove('show'); + ['stocks','chat'].forEach(t=>document.getElementById('mobtab-'+t)?.classList.remove('active')); + document.getElementById('mobtab-home').style.cssText='color:var(--brand,#22c55e);border-top:2px solid var(--brand,#22c55e)'; +} +function mobToggle(which){ + const sb=document.querySelector('.iv-sb'); + const cp=document.getElementById('chat-panel'); + const bd=document.getElementById('mob-backdrop'); + if(which==='home'){mobCloseAll();return;} + const sbOpen=sb.classList.contains('mob-open'); + const cpOpen=cp.classList.contains('mob-open'); + mobCloseAll(); + if(which==='stocks'&&!sbOpen){sb.classList.add('mob-open');bd.classList.add('show');document.getElementById('mobtab-stocks').classList.add('active');document.getElementById('mobtab-home').style.cssText='';} + if(which==='chat'&&!cpOpen){cp.classList.add('mob-open');bd.classList.add('show');document.getElementById('mobtab-chat').classList.add('active');document.getElementById('mobtab-home').style.cssText='';} +} + // ── Resize ──────────────────────────────────────────────────────────────────── (function(){ const handle=document.getElementById('resize-handle'); @@ -479,6 +544,7 @@ function showEmpty(){ // Korean stock via Naver Finance image function loadKStock(code,name,period){ + if(window.innerWidth<=640) mobCloseAll(); period=period||document.getElementById('period-sel').value||'day'; setChartType('kr'); document.getElementById('sym-input').value=code; @@ -504,6 +570,7 @@ function loadKStock(code,name,period){ // TradingView widget function loadTV(symbol,interval,name){ + if(window.innerWidth<=640) mobCloseAll(); interval=interval||'D'; setChartType('tv'); document.getElementById('sym-input').value=symbol; @@ -654,11 +721,15 @@ async function sendMessage(){ const msg=input.value.trim(); if(!msg||document.getElementById('send-btn').disabled)return; input.value='';autoResize(input); + if(window.innerWidth<=640) mobToggle('chat'); addMsg('user',escHtml(msg)); await callChat(msg); } -function sendQuick(msg){addMsg('user',escHtml(msg));callChat(msg);} +function sendQuick(msg){ + if(window.innerWidth<=640){mobCloseAll();} + addMsg('user',escHtml(msg));callChat(msg); +} async function callChat(message){ const btn=document.getElementById('send-btn'); diff --git a/web-ui/language-app.html b/web-ui/language-app.html index fc53870..72c25b5 100644 --- a/web-ui/language-app.html +++ b/web-ui/language-app.html @@ -163,6 +163,60 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; } + +/* ── Mobile ── */ +@media(max-width:640px){ + /* Header: wrap to 2 rows */ + .lg-header { flex-wrap:wrap; padding:6px 10px; gap:5px; } + .lg-header h1 { font-size:13px; } + .lg-header .sub { display:none; } + /* hide theme & voice on mobile (secondary settings) */ + .lg-header > div:has(.lg-theme-btn), + .lg-header > div:has(.tts-gender) { display:none; } + .lg-header > a[href="/"] { display:none; } + /* stat chip stays, moves to right */ + + /* Sidebar: overlay drawer */ + .lg-sidebar { + position:fixed;top:0;left:0;bottom:0;z-index:500; + width:80vw;max-width:280px; + transform:translateX(-100%);transition:transform .25s ease; + box-shadow:4px 0 24px rgba(0,0,0,.45); + } + .lg-sidebar.mob-open { transform:translateX(0); } + /* hide sidebar nav tabs on mobile — bottom tabbar replaces them */ + .csb-tab-bar { display:none; } + /* all sb-* content visible in drawer (setNav handles show/hide) */ + + #mob-backdrop { display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:499; } + #mob-backdrop.show { display:block; } + + /* Main takes full width */ + .lg-main { width:100%; } + + /* bottom padding so content clears tabbar */ + .card-wrap, .vocab-wrap, .quiz-wrap, .lessons-grid, .lesson-steps { padding-bottom:64px; } + + /* Bottom tabbar */ + #mob-tabbar { + display:flex !important; + position:fixed;bottom:0;left:0;right:0;z-index:400; + background:var(--panel);border-top:1px solid var(--line); + height:52px; + } + + /* Add filter button to toolbar (hidden on desktop) */ + .mob-filter-btn { display:flex !important; } + + /* iOS zoom prevention */ + input, textarea, select { font-size:16px !important; } + .quiz-fill { font-size:16px !important; } +} +@media(min-width:641px){ + #mob-tabbar { display:none !important; } + #mob-backdrop { display:none !important; } + .mob-filter-btn { display:none !important; } +} @@ -187,6 +241,27 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s ← 메인
+
+ + +
@@ -245,6 +320,7 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s
+ 플래시카드 — 전체 0 / 0