v3.1.0: 모바일 대응 + 미디 마스터채널 + Content-Disposition 수정
- music-app: 마스터채널 추가 (볼륨 0–200%, FX 항상 표시), 효과 토글 패널 제거 - routes-music.ts: 한글 파일명 Content-Disposition 500 에러 수정 (Buffer.ascii → JS replace) - 모바일 오버레이 패턴 (max-width:640px): mind/accountant/investor/lawyer/language/pptx-wizard/dental-agent 각 앱 사이드바·채팅 패널을 fixed 오버레이로, 바텀 탭바로 네비게이션 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -119,6 +148,23 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
<button class="ac-hdr-btn" onclick="toggleTheme()">🌙 테마</button>
|
||||
</header>
|
||||
|
||||
<div id="mob-backdrop" onclick="mobCloseAll()"></div>
|
||||
|
||||
<div id="mob-tabbar" style="display:none">
|
||||
<button class="mob-tab" id="mobtab-files" onclick="mobToggle('files')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
<span>파일</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-home" onclick="mobToggle('home')" style="color:var(--brand,#22c55e);border-top:2px solid var(--brand,#22c55e)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M3 12L12 3l9 9"/><path d="M9 21V12h6v9"/></svg>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-chat" onclick="mobToggle('chat')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span>AI</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="ac-layout">
|
||||
|
||||
<!-- ── Sidebar ── -->
|
||||
@@ -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');
|
||||
|
||||
@@ -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; }
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="mob-backdrop" onclick="mobCloseAll()"></div>
|
||||
<div id="mob-tabbar" style="display:none;position:fixed;bottom:0;left:0;right:0;height:52px;background:var(--panel);border-top:1px solid var(--border);z-index:490;align-items:stretch;">
|
||||
<button class="mob-tab" id="mobtab-ctrl" onclick="mobToggle('left')" style="flex:1;border:none;border-radius:0;background:none;color:var(--muted);font-size:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:0;cursor:pointer;">
|
||||
<span style="font-size:18px">⚙</span>컨트롤
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-log" onclick="mobCloseAll()" style="flex:1;border:none;border-radius:0;background:none;color:var(--accent);font-size:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:0;cursor:pointer;">
|
||||
<span style="font-size:18px">📋</span>로그
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-panel" onclick="mobToggle('right')" style="flex:1;border:none;border-radius:0;background:none;color:var(--muted);font-size:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:0;cursor:pointer;">
|
||||
<span style="font-size:18px">🖼</span>패널
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<h1>🦷 치과 사전 이미지 에이전트</h1>
|
||||
@@ -676,6 +732,10 @@ button.danger:hover { background: #fecaca; }
|
||||
<!-- Left: stats + controls -->
|
||||
<div class="left-panel">
|
||||
|
||||
<div class="panel-section" id="left-mob-close" style="display:none;padding:8px 12px;">
|
||||
<button onclick="mobCloseAll()" style="width:100%;background:none;border:1px solid var(--border);color:var(--muted);font-size:12px;">✕ 닫기</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-section">
|
||||
<h2>전체 현황</h2>
|
||||
<div class="stat-row">
|
||||
@@ -867,6 +927,10 @@ button.danger:hover { background: #fecaca; }
|
||||
<!-- Right: tabbed panel -->
|
||||
<div class="right-panel">
|
||||
|
||||
<div style="display:none;padding:6px 10px;border-bottom:1px solid var(--border);background:var(--panel);" id="right-mob-close">
|
||||
<button onclick="mobCloseAll()" style="width:100%;background:none;border:1px solid var(--border);color:var(--muted);font-size:12px;">✕ 닫기</button>
|
||||
</div>
|
||||
|
||||
<div class="r-tabs">
|
||||
<div class="r-tab active" onclick="setRTab('gallery')">🖼 갤러리</div>
|
||||
<div class="r-tab" onclick="setRTab('search')">🔍 검색</div>
|
||||
@@ -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()]);
|
||||
|
||||
@@ -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;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -143,6 +171,23 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
<button class="hdr-btn" onclick="toggleTheme()">🌙 테마</button>
|
||||
</header>
|
||||
|
||||
<div id="mob-backdrop" onclick="mobCloseAll()"></div>
|
||||
|
||||
<div id="mob-tabbar" style="display:none">
|
||||
<button class="mob-tab" id="mobtab-stocks" onclick="mobToggle('stocks')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></svg>
|
||||
<span>종목</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-home" onclick="mobToggle('home')" style="color:var(--brand,#22c55e);border-top:2px solid var(--brand,#22c55e)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><rect x="2" y="3" width="20" height="14" rx="2"/><polyline points="8 21 12 17 16 21"/></svg>
|
||||
<span>차트</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-chat" onclick="mobToggle('chat')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span>AI</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="iv-layout">
|
||||
|
||||
<!-- ── Sidebar ── -->
|
||||
@@ -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');
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -187,6 +241,27 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s
|
||||
<a href="/" style="font-size:12px;color:var(--muted);text-decoration:none;margin-left:4px">← 메인</a>
|
||||
</div>
|
||||
|
||||
<div id="mob-backdrop" onclick="mobCloseFilter()"></div>
|
||||
|
||||
<div id="mob-tabbar" style="display:none">
|
||||
<button class="mob-tab" id="mobtab-flash" onclick="mobNav('flash')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><rect x="2" y="4" width="20" height="16" rx="2"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="16" y1="2" x2="16" y2="6"/></svg>
|
||||
<span>카드</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-vocab" onclick="mobNav('vocab')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
||||
<span>어휘</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-quiz" onclick="mobNav('quiz')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
||||
<span>퀴즈</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-lessons" onclick="mobNav('lessons')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
|
||||
<span>레슨</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="lg-layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
@@ -245,6 +320,7 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s
|
||||
<!-- ═══ FLASHCARD PANEL ═══ -->
|
||||
<div class="lg-panel active" id="panel-flash">
|
||||
<div class="lg-toolbar">
|
||||
<button class="lg-btn mob-filter-btn" style="display:none;padding:4px 8px" onclick="mobOpenFilter()" title="카테고리">☰</button>
|
||||
<span class="lg-toolbar-title" id="flash-title">플래시카드 — 전체</span>
|
||||
<span style="font-size:12px;color:var(--muted)" id="flash-counter">0 / 0</span>
|
||||
<label style="display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);cursor:pointer">
|
||||
@@ -281,6 +357,7 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s
|
||||
<!-- ═══ VOCAB PANEL ═══ -->
|
||||
<div class="lg-panel" id="panel-vocab">
|
||||
<div class="search-row">
|
||||
<button class="lg-btn mob-filter-btn" style="display:none;padding:4px 8px;flex-shrink:0" onclick="mobOpenFilter()" title="필터">☰</button>
|
||||
<input type="text" id="vocab-search" placeholder="단어 검색..." oninput="renderVocabTable()">
|
||||
<label style="display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);cursor:pointer;white-space:nowrap">
|
||||
<input type="checkbox" id="vocab-show-rom" checked onchange="renderVocabTable()" style="accent-color:#22c55e">발음
|
||||
@@ -305,6 +382,7 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s
|
||||
<!-- ═══ QUIZ PANEL ═══ -->
|
||||
<div class="lg-panel" id="panel-quiz">
|
||||
<div class="lg-toolbar">
|
||||
<button class="lg-btn mob-filter-btn" style="display:none;padding:4px 8px" onclick="mobOpenFilter()" title="퀴즈 설정">☰</button>
|
||||
<span class="lg-toolbar-title">퀴즈</span>
|
||||
<select class="lg-select" id="quiz-type-sel">
|
||||
<option value="multiple">객관식</option>
|
||||
@@ -365,6 +443,7 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s
|
||||
<!-- Lesson grid view -->
|
||||
<div id="lessons-grid-view" style="display:flex;flex-direction:column;flex:1;overflow:hidden">
|
||||
<div class="lg-toolbar">
|
||||
<button class="lg-btn mob-filter-btn" style="display:none;padding:4px 8px" onclick="mobOpenFilter()" title="레슨 목록">☰</button>
|
||||
<span class="lg-toolbar-title">레슨</span>
|
||||
<button class="lg-btn" onclick="resetLessons()">↺ 진도 초기화</button>
|
||||
</div>
|
||||
@@ -392,6 +471,30 @@ body { background: var(--bg); color: var(--text); font-family: 'Nanum Gothic', s
|
||||
</div><!-- /lg-layout -->
|
||||
|
||||
<script>
|
||||
// ── Mobile ────────────────────────────────────────────────────────────────────
|
||||
function mobCloseFilter(){
|
||||
document.querySelector('.lg-sidebar').classList.remove('mob-open');
|
||||
document.getElementById('mob-backdrop').classList.remove('show');
|
||||
}
|
||||
function mobOpenFilter(){
|
||||
document.querySelector('.lg-sidebar').classList.add('mob-open');
|
||||
document.getElementById('mob-backdrop').classList.add('show');
|
||||
}
|
||||
function mobNav(nav){
|
||||
mobCloseFilter();
|
||||
setNav(nav);
|
||||
// update bottom tab active state
|
||||
['flash','vocab','quiz','lessons'].forEach(n=>{
|
||||
const t=document.getElementById('mobtab-'+n);
|
||||
if(t) t.classList.toggle('active', n===nav);
|
||||
});
|
||||
}
|
||||
// hook category/filter buttons to auto-close sidebar on mobile
|
||||
['setCategory','setVocabCat','setQuizCat'].forEach(fn=>{
|
||||
const orig=window[fn];
|
||||
if(orig) window[fn]=function(...a){orig(...a);if(window.innerWidth<=640)mobCloseFilter();};
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// DATA
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
+72
-1
@@ -131,6 +131,35 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
.lw-send{background:#92400e;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;}
|
||||
.lw-send:hover:not(:disabled){background:#b45309;}
|
||||
.lw-send:disabled{opacity:.5;cursor:not-allowed;}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media(max-width:640px){
|
||||
.lw-resize{display:none;}
|
||||
.lw-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);
|
||||
}
|
||||
.lw-sb.mob-open{transform:translateX(0);}
|
||||
.lw-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);
|
||||
}
|
||||
.lw-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;}
|
||||
.lw-center{width:100%;}
|
||||
.lw-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;}
|
||||
.lw-textarea,input,textarea{font-size:16px !important;}
|
||||
}
|
||||
@media(min-width:641px){
|
||||
#mob-tabbar{display:none !important;}
|
||||
#mob-backdrop{display:none !important;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -143,6 +172,23 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
<button class="hdr-btn" onclick="toggleTheme()">🌙 테마</button>
|
||||
</header>
|
||||
|
||||
<div id="mob-backdrop" onclick="mobCloseAll()"></div>
|
||||
|
||||
<div id="mob-tabbar" style="display:none">
|
||||
<button class="mob-tab" id="mobtab-quick" onclick="mobToggle('quick')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||
<span>빠른질문</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-home" onclick="mobToggle('home')" style="color:var(--brand,#eab308);border-top:2px solid var(--brand,#eab308)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
<span>검색</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-chat" onclick="mobToggle('chat')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span>AI</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="lw-layout">
|
||||
|
||||
<!-- ── Sidebar ── -->
|
||||
@@ -249,6 +295,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('.lw-sb').classList.remove('mob-open');
|
||||
document.getElementById('chat-panel').classList.remove('mob-open');
|
||||
document.getElementById('mob-backdrop').classList.remove('show');
|
||||
['quick','chat'].forEach(t=>document.getElementById('mobtab-'+t)?.classList.remove('active'));
|
||||
document.getElementById('mobtab-home').style.cssText='color:var(--brand,#eab308);border-top:2px solid var(--brand,#eab308)';
|
||||
}
|
||||
function mobToggle(which){
|
||||
const sb=document.querySelector('.lw-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==='quick'&&!sbOpen){sb.classList.add('mob-open');bd.classList.add('show');document.getElementById('mobtab-quick').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 CHAT_W='lw_chat_width';
|
||||
@@ -350,6 +416,7 @@ function renderResults(data,query){
|
||||
}
|
||||
|
||||
async function selectResult(idx){
|
||||
if(window.innerWidth<=640) mobCloseAll();
|
||||
document.querySelectorAll('.lw-result-item').forEach(el=>el.classList.remove('active'));
|
||||
const el=document.getElementById('ri-'+idx);
|
||||
if(el)el.classList.add('active');
|
||||
@@ -446,11 +513,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');
|
||||
|
||||
@@ -155,6 +155,54 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
.mn-resize{width:4px;flex-shrink:0;background:var(--line);cursor:col-resize;transition:.15s;}
|
||||
.mn-resize:hover,.mn-resize.dragging{background:#8b5cf6;}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media(max-width:640px){
|
||||
.mn-hdr{flex-wrap:wrap;padding:5px 8px;gap:5px;}
|
||||
.mn-hdr h1{font-size:12px;}
|
||||
.mode-toggle{order:10;flex:0 0 100%;}
|
||||
.mode-tab{flex:1;font-size:10px;padding:4px 0;}
|
||||
.mn-hdr-sp{display:none;}
|
||||
/* hide desktop resize */
|
||||
.mn-resize{display:none;}
|
||||
/* sidebar & chat become fixed overlays */
|
||||
.mn-sb{
|
||||
position:fixed;top:0;left:0;bottom:0;z-index:500;
|
||||
width:80vw;max-width:300px;
|
||||
transform:translateX(-100%);transition:transform .25s ease;
|
||||
border-right:1px solid var(--line);
|
||||
box-shadow:4px 0 24px rgba(0,0,0,.4);
|
||||
}
|
||||
.mn-sb.mob-open{transform:translateX(0);}
|
||||
.mn-chat{
|
||||
position:fixed;top:0;right:0;bottom:0;z-index:500;
|
||||
width:92vw;max-width:380px;
|
||||
transform:translateX(100%);transition:transform .25s ease;
|
||||
border-left:1px solid var(--line);
|
||||
box-shadow:-4px 0 24px rgba(0,0,0,.4);
|
||||
}
|
||||
.mn-chat.mob-open{transform:translateX(0);}
|
||||
/* overlay backdrop */
|
||||
#mob-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:499;}
|
||||
#mob-backdrop.show{display:block;}
|
||||
/* center takes full width */
|
||||
.mn-center{width:100%;}
|
||||
/* bottom tab bar */
|
||||
#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 bottom padding so content isn't hidden behind tab bar */
|
||||
.mn-center-body{padding-bottom:64px;}
|
||||
/* font-size ≥16px to prevent iOS auto-zoom */
|
||||
.mn-textarea,input,textarea{font-size:16px !important;}
|
||||
}
|
||||
@media(min-width:641px){
|
||||
#mob-tabbar{display:none !important;}
|
||||
#mob-backdrop{display:none !important;}
|
||||
}
|
||||
|
||||
/* ── Chat ── */
|
||||
.mn-chat{width:340px;flex-shrink:0;display:flex;flex-direction:column;border-left:1px solid var(--line);background:var(--panel);min-height:0;}
|
||||
.mn-chat-hdr{display:flex;align-items:center;padding:6px 10px;border-bottom:1px solid var(--line);flex-shrink:0;gap:6px;}
|
||||
@@ -210,6 +258,23 @@ body{background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;dis
|
||||
<button class="hdr-btn" onclick="toggleTheme()">🌙 테마</button>
|
||||
</header>
|
||||
|
||||
<div id="mob-backdrop" onclick="mobCloseAll()"></div>
|
||||
|
||||
<div id="mob-tabbar" style="display:none">
|
||||
<button class="mob-tab" id="mobtab-tools" onclick="mobToggle('tools')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
||||
<span>도구</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-home" onclick="mobToggle('home')" style="color:var(--brand,#8b5cf6);border-top:2px solid var(--brand,#8b5cf6)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M3 12L12 3l9 9"/><path d="M9 21V12h6v9"/></svg>
|
||||
<span>홈</span>
|
||||
</button>
|
||||
<button class="mob-tab" id="mobtab-chat" onclick="mobToggle('chat')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" style="width:20px;height:20px;stroke-width:2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<span>AI 상담</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mn-layout">
|
||||
|
||||
<!-- ── Sidebar ── -->
|
||||
@@ -314,6 +379,26 @@ async function checkAuth(){
|
||||
catch{location.href='/login.html?redirect='+encodeURIComponent(location.pathname);return false;}
|
||||
}
|
||||
|
||||
// ── Mobile overlay ────────────────────────────────────────────────────────────
|
||||
function mobCloseAll(){
|
||||
document.querySelector('.mn-sb').classList.remove('mob-open');
|
||||
document.getElementById('chat-panel').classList.remove('mob-open');
|
||||
document.getElementById('mob-backdrop').classList.remove('show');
|
||||
['tools','chat'].forEach(t=>document.getElementById('mobtab-'+t)?.classList.remove('active'));
|
||||
document.getElementById('mobtab-home').style.cssText='color:var(--brand,#8b5cf6);border-top:2px solid var(--brand,#8b5cf6)';
|
||||
}
|
||||
function mobToggle(which){
|
||||
const sb=document.querySelector('.mn-sb');
|
||||
const cp=document.getElementById('chat-panel');
|
||||
const bd=document.getElementById('mob-backdrop');
|
||||
if(which==='home'){mobCloseAll();return;}
|
||||
const isSbOpen=sb.classList.contains('mob-open');
|
||||
const isCpOpen=cp.classList.contains('mob-open');
|
||||
mobCloseAll();
|
||||
if(which==='tools'&&!isSbOpen){sb.classList.add('mob-open');bd.classList.add('show');document.getElementById('mobtab-tools').classList.add('active');document.getElementById('mobtab-home').style.cssText='';}
|
||||
if(which==='chat'&&!isCpOpen){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{}}
|
||||
|
||||
@@ -408,6 +493,8 @@ const toolTitles={
|
||||
};
|
||||
|
||||
function showTool(name){
|
||||
// on mobile: close sidebar overlay after selecting a tool
|
||||
if(window.innerWidth<=640) mobCloseAll();
|
||||
// stop breath timer if switching
|
||||
if(breathTimer&&name!=='breath'){clearInterval(breathTimer);breathTimer=null;}
|
||||
activeTool=name;
|
||||
@@ -931,6 +1018,8 @@ async function sendMessage(){
|
||||
const msg=input.value.trim();
|
||||
if(!msg||document.getElementById('send-btn').disabled)return;
|
||||
input.value='';autoResize(input);
|
||||
// on mobile: open chat panel so user can see the response
|
||||
if(window.innerWidth<=640) mobToggle('chat');
|
||||
addMsg('user',escHtml(msg));
|
||||
await callChat(msg);
|
||||
}
|
||||
|
||||
+878
-260
File diff suppressed because it is too large
Load Diff
+85
-2
@@ -258,6 +258,66 @@ body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,san
|
||||
.sed-img-thumb{width:100%;height:100px;border-radius:6px;background:var(--panel) center/cover no-repeat;border:1px solid var(--line);display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--muted);cursor:pointer;transition:.15s;}
|
||||
.sed-img-thumb:hover{border-color:var(--brand);}
|
||||
.sed-nav-info{font-size:12px;color:var(--muted);text-align:center;flex:1;}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media(max-width:640px){
|
||||
/* Header: compact */
|
||||
.header{flex-wrap:wrap;padding:7px 12px;gap:6px;}
|
||||
.header h1{font-size:13px;}
|
||||
/* hide theme buttons — they're inside sidebar on mobile */
|
||||
.header > div:has(.theme-btn){display:none;}
|
||||
/* step dots: hide text labels, keep numbers */
|
||||
.sdot{padding:5px 8px;font-size:0;}
|
||||
.sdot .n{font-size:10px;}
|
||||
.step-hint{display:none;}
|
||||
.steps{gap:0;margin-left:auto;}
|
||||
|
||||
/* Sidebar: overlay drawer */
|
||||
.sidebar{
|
||||
position:fixed;top:0;left:0;bottom:0;z-index:500;
|
||||
width:82vw;max-width:310px;
|
||||
transform:translateX(-100%);transition:transform .25s ease;
|
||||
box-shadow:4px 0 28px rgba(0,0,0,.5);
|
||||
order:0;
|
||||
}
|
||||
.sidebar.mob-open{transform:translateX(0);}
|
||||
/* theme buttons inside sidebar on mobile */
|
||||
.sidebar-mob-theme{display:flex !important;}
|
||||
|
||||
#mob-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:499;}
|
||||
#mob-backdrop.show{display:block;}
|
||||
|
||||
/* settings toggle button */
|
||||
.mob-cfg-btn{display:inline-flex !important;}
|
||||
|
||||
/* Step scroll area: bottom padding for bbar */
|
||||
.sv .scroll{padding-bottom:16px;}
|
||||
|
||||
/* Search row: wrap */
|
||||
.srow{flex-wrap:wrap;}
|
||||
.srow input{min-width:0;}
|
||||
|
||||
/* Source checkboxes: narrower */
|
||||
.src-chk{width:calc(50% - 4px);min-width:0;}
|
||||
|
||||
/* Step 2: stack layout */
|
||||
#step-2 .scroll > div[style*="flex:2"],
|
||||
#step-2 .scroll > div[style*="flex:1"]{flex:1 1 100%;}
|
||||
#step-2 .scroll{flex-wrap:wrap;}
|
||||
|
||||
/* Slide editor modal: full screen */
|
||||
.sedmodal{width:100vw !important;height:100dvh !important;max-width:100vw;max-height:100dvh;border-radius:0;left:0;top:0;transform:none;}
|
||||
.sed-body{flex-direction:column;}
|
||||
.sed-ctrl{width:100%;max-height:40vh;border-right:none;border-bottom:1px solid var(--line);}
|
||||
|
||||
/* iOS zoom prevention */
|
||||
input,textarea,select{font-size:16px !important;}
|
||||
}
|
||||
@media(min-width:641px){
|
||||
#mob-backdrop{display:none !important;}
|
||||
.mob-cfg-btn{display:none !important;}
|
||||
.sidebar-mob-theme{display:none !important;}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -265,6 +325,7 @@ body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,san
|
||||
<div class="header">
|
||||
<span style="font-size:18px">📊</span>
|
||||
<h1>슬라이드 제작 마법사</h1>
|
||||
<button class="mob-cfg-btn theme-btn" style="display:none;padding:5px 10px" onclick="mobToggleSidebar()">⚙ 설정</button>
|
||||
<div style="display:flex;gap:4px;margin-left:14px">
|
||||
<button class="theme-btn active" id="tbtn-dark" onclick="setTheme('dark')">🌙 다크</button>
|
||||
<button class="theme-btn" id="tbtn-light" onclick="setTheme('light')">☀️ 라이트</button>
|
||||
@@ -279,6 +340,8 @@ body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,san
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mob-backdrop" onclick="mobCloseSidebar()"></div>
|
||||
|
||||
<div class="body">
|
||||
|
||||
<!-- ── STEP 1 ── -->
|
||||
@@ -429,7 +492,15 @@ body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,san
|
||||
|
||||
<!-- ── SETTINGS SIDEBAR ── -->
|
||||
<div class="sidebar">
|
||||
<div class="ptitle">설정</div>
|
||||
<div class="ptitle" style="display:flex;align-items:center;justify-content:space-between">
|
||||
<span>설정</span>
|
||||
<div class="sidebar-mob-theme" style="display:none;gap:4px;align-items:center">
|
||||
<button class="theme-btn" id="tbtn-dark2" onclick="setTheme('dark')" style="padding:2px 8px;font-size:11px">🌙</button>
|
||||
<button class="theme-btn" id="tbtn-light2" onclick="setTheme('light')" style="padding:2px 8px;font-size:11px">☀️</button>
|
||||
<button class="theme-btn" id="tbtn-warm2" onclick="setTheme('warm')" style="padding:2px 8px;font-size:11px">🌿</button>
|
||||
<button onclick="mobCloseSidebar()" style="background:none;border:none;color:var(--muted);cursor:pointer;font-size:16px;padding:0 4px;line-height:1">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="field">
|
||||
<button class="btn bgreen" style="width:100%;font-size:12px;padding:7px 0" onclick="newProject()">+ 새 프로젝트</button>
|
||||
@@ -744,11 +815,23 @@ body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,san
|
||||
'use strict';
|
||||
|
||||
// ── Theme ────────────────────────────────────────────────────────────────────
|
||||
// ── Mobile sidebar ────────────────────────────────────────────────────────────
|
||||
function mobCloseSidebar(){
|
||||
document.querySelector('.sidebar').classList.remove('mob-open');
|
||||
document.getElementById('mob-backdrop').classList.remove('show');
|
||||
}
|
||||
function mobToggleSidebar(){
|
||||
const sb=document.querySelector('.sidebar');
|
||||
const open=sb.classList.contains('mob-open');
|
||||
if(open){mobCloseSidebar();}
|
||||
else{sb.classList.add('mob-open');document.getElementById('mob-backdrop').classList.add('show');}
|
||||
}
|
||||
|
||||
function setTheme(name) {
|
||||
document.body.classList.remove('theme-light','theme-warm');
|
||||
if(name==='light') document.body.classList.add('theme-light');
|
||||
if(name==='warm') document.body.classList.add('theme-warm');
|
||||
document.querySelectorAll('.theme-btn').forEach(b=>b.classList.toggle('active',b.id==='tbtn-'+name));
|
||||
document.querySelectorAll('.theme-btn').forEach(b=>b.classList.toggle('active',b.id==='tbtn-'+name||b.id==='tbtn-'+name+'2'));
|
||||
localStorage.setItem('pptx-wizard-theme',name);
|
||||
}
|
||||
(function(){ setTheme(localStorage.getItem('pptx-wizard-theme')||'dark'); })();
|
||||
|
||||
Reference in New Issue
Block a user