Spaces:
Running
Running
Upload 13 files
Browse files- index.html +96 -71
- stats.html +175 -92
- style.css +51 -34
index.html
CHANGED
|
@@ -22,14 +22,14 @@
|
|
| 22 |
}
|
| 23 |
|
| 24 |
header {
|
| 25 |
-
margin-top: 115px; /*
|
| 26 |
}
|
| 27 |
|
| 28 |
.history-container {
|
| 29 |
padding-bottom: 20px !important;
|
| 30 |
}
|
| 31 |
|
| 32 |
-
/*
|
| 33 |
.control-bar {
|
| 34 |
max-width: 1400px;
|
| 35 |
margin: 0 auto;
|
|
@@ -46,37 +46,64 @@
|
|
| 46 |
padding: 12px 20px;
|
| 47 |
}
|
| 48 |
|
| 49 |
-
/*
|
| 50 |
.has-preview header {
|
| 51 |
-
margin-top: 260px;
|
| 52 |
}
|
| 53 |
|
| 54 |
@media (max-width: 768px) {
|
| 55 |
header {
|
| 56 |
-
margin-top: 170px;
|
| 57 |
}
|
| 58 |
.has-preview header {
|
| 59 |
-
margin-top: 320px;
|
| 60 |
}
|
| 61 |
}
|
| 62 |
</style>
|
| 63 |
</head>
|
| 64 |
<body>
|
| 65 |
-
<!--
|
| 66 |
-
|
| 67 |
-
//
|
| 68 |
-
const observePreviewBar = () => {
|
| 69 |
-
const previewBar = document.getElementById('preview-bar');
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
</script>
|
| 81 |
|
| 82 |
<!-- 登录界面 -->
|
|
@@ -89,16 +116,16 @@
|
|
| 89 |
</div>
|
| 90 |
</div>
|
| 91 |
|
| 92 |
-
<!--
|
| 93 |
<div class="app-container" id="app" style="filter: blur(10px); pointer-events: none;">
|
| 94 |
-
<!--
|
| 95 |
<div class="input-section glass-panel" id="drop-zone">
|
| 96 |
-
<!--
|
| 97 |
<div class="preview-bar" id="preview-bar"></div>
|
| 98 |
|
| 99 |
-
<!--
|
| 100 |
<div class="control-bar">
|
| 101 |
-
<button class="upload-trigger" id="upload-btn" title="
|
| 102 |
<textarea id="prompt" class="main-input" rows="1" placeholder="描述画面... (支持拖拽图片)"></textarea>
|
| 103 |
<button class="ghost-btn" id="clear-btn" title="清空输入" aria-label="清空输入">清空</button>
|
| 104 |
<button class="btn-3d send-btn" id="send-btn">
|
|
@@ -107,8 +134,8 @@
|
|
| 107 |
</button>
|
| 108 |
</div>
|
| 109 |
<div class="tips-row" id="tips-row">
|
| 110 |
-
<span class="tip-pill"
|
| 111 |
-
<span class="tip-pill"
|
| 112 |
<span class="tip-pill">画面比例 / 构图 写清楚更稳定</span>
|
| 113 |
</div>
|
| 114 |
</div>
|
|
@@ -141,7 +168,7 @@
|
|
| 141 |
<div class="section-title">
|
| 142 |
<div>
|
| 143 |
<h3>社区创意画廊</h3>
|
| 144 |
-
<p class="section-subtitle" id="public-gallery-hint"
|
| 145 |
</div>
|
| 146 |
<div class="section-actions">
|
| 147 |
<button class="icon-btn" id="refresh-public-gallery" title="刷新公共画廊" aria-label="刷新公共画廊">
|
|
@@ -175,22 +202,21 @@
|
|
| 175 |
<button class="icon-btn" id="m-download" title="保存图片" aria-label="保存图片">保存</button>
|
| 176 |
</div>
|
| 177 |
<button class="btn-3d" id="m-reuse" style="width: 100%; padding: 12px;">
|
| 178 |
-
|
| 179 |
-
</button>
|
| 180 |
</div>
|
| 181 |
</div>
|
| 182 |
</div>
|
| 183 |
|
| 184 |
<script>
|
| 185 |
// ============================================
|
| 186 |
-
//
|
| 187 |
// ============================================
|
| 188 |
const AppState = {
|
| 189 |
db: null,
|
| 190 |
-
currentImages: [], //
|
| 191 |
galleryData: [], // 个人画廊数据缓存
|
| 192 |
publicGalleryData: [], // 公共画廊数据缓存
|
| 193 |
-
currentModalItem: null //
|
| 194 |
};
|
| 195 |
|
| 196 |
const Device = {
|
|
@@ -202,7 +228,7 @@
|
|
| 202 |
publicGalleryTokens: 'BananaPro_PublicGallery_Tokens_v1'
|
| 203 |
};
|
| 204 |
|
| 205 |
-
const DEFAULT_PUBLIC_HINT = '
|
| 206 |
|
| 207 |
const DB_NAME = 'BananaProDB_v3';
|
| 208 |
const DB_VERSION = 1;
|
|
@@ -242,13 +268,13 @@
|
|
| 242 |
const tx = AppState.db.transaction([STORE_NAME], 'readwrite');
|
| 243 |
const store = tx.objectStore(STORE_NAME);
|
| 244 |
|
| 245 |
-
//
|
| 246 |
const record = {
|
| 247 |
prompt: item.prompt,
|
| 248 |
-
image: item.image, // data:image/...
|
| 249 |
imageUrl: item.imageUrl || null,
|
| 250 |
imageId: item.imageId || null,
|
| 251 |
-
thumb: item.thumb || null, //
|
| 252 |
inputImages: item.inputImages || [],
|
| 253 |
timestamp: Date.now()
|
| 254 |
};
|
|
@@ -319,7 +345,7 @@
|
|
| 319 |
// 图片处理模块
|
| 320 |
// ============================================
|
| 321 |
const ImageHandler = {
|
| 322 |
-
//
|
| 323 |
fileToBase64(file) {
|
| 324 |
return new Promise((resolve, reject) => {
|
| 325 |
const reader = new FileReader();
|
|
@@ -329,7 +355,7 @@
|
|
| 329 |
});
|
| 330 |
},
|
| 331 |
|
| 332 |
-
//
|
| 333 |
async compressImage(base64Data, maxWidth = 1280, quality = 0.8) {
|
| 334 |
return new Promise((resolve) => {
|
| 335 |
const img = new Image();
|
|
@@ -460,14 +486,14 @@
|
|
| 460 |
item.displayUrl = null;
|
| 461 |
},
|
| 462 |
|
| 463 |
-
//
|
| 464 |
async processFiles(files) {
|
| 465 |
const maxImages = 16;
|
| 466 |
const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/'));
|
| 467 |
|
| 468 |
for (const file of imageFiles) {
|
| 469 |
if (AppState.currentImages.length >= maxImages) {
|
| 470 |
-
alert(
|
| 471 |
break;
|
| 472 |
}
|
| 473 |
|
|
@@ -485,13 +511,13 @@
|
|
| 485 |
PreviewManager.render();
|
| 486 |
},
|
| 487 |
|
| 488 |
-
//
|
| 489 |
removeAt(index) {
|
| 490 |
AppState.currentImages.splice(index, 1);
|
| 491 |
PreviewManager.render();
|
| 492 |
},
|
| 493 |
|
| 494 |
-
//
|
| 495 |
clear() {
|
| 496 |
AppState.currentImages = [];
|
| 497 |
PreviewManager.render();
|
|
@@ -533,7 +559,7 @@
|
|
| 533 |
|
| 534 |
this.container.classList.add('visible');
|
| 535 |
this.uploadBtn.classList.add('active');
|
| 536 |
-
StatusBar.setText("
|
| 537 |
|
| 538 |
images.forEach((imgData, index) => {
|
| 539 |
const wrapper = document.createElement('div');
|
|
@@ -792,7 +818,7 @@
|
|
| 792 |
this.container.innerHTML = `
|
| 793 |
<div style="grid-column: 1/-1; text-align: center; color: var(--text-sub); padding: 60px 20px;">
|
| 794 |
<div style="font-size: 48px; margin-bottom: 10px;">暂无</div>
|
| 795 |
-
<div
|
| 796 |
</div>
|
| 797 |
`;
|
| 798 |
return;
|
|
@@ -837,7 +863,7 @@
|
|
| 837 |
if (item.inputImages && item.inputImages.length > 0) {
|
| 838 |
const badge = document.createElement('div');
|
| 839 |
badge.className = 'item-badge';
|
| 840 |
-
badge.textContent =
|
| 841 |
el.appendChild(badge);
|
| 842 |
}
|
| 843 |
|
|
@@ -862,8 +888,8 @@
|
|
| 862 |
const shareBtn = document.createElement('button');
|
| 863 |
shareBtn.className = 'icon-btn share-btn';
|
| 864 |
shareBtn.textContent = '发布';
|
| 865 |
-
shareBtn.title = '
|
| 866 |
-
shareBtn.setAttribute('aria-label', '
|
| 867 |
shareBtn.onclick = (e) => {
|
| 868 |
e.stopPropagation();
|
| 869 |
PublicGalleryManager.share(item, shareBtn);
|
|
@@ -970,7 +996,7 @@
|
|
| 970 |
},
|
| 971 |
|
| 972 |
async deleteItem(id) {
|
| 973 |
-
if (!confirm('
|
| 974 |
|
| 975 |
try {
|
| 976 |
const targetItem = (AppState.galleryData || []).find((item) => item.id === id);
|
|
@@ -1027,19 +1053,19 @@
|
|
| 1027 |
open(item) {
|
| 1028 |
AppState.currentModalItem = item;
|
| 1029 |
|
| 1030 |
-
// 设置主图 -
|
| 1031 |
this.imgEl.src = ImageHandler.getDisplayImage(item) || item.thumb || '';
|
| 1032 |
|
| 1033 |
-
//
|
| 1034 |
this.promptEl.textContent = item.prompt;
|
| 1035 |
|
| 1036 |
-
//
|
| 1037 |
this.refsEl.innerHTML = '';
|
| 1038 |
if (item.inputImages && item.inputImages.length > 0) {
|
| 1039 |
item.inputImages.forEach(imgData => {
|
| 1040 |
const thumb = document.createElement('img');
|
| 1041 |
thumb.className = 'ref-thumb';
|
| 1042 |
-
thumb.src = imgData; //
|
| 1043 |
thumb.onclick = () => window.open(imgData, '_blank');
|
| 1044 |
this.refsEl.appendChild(thumb);
|
| 1045 |
});
|
|
@@ -1071,13 +1097,13 @@
|
|
| 1071 |
const item = AppState.currentModalItem;
|
| 1072 |
if (!item) return;
|
| 1073 |
|
| 1074 |
-
//
|
| 1075 |
const textarea = document.getElementById('prompt');
|
| 1076 |
textarea.value = item.prompt;
|
| 1077 |
textarea.style.height = 'auto';
|
| 1078 |
textarea.style.height = textarea.scrollHeight + 'px';
|
| 1079 |
|
| 1080 |
-
//
|
| 1081 |
if (item.inputImages && item.inputImages.length > 0) {
|
| 1082 |
ImageHandler.setImages(item.inputImages);
|
| 1083 |
} else {
|
|
@@ -1240,7 +1266,7 @@
|
|
| 1240 |
},
|
| 1241 |
|
| 1242 |
startRealtimeSync() {
|
| 1243 |
-
//
|
| 1244 |
},
|
| 1245 |
|
| 1246 |
stopRealtimeSync() {
|
|
@@ -1308,7 +1334,7 @@
|
|
| 1308 |
async fetch() {
|
| 1309 |
if (!this.container) return;
|
| 1310 |
|
| 1311 |
-
//
|
| 1312 |
const now = Date.now();
|
| 1313 |
if (now - this.lastFetchTime < this.minFetchInterval) {
|
| 1314 |
return;
|
|
@@ -1340,8 +1366,8 @@
|
|
| 1340 |
|
| 1341 |
let errorMessage = '加载失败,请稍后重试';
|
| 1342 |
if (error.name === 'AbortError') {
|
| 1343 |
-
errorMessage = '
|
| 1344 |
-
this.setHint('
|
| 1345 |
} else {
|
| 1346 |
this.setHint(errorMessage, 'error');
|
| 1347 |
}
|
|
@@ -1548,7 +1574,7 @@
|
|
| 1548 |
async share(item, triggerBtn) {
|
| 1549 |
if (!item) return;
|
| 1550 |
|
| 1551 |
-
const confirmShare = confirm('
|
| 1552 |
if (!confirmShare) {
|
| 1553 |
return;
|
| 1554 |
}
|
|
@@ -1593,7 +1619,7 @@
|
|
| 1593 |
this.render();
|
| 1594 |
this.setHint('作品已发布至公共画廊', 'success');
|
| 1595 |
} catch (error) {
|
| 1596 |
-
console.error('
|
| 1597 |
alert('发布失败: ' + error.message);
|
| 1598 |
this.setHint('发布失败,请稍后重试', 'error');
|
| 1599 |
} finally {
|
|
@@ -1604,7 +1630,7 @@
|
|
| 1604 |
},
|
| 1605 |
|
| 1606 |
async delete(id) {
|
| 1607 |
-
const confirmDelete = confirm('
|
| 1608 |
if (!confirmDelete) {
|
| 1609 |
return;
|
| 1610 |
}
|
|
@@ -1628,7 +1654,7 @@
|
|
| 1628 |
AppState.publicGalleryData = AppState.publicGalleryData.filter(item => item.id !== id);
|
| 1629 |
this.removeOwnership(id);
|
| 1630 |
this.render();
|
| 1631 |
-
this.setHint('
|
| 1632 |
} catch (error) {
|
| 1633 |
console.error('删除公共画廊作品失败:', error);
|
| 1634 |
alert('删除失败: ' + error.message);
|
|
@@ -1706,7 +1732,7 @@
|
|
| 1706 |
if (this.isGenerating) {
|
| 1707 |
this.setLoading(false);
|
| 1708 |
StatusBar.flash("生成超时");
|
| 1709 |
-
alert("
|
| 1710 |
}
|
| 1711 |
}, uiTimeoutMs);
|
| 1712 |
} else {
|
|
@@ -1968,7 +1994,7 @@
|
|
| 1968 |
};
|
| 1969 |
|
| 1970 |
// ============================================
|
| 1971 |
-
// 全局函数(供 HTML onclick
|
| 1972 |
// ============================================
|
| 1973 |
async function loadWorkspaceData() {
|
| 1974 |
try {
|
|
@@ -2001,12 +2027,11 @@
|
|
| 2001 |
}
|
| 2002 |
|
| 2003 |
// ============================================
|
| 2004 |
-
//
|
| 2005 |
// ============================================
|
| 2006 |
async function initApp() {
|
| 2007 |
try {
|
| 2008 |
-
//
|
| 2009 |
-
try {
|
| 2010 |
await Database.init();
|
| 2011 |
} catch (dbErr) {
|
| 2012 |
console.warn('Database init failed:', dbErr);
|
|
@@ -2024,8 +2049,7 @@
|
|
| 2024 |
DragDrop.init();
|
| 2025 |
FileSelector.init();
|
| 2026 |
|
| 2027 |
-
//
|
| 2028 |
-
const isAuth = await Auth.check();
|
| 2029 |
if (isAuth) {
|
| 2030 |
Auth.unlock();
|
| 2031 |
await loadWorkspaceData();
|
|
@@ -2046,3 +2070,4 @@
|
|
| 2046 |
</script>
|
| 2047 |
</body>
|
| 2048 |
</html>
|
|
|
|
|
|
| 22 |
}
|
| 23 |
|
| 24 |
header {
|
| 25 |
+
margin-top: var(--input-offset, 115px); /* 为输入区留空�?*/
|
| 26 |
}
|
| 27 |
|
| 28 |
.history-container {
|
| 29 |
padding-bottom: 20px !important;
|
| 30 |
}
|
| 31 |
|
| 32 |
+
/* 调整输入区内部布�? */
|
| 33 |
.control-bar {
|
| 34 |
max-width: 1400px;
|
| 35 |
margin: 0 auto;
|
|
|
|
| 46 |
padding: 12px 20px;
|
| 47 |
}
|
| 48 |
|
| 49 |
+
/* 动�?�调整header间距 */
|
| 50 |
.has-preview header {
|
| 51 |
+
margin-top: var(--input-offset, 260px);
|
| 52 |
}
|
| 53 |
|
| 54 |
@media (max-width: 768px) {
|
| 55 |
header {
|
| 56 |
+
margin-top: var(--input-offset, 170px);
|
| 57 |
}
|
| 58 |
.has-preview header {
|
| 59 |
+
margin-top: var(--input-offset, 320px);
|
| 60 |
}
|
| 61 |
}
|
| 62 |
</style>
|
| 63 |
</head>
|
| 64 |
<body>
|
| 65 |
+
<!-- 在预览栏显示时添�?has-preview �?-->
|
| 66 |
+
<script>
|
| 67 |
+
// Observe preview bar changes and input height.
|
| 68 |
+
const observePreviewBar = () => {
|
| 69 |
+
const previewBar = document.getElementById('preview-bar');
|
| 70 |
+
if (!previewBar) return;
|
| 71 |
+
const observer = new MutationObserver(() => {
|
| 72 |
+
if (previewBar.classList.contains('visible')) {
|
| 73 |
+
document.body.classList.add('has-preview');
|
| 74 |
+
} else {
|
| 75 |
+
document.body.classList.remove('has-preview');
|
| 76 |
+
}
|
| 77 |
+
});
|
| 78 |
+
observer.observe(previewBar, { attributes: true, attributeFilter: ['class'] });
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
const updateInputOffset = () => {
|
| 82 |
+
const inputSection = document.querySelector('.input-section');
|
| 83 |
+
if (!inputSection) return;
|
| 84 |
+
const gap = 16;
|
| 85 |
+
const nextOffset = inputSection.offsetHeight + gap;
|
| 86 |
+
document.documentElement.style.setProperty('--input-offset', nextOffset + 'px');
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
const observeInputSection = () => {
|
| 90 |
+
const inputSection = document.querySelector('.input-section');
|
| 91 |
+
if (!inputSection) return;
|
| 92 |
+
updateInputOffset();
|
| 93 |
+
if ('ResizeObserver' in window) {
|
| 94 |
+
const observer = new ResizeObserver(() => {
|
| 95 |
+
requestAnimationFrame(updateInputOffset);
|
| 96 |
+
});
|
| 97 |
+
observer.observe(inputSection);
|
| 98 |
+
} else {
|
| 99 |
+
window.addEventListener('resize', updateInputOffset);
|
| 100 |
+
}
|
| 101 |
+
};
|
| 102 |
+
|
| 103 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 104 |
+
observePreviewBar();
|
| 105 |
+
observeInputSection();
|
| 106 |
+
});
|
| 107 |
</script>
|
| 108 |
|
| 109 |
<!-- 登录界面 -->
|
|
|
|
| 116 |
</div>
|
| 117 |
</div>
|
| 118 |
|
| 119 |
+
<!-- 主应�?-->
|
| 120 |
<div class="app-container" id="app" style="filter: blur(10px); pointer-events: none;">
|
| 121 |
+
<!-- 顶部固定输入�?-->
|
| 122 |
<div class="input-section glass-panel" id="drop-zone">
|
| 123 |
+
<!-- 上部分:预览�?-->
|
| 124 |
<div class="preview-bar" id="preview-bar"></div>
|
| 125 |
|
| 126 |
+
<!-- 下部分:操作�?-->
|
| 127 |
<div class="control-bar">
|
| 128 |
+
<button class="upload-trigger" id="upload-btn" title="上传参�?�图">上传图片</button>
|
| 129 |
<textarea id="prompt" class="main-input" rows="1" placeholder="描述画面... (支持拖拽图片)"></textarea>
|
| 130 |
<button class="ghost-btn" id="clear-btn" title="清空输入" aria-label="清空输入">清空</button>
|
| 131 |
<button class="btn-3d send-btn" id="send-btn">
|
|
|
|
| 134 |
</button>
|
| 135 |
</div>
|
| 136 |
<div class="tips-row" id="tips-row">
|
| 137 |
+
<span class="tip-pill">提示:写明主�?+ 风格 + 光线</span>
|
| 138 |
+
<span class="tip-pill">支持参�?�图:先上传再描�?/span>
|
| 139 |
<span class="tip-pill">画面比例 / 构图 写清楚更稳定</span>
|
| 140 |
</div>
|
| 141 |
</div>
|
|
|
|
| 168 |
<div class="section-title">
|
| 169 |
<div>
|
| 170 |
<h3>社区创意画廊</h3>
|
| 171 |
+
<p class="section-subtitle" id="public-gallery-hint">分享你的作品,欣赏社区灵�?/p>
|
| 172 |
</div>
|
| 173 |
<div class="section-actions">
|
| 174 |
<button class="icon-btn" id="refresh-public-gallery" title="刷新公共画廊" aria-label="刷新公共画廊">
|
|
|
|
| 202 |
<button class="icon-btn" id="m-download" title="保存图片" aria-label="保存图片">保存</button>
|
| 203 |
</div>
|
| 204 |
<button class="btn-3d" id="m-reuse" style="width: 100%; padding: 12px;">
|
| 205 |
+
复用参数与图�? </button>
|
|
|
|
| 206 |
</div>
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
|
| 210 |
<script>
|
| 211 |
// ============================================
|
| 212 |
+
// 全局状�?�管�?
|
| 213 |
// ============================================
|
| 214 |
const AppState = {
|
| 215 |
db: null,
|
| 216 |
+
currentImages: [], // 当前上传图片的图�?Base64 数组
|
| 217 |
galleryData: [], // 个人画廊数据缓存
|
| 218 |
publicGalleryData: [], // 公共画廊数据缓存
|
| 219 |
+
currentModalItem: null // 当前弹窗显示的项�?
|
| 220 |
};
|
| 221 |
|
| 222 |
const Device = {
|
|
|
|
| 228 |
publicGalleryTokens: 'BananaPro_PublicGallery_Tokens_v1'
|
| 229 |
};
|
| 230 |
|
| 231 |
+
const DEFAULT_PUBLIC_HINT = '分享你的作品,欣赏社区灵�?;
|
| 232 |
|
| 233 |
const DB_NAME = 'BananaProDB_v3';
|
| 234 |
const DB_VERSION = 1;
|
|
|
|
| 268 |
const tx = AppState.db.transaction([STORE_NAME], 'readwrite');
|
| 269 |
const store = tx.objectStore(STORE_NAME);
|
| 270 |
|
| 271 |
+
// 直接存储完整对象,不做任何转�?
|
| 272 |
const record = {
|
| 273 |
prompt: item.prompt,
|
| 274 |
+
image: item.image, // data:image/... �?/generated/... URL
|
| 275 |
imageUrl: item.imageUrl || null,
|
| 276 |
imageId: item.imageId || null,
|
| 277 |
+
thumb: item.thumb || null, // 缩略图(用于加�?�列表渲染)
|
| 278 |
inputImages: item.inputImages || [],
|
| 279 |
timestamp: Date.now()
|
| 280 |
};
|
|
|
|
| 345 |
// 图片处理模块
|
| 346 |
// ============================================
|
| 347 |
const ImageHandler = {
|
| 348 |
+
// 文件�?Base64
|
| 349 |
fileToBase64(file) {
|
| 350 |
return new Promise((resolve, reject) => {
|
| 351 |
const reader = new FileReader();
|
|
|
|
| 355 |
});
|
| 356 |
},
|
| 357 |
|
| 358 |
+
// 压缩图片(手机端优化�?
|
| 359 |
async compressImage(base64Data, maxWidth = 1280, quality = 0.8) {
|
| 360 |
return new Promise((resolve) => {
|
| 361 |
const img = new Image();
|
|
|
|
| 486 |
item.displayUrl = null;
|
| 487 |
},
|
| 488 |
|
| 489 |
+
// 处理上传图片的文�?
|
| 490 |
async processFiles(files) {
|
| 491 |
const maxImages = 16;
|
| 492 |
const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/'));
|
| 493 |
|
| 494 |
for (const file of imageFiles) {
|
| 495 |
if (AppState.currentImages.length >= maxImages) {
|
| 496 |
+
alert(`�?多只能上传图�?${maxImages} 张图片`);
|
| 497 |
break;
|
| 498 |
}
|
| 499 |
|
|
|
|
| 511 |
PreviewManager.render();
|
| 512 |
},
|
| 513 |
|
| 514 |
+
// 移除指定索引的图�?
|
| 515 |
removeAt(index) {
|
| 516 |
AppState.currentImages.splice(index, 1);
|
| 517 |
PreviewManager.render();
|
| 518 |
},
|
| 519 |
|
| 520 |
+
// 清空�?有上传图片的图片
|
| 521 |
clear() {
|
| 522 |
AppState.currentImages = [];
|
| 523 |
PreviewManager.render();
|
|
|
|
| 559 |
|
| 560 |
this.container.classList.add('visible');
|
| 561 |
this.uploadBtn.classList.add('active');
|
| 562 |
+
StatusBar.setText("已�?�择 " + images.length + '/16 ' + "张图�?);
|
| 563 |
|
| 564 |
images.forEach((imgData, index) => {
|
| 565 |
const wrapper = document.createElement('div');
|
|
|
|
| 818 |
this.container.innerHTML = `
|
| 819 |
<div style="grid-column: 1/-1; text-align: center; color: var(--text-sub); padding: 60px 20px;">
|
| 820 |
<div style="font-size: 48px; margin-bottom: 10px;">暂无</div>
|
| 821 |
+
<div>暂无作品,开始创作吧�?/div>
|
| 822 |
</div>
|
| 823 |
`;
|
| 824 |
return;
|
|
|
|
| 863 |
if (item.inputImages && item.inputImages.length > 0) {
|
| 864 |
const badge = document.createElement('div');
|
| 865 |
badge.className = 'item-badge';
|
| 866 |
+
badge.textContent = `参�??${item.inputImages.length}`;
|
| 867 |
el.appendChild(badge);
|
| 868 |
}
|
| 869 |
|
|
|
|
| 888 |
const shareBtn = document.createElement('button');
|
| 889 |
shareBtn.className = 'icon-btn share-btn';
|
| 890 |
shareBtn.textContent = '发布';
|
| 891 |
+
shareBtn.title = '发布到公共画�?;
|
| 892 |
+
shareBtn.setAttribute('aria-label', '发布到公共画�?);
|
| 893 |
shareBtn.onclick = (e) => {
|
| 894 |
e.stopPropagation();
|
| 895 |
PublicGalleryManager.share(item, shareBtn);
|
|
|
|
| 996 |
},
|
| 997 |
|
| 998 |
async deleteItem(id) {
|
| 999 |
+
if (!confirm('确定要删除这张图片吗�?)) return;
|
| 1000 |
|
| 1001 |
try {
|
| 1002 |
const targetItem = (AppState.galleryData || []).find((item) => item.id === id);
|
|
|
|
| 1053 |
open(item) {
|
| 1054 |
AppState.currentModalItem = item;
|
| 1055 |
|
| 1056 |
+
// 设置主图 - 直接赋�??
|
| 1057 |
this.imgEl.src = ImageHandler.getDisplayImage(item) || item.thumb || '';
|
| 1058 |
|
| 1059 |
+
// 设置提示�?
|
| 1060 |
this.promptEl.textContent = item.prompt;
|
| 1061 |
|
| 1062 |
+
// 渲染参�?�图
|
| 1063 |
this.refsEl.innerHTML = '';
|
| 1064 |
if (item.inputImages && item.inputImages.length > 0) {
|
| 1065 |
item.inputImages.forEach(imgData => {
|
| 1066 |
const thumb = document.createElement('img');
|
| 1067 |
thumb.className = 'ref-thumb';
|
| 1068 |
+
thumb.src = imgData; // 直接赋�??
|
| 1069 |
thumb.onclick = () => window.open(imgData, '_blank');
|
| 1070 |
this.refsEl.appendChild(thumb);
|
| 1071 |
});
|
|
|
|
| 1097 |
const item = AppState.currentModalItem;
|
| 1098 |
if (!item) return;
|
| 1099 |
|
| 1100 |
+
// 复用提示�?
|
| 1101 |
const textarea = document.getElementById('prompt');
|
| 1102 |
textarea.value = item.prompt;
|
| 1103 |
textarea.style.height = 'auto';
|
| 1104 |
textarea.style.height = textarea.scrollHeight + 'px';
|
| 1105 |
|
| 1106 |
+
// 复用参�?�图
|
| 1107 |
if (item.inputImages && item.inputImages.length > 0) {
|
| 1108 |
ImageHandler.setImages(item.inputImages);
|
| 1109 |
} else {
|
|
|
|
| 1266 |
},
|
| 1267 |
|
| 1268 |
startRealtimeSync() {
|
| 1269 |
+
// 实时同步已禁�?- 改为手动刷新提高性能
|
| 1270 |
},
|
| 1271 |
|
| 1272 |
stopRealtimeSync() {
|
|
|
|
| 1334 |
async fetch() {
|
| 1335 |
if (!this.container) return;
|
| 1336 |
|
| 1337 |
+
// 防止过于频繁的请�?
|
| 1338 |
const now = Date.now();
|
| 1339 |
if (now - this.lastFetchTime < this.minFetchInterval) {
|
| 1340 |
return;
|
|
|
|
| 1366 |
|
| 1367 |
let errorMessage = '加载失败,请稍后重试';
|
| 1368 |
if (error.name === 'AbortError') {
|
| 1369 |
+
errorMessage = '加载超时,请�?查网�?;
|
| 1370 |
+
this.setHint('加载超时,请�?查网�?, 'error');
|
| 1371 |
} else {
|
| 1372 |
this.setHint(errorMessage, 'error');
|
| 1373 |
}
|
|
|
|
| 1574 |
async share(item, triggerBtn) {
|
| 1575 |
if (!item) return;
|
| 1576 |
|
| 1577 |
+
const confirmShare = confirm('确认将这张作品发布到公共画廊?提示词将对�?有人可见�?);
|
| 1578 |
if (!confirmShare) {
|
| 1579 |
return;
|
| 1580 |
}
|
|
|
|
| 1619 |
this.render();
|
| 1620 |
this.setHint('作品已发布至公共画廊', 'success');
|
| 1621 |
} catch (error) {
|
| 1622 |
+
console.error('发布到公共画廊失�?', error);
|
| 1623 |
alert('发布失败: ' + error.message);
|
| 1624 |
this.setHint('发布失败,请稍后重试', 'error');
|
| 1625 |
} finally {
|
|
|
|
| 1630 |
},
|
| 1631 |
|
| 1632 |
async delete(id) {
|
| 1633 |
+
const confirmDelete = confirm('确定要删除这张公共画廊作品吗�?);
|
| 1634 |
if (!confirmDelete) {
|
| 1635 |
return;
|
| 1636 |
}
|
|
|
|
| 1654 |
AppState.publicGalleryData = AppState.publicGalleryData.filter(item => item.id !== id);
|
| 1655 |
this.removeOwnership(id);
|
| 1656 |
this.render();
|
| 1657 |
+
this.setHint('作品已删�?, 'success');
|
| 1658 |
} catch (error) {
|
| 1659 |
console.error('删除公共画廊作品失败:', error);
|
| 1660 |
alert('删除失败: ' + error.message);
|
|
|
|
| 1732 |
if (this.isGenerating) {
|
| 1733 |
this.setLoading(false);
|
| 1734 |
StatusBar.flash("生成超时");
|
| 1735 |
+
alert("生成超时,请�?查网络后重试");
|
| 1736 |
}
|
| 1737 |
}, uiTimeoutMs);
|
| 1738 |
} else {
|
|
|
|
| 1994 |
};
|
| 1995 |
|
| 1996 |
// ============================================
|
| 1997 |
+
// 全局函数(供 HTML onclick 调用�?
|
| 1998 |
// ============================================
|
| 1999 |
async function loadWorkspaceData() {
|
| 2000 |
try {
|
|
|
|
| 2027 |
}
|
| 2028 |
|
| 2029 |
// ============================================
|
| 2030 |
+
// 应用初始�?
|
| 2031 |
// ============================================
|
| 2032 |
async function initApp() {
|
| 2033 |
try {
|
| 2034 |
+
// 初始化数据库(允许失败,避免移动端被阻塞�? try {
|
|
|
|
| 2035 |
await Database.init();
|
| 2036 |
} catch (dbErr) {
|
| 2037 |
console.warn('Database init failed:', dbErr);
|
|
|
|
| 2049 |
DragDrop.init();
|
| 2050 |
FileSelector.init();
|
| 2051 |
|
| 2052 |
+
// �?查认证状�? const isAuth = await Auth.check();
|
|
|
|
| 2053 |
if (isAuth) {
|
| 2054 |
Auth.unlock();
|
| 2055 |
await loadWorkspaceData();
|
|
|
|
| 2070 |
</script>
|
| 2071 |
</body>
|
| 2072 |
</html>
|
| 2073 |
+
|
stats.html
CHANGED
|
@@ -6,24 +6,49 @@
|
|
| 6 |
<title>Banana Pro - 站点统计</title>
|
| 7 |
<link rel="stylesheet" href="style.css">
|
| 8 |
<style>
|
| 9 |
-
body.stats-page {
|
| 10 |
-
background:
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
.stats-title {
|
| 29 |
margin: 0;
|
|
@@ -35,14 +60,24 @@
|
|
| 35 |
background-clip: text;
|
| 36 |
}
|
| 37 |
|
| 38 |
-
.stats-meta {
|
| 39 |
-
display: flex;
|
| 40 |
-
gap: 16px;
|
| 41 |
-
flex-wrap: wrap;
|
| 42 |
-
margin-top: 6px;
|
| 43 |
-
color: var(--text-sub);
|
| 44 |
-
font-size: 12px;
|
| 45 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
.stats-actions {
|
| 48 |
display: flex;
|
|
@@ -51,34 +86,55 @@
|
|
| 51 |
flex-wrap: wrap;
|
| 52 |
}
|
| 53 |
|
| 54 |
-
.stats-grid {
|
| 55 |
-
display: grid;
|
| 56 |
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
| 57 |
-
gap:
|
| 58 |
-
margin-top: 24px;
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
.stats-card {
|
| 62 |
-
background: var(--panel-bg);
|
| 63 |
-
border: 1px solid var(--panel-border);
|
| 64 |
-
border-radius: 18px;
|
| 65 |
-
padding: 16px 18px;
|
| 66 |
-
box-shadow: 0
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
.stats-sub {
|
| 84 |
margin-top: 8px;
|
|
@@ -89,28 +145,45 @@
|
|
| 89 |
flex-wrap: wrap;
|
| 90 |
}
|
| 91 |
|
| 92 |
-
.stats-panel {
|
| 93 |
-
margin-top: 20px;
|
| 94 |
-
background:
|
| 95 |
-
border: 1px solid var(--panel-border);
|
| 96 |
-
border-radius: 20px;
|
| 97 |
-
padding: 18px 20px;
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
.stats-legend {
|
| 116 |
margin-top: 10px;
|
|
@@ -165,14 +238,24 @@
|
|
| 165 |
height: 100%;
|
| 166 |
}
|
| 167 |
|
| 168 |
-
.bar-fill {
|
| 169 |
-
width: 100%;
|
| 170 |
-
border-radius: 10px 10px 6px 6px;
|
| 171 |
-
background: linear-gradient(180deg, var(--accent-color), var(--accent-secondary));
|
| 172 |
-
min-height: 10px;
|
| 173 |
-
transition: height 0.4s ease;
|
| 174 |
-
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3);
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
.bar-fill.share {
|
| 178 |
background: linear-gradient(180deg, #22c55e, #16a34a);
|
|
@@ -205,19 +288,19 @@
|
|
| 205 |
color: rgba(148, 163, 184, 0.7);
|
| 206 |
}
|
| 207 |
|
| 208 |
-
@media (max-width: 768px) {
|
| 209 |
-
.stats-shell {
|
| 210 |
-
padding: 100px 16px 60px;
|
| 211 |
-
}
|
| 212 |
|
| 213 |
.stats-title {
|
| 214 |
font-size: 1.35rem;
|
| 215 |
}
|
| 216 |
|
| 217 |
-
.bars {
|
| 218 |
-
height: 140px;
|
| 219 |
-
}
|
| 220 |
-
}
|
| 221 |
</style>
|
| 222 |
</head>
|
| 223 |
<body class="stats-page">
|
|
|
|
| 6 |
<title>Banana Pro - 站点统计</title>
|
| 7 |
<link rel="stylesheet" href="style.css">
|
| 8 |
<style>
|
| 9 |
+
body.stats-page {
|
| 10 |
+
background:
|
| 11 |
+
radial-gradient(700px 360px at 12% -10%, rgba(59, 130, 246, 0.18), transparent 60%),
|
| 12 |
+
radial-gradient(700px 420px at 88% -20%, rgba(34, 211, 238, 0.2), transparent 55%),
|
| 13 |
+
var(--bg-color);
|
| 14 |
+
min-height: 100vh;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
.stats-shell {
|
| 18 |
+
max-width: 1200px;
|
| 19 |
+
margin: 0 auto;
|
| 20 |
+
padding: 120px 20px 80px;
|
| 21 |
+
width: 100%;
|
| 22 |
+
position: relative;
|
| 23 |
+
z-index: 0;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.stats-shell::before {
|
| 27 |
+
content: '';
|
| 28 |
+
position: absolute;
|
| 29 |
+
inset: 60px 0 0;
|
| 30 |
+
background:
|
| 31 |
+
radial-gradient(480px 280px at 15% 0%, rgba(59, 130, 246, 0.16), transparent 70%),
|
| 32 |
+
radial-gradient(520px 320px at 85% 10%, rgba(34, 211, 238, 0.12), transparent 70%);
|
| 33 |
+
opacity: 0.75;
|
| 34 |
+
pointer-events: none;
|
| 35 |
+
z-index: 0;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.stats-shell > * {
|
| 39 |
+
position: relative;
|
| 40 |
+
z-index: 1;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.stats-header {
|
| 44 |
+
display: flex;
|
| 45 |
+
align-items: flex-start;
|
| 46 |
+
justify-content: space-between;
|
| 47 |
+
gap: 16px;
|
| 48 |
+
flex-wrap: wrap;
|
| 49 |
+
padding-bottom: 12px;
|
| 50 |
+
border-bottom: 1px solid var(--panel-border);
|
| 51 |
+
}
|
| 52 |
|
| 53 |
.stats-title {
|
| 54 |
margin: 0;
|
|
|
|
| 60 |
background-clip: text;
|
| 61 |
}
|
| 62 |
|
| 63 |
+
.stats-meta {
|
| 64 |
+
display: flex;
|
| 65 |
+
gap: 16px;
|
| 66 |
+
flex-wrap: wrap;
|
| 67 |
+
margin-top: 6px;
|
| 68 |
+
color: var(--text-sub);
|
| 69 |
+
font-size: 12px;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.stats-meta span {
|
| 73 |
+
display: inline-flex;
|
| 74 |
+
align-items: center;
|
| 75 |
+
gap: 6px;
|
| 76 |
+
padding: 4px 10px;
|
| 77 |
+
border-radius: 999px;
|
| 78 |
+
background: rgba(15, 23, 42, 0.55);
|
| 79 |
+
border: 1px solid rgba(148, 163, 184, 0.2);
|
| 80 |
+
}
|
| 81 |
|
| 82 |
.stats-actions {
|
| 83 |
display: flex;
|
|
|
|
| 86 |
flex-wrap: wrap;
|
| 87 |
}
|
| 88 |
|
| 89 |
+
.stats-grid {
|
| 90 |
+
display: grid;
|
| 91 |
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
| 92 |
+
gap: 18px;
|
| 93 |
+
margin-top: 24px;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.stats-card {
|
| 97 |
+
background: var(--panel-bg);
|
| 98 |
+
border: 1px solid var(--panel-border);
|
| 99 |
+
border-radius: 18px;
|
| 100 |
+
padding: 16px 18px;
|
| 101 |
+
box-shadow: 0 14px 32px rgba(0, 0, 0, 0.25);
|
| 102 |
+
position: relative;
|
| 103 |
+
overflow: hidden;
|
| 104 |
+
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.stats-card::before {
|
| 108 |
+
content: '';
|
| 109 |
+
position: absolute;
|
| 110 |
+
top: 0;
|
| 111 |
+
left: 0;
|
| 112 |
+
right: 0;
|
| 113 |
+
height: 3px;
|
| 114 |
+
background: linear-gradient(90deg, rgba(59, 130, 246, 0.9), rgba(34, 211, 238, 0.9));
|
| 115 |
+
opacity: 0.75;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.stats-card:hover {
|
| 119 |
+
transform: translateY(-4px);
|
| 120 |
+
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.35);
|
| 121 |
+
border-color: var(--panel-border-strong);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.stats-label {
|
| 125 |
+
color: var(--text-sub);
|
| 126 |
+
font-size: 11px;
|
| 127 |
+
letter-spacing: 1px;
|
| 128 |
+
text-transform: uppercase;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.stats-value {
|
| 132 |
+
font-size: 2rem;
|
| 133 |
+
font-weight: 700;
|
| 134 |
+
margin-top: 10px;
|
| 135 |
+
color: var(--text-main);
|
| 136 |
+
letter-spacing: 0.5px;
|
| 137 |
+
}
|
| 138 |
|
| 139 |
.stats-sub {
|
| 140 |
margin-top: 8px;
|
|
|
|
| 145 |
flex-wrap: wrap;
|
| 146 |
}
|
| 147 |
|
| 148 |
+
.stats-panel {
|
| 149 |
+
margin-top: 20px;
|
| 150 |
+
background: linear-gradient(135deg, rgba(15, 23, 42, 0.7), rgba(15, 23, 42, 0.95));
|
| 151 |
+
border: 1px solid var(--panel-border);
|
| 152 |
+
border-radius: 20px;
|
| 153 |
+
padding: 18px 20px;
|
| 154 |
+
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.25);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.panel-title {
|
| 158 |
+
font-size: 0.95rem;
|
| 159 |
+
color: var(--text-main);
|
| 160 |
+
font-weight: 600;
|
| 161 |
+
display: flex;
|
| 162 |
+
align-items: center;
|
| 163 |
+
gap: 8px;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.panel-title::before {
|
| 167 |
+
content: '';
|
| 168 |
+
width: 10px;
|
| 169 |
+
height: 10px;
|
| 170 |
+
border-radius: 999px;
|
| 171 |
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.9), rgba(34, 211, 238, 0.9));
|
| 172 |
+
box-shadow: 0 0 10px rgba(34, 211, 238, 0.45);
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
.bars {
|
| 176 |
+
margin-top: 16px;
|
| 177 |
+
display: grid;
|
| 178 |
+
grid-template-columns: repeat(7, 1fr);
|
| 179 |
+
gap: 10px;
|
| 180 |
+
height: 190px;
|
| 181 |
+
align-items: end;
|
| 182 |
+
padding: 12px;
|
| 183 |
+
border-radius: 16px;
|
| 184 |
+
background: rgba(15, 23, 42, 0.35);
|
| 185 |
+
border: 1px solid rgba(148, 163, 184, 0.18);
|
| 186 |
+
}
|
| 187 |
|
| 188 |
.stats-legend {
|
| 189 |
margin-top: 10px;
|
|
|
|
| 238 |
height: 100%;
|
| 239 |
}
|
| 240 |
|
| 241 |
+
.bar-fill {
|
| 242 |
+
width: 100%;
|
| 243 |
+
border-radius: 10px 10px 6px 6px;
|
| 244 |
+
background: linear-gradient(180deg, var(--accent-color), var(--accent-secondary));
|
| 245 |
+
min-height: 10px;
|
| 246 |
+
transition: height 0.4s ease;
|
| 247 |
+
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3);
|
| 248 |
+
position: relative;
|
| 249 |
+
overflow: hidden;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.bar-fill::after {
|
| 253 |
+
content: '';
|
| 254 |
+
position: absolute;
|
| 255 |
+
inset: 0;
|
| 256 |
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.25), transparent 65%);
|
| 257 |
+
opacity: 0.55;
|
| 258 |
+
}
|
| 259 |
|
| 260 |
.bar-fill.share {
|
| 261 |
background: linear-gradient(180deg, #22c55e, #16a34a);
|
|
|
|
| 288 |
color: rgba(148, 163, 184, 0.7);
|
| 289 |
}
|
| 290 |
|
| 291 |
+
@media (max-width: 768px) {
|
| 292 |
+
.stats-shell {
|
| 293 |
+
padding: 100px 16px 60px;
|
| 294 |
+
}
|
| 295 |
|
| 296 |
.stats-title {
|
| 297 |
font-size: 1.35rem;
|
| 298 |
}
|
| 299 |
|
| 300 |
+
.bars {
|
| 301 |
+
height: 140px;
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
</style>
|
| 305 |
</head>
|
| 306 |
<body class="stats-page">
|
style.css
CHANGED
|
@@ -55,29 +55,29 @@ body {
|
|
| 55 |
will-change: transform;
|
| 56 |
}
|
| 57 |
|
| 58 |
-
@media (max-width: 768px) {
|
| 59 |
-
.status-meter {
|
| 60 |
-
width: 120px;
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
.prompt-link {
|
| 72 |
-
font-size: 0.75rem;
|
| 73 |
-
padding: 5px 10px;
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
.glass-panel {
|
| 77 |
-
backdrop-filter: blur(10px) saturate(120%);
|
| 78 |
-
-webkit-backdrop-filter: blur(10px) saturate(120%);
|
| 79 |
-
}
|
| 80 |
-
}
|
| 81 |
|
| 82 |
.glass-panel::before {
|
| 83 |
content: '';
|
|
@@ -183,7 +183,7 @@ header {
|
|
| 183 |
justify-content: space-between;
|
| 184 |
align-items: center;
|
| 185 |
flex-shrink: 0;
|
| 186 |
-
margin-top: 190px;
|
| 187 |
transition: margin-top var(--transition-normal);
|
| 188 |
}
|
| 189 |
|
|
@@ -314,7 +314,7 @@ header h2 {
|
|
| 314 |
|
| 315 |
|
| 316 |
body.has-preview header {
|
| 317 |
-
margin-top: 260px;
|
| 318 |
}
|
| 319 |
|
| 320 |
/* --- History Gallery --- */
|
|
@@ -1340,11 +1340,28 @@ body.has-preview header {
|
|
| 1340 |
}
|
| 1341 |
|
| 1342 |
/* --- Mobile Responsive Design --- */
|
| 1343 |
-
@media (max-width: 768px) {
|
| 1344 |
-
|
| 1345 |
-
|
| 1346 |
-
|
| 1347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1348 |
|
| 1349 |
.public-gallery-section {
|
| 1350 |
padding: 20px;
|
|
@@ -1461,7 +1478,7 @@ body.has-preview header {
|
|
| 1461 |
}
|
| 1462 |
|
| 1463 |
/* --- Tablet Responsive Design --- */
|
| 1464 |
-
@media (min-width: 768px) and (max-width: 1024px) {
|
| 1465 |
.grid-layout {
|
| 1466 |
grid-template-columns: repeat(2, 1fr);
|
| 1467 |
gap: 16px;
|
|
@@ -1483,13 +1500,13 @@ body.has-preview header {
|
|
| 1483 |
}
|
| 1484 |
|
| 1485 |
header {
|
| 1486 |
-
margin-top: 190px;
|
| 1487 |
}
|
| 1488 |
|
| 1489 |
body.has-preview header {
|
| 1490 |
-
margin-top: 360px;
|
| 1491 |
}
|
| 1492 |
-
}
|
| 1493 |
|
| 1494 |
/* --- Large Screen Optimization --- */
|
| 1495 |
@media (min-width: 1200px) {
|
|
|
|
| 55 |
will-change: transform;
|
| 56 |
}
|
| 57 |
|
| 58 |
+
@media (max-width: 768px) {
|
| 59 |
+
.status-meter {
|
| 60 |
+
width: 120px;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.tips-row {
|
| 64 |
+
padding: 0 16px 10px;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.tip-pill {
|
| 68 |
+
font-size: 11px;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.prompt-link {
|
| 72 |
+
font-size: 0.75rem;
|
| 73 |
+
padding: 5px 10px;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.glass-panel {
|
| 77 |
+
backdrop-filter: blur(10px) saturate(120%);
|
| 78 |
+
-webkit-backdrop-filter: blur(10px) saturate(120%);
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
|
| 82 |
.glass-panel::before {
|
| 83 |
content: '';
|
|
|
|
| 183 |
justify-content: space-between;
|
| 184 |
align-items: center;
|
| 185 |
flex-shrink: 0;
|
| 186 |
+
margin-top: var(--input-offset, 190px);
|
| 187 |
transition: margin-top var(--transition-normal);
|
| 188 |
}
|
| 189 |
|
|
|
|
| 314 |
|
| 315 |
|
| 316 |
body.has-preview header {
|
| 317 |
+
margin-top: var(--input-offset, 260px);
|
| 318 |
}
|
| 319 |
|
| 320 |
/* --- History Gallery --- */
|
|
|
|
| 1340 |
}
|
| 1341 |
|
| 1342 |
/* --- Mobile Responsive Design --- */
|
| 1343 |
+
@media (max-width: 768px) {
|
| 1344 |
+
body {
|
| 1345 |
+
height: auto;
|
| 1346 |
+
min-height: 100vh;
|
| 1347 |
+
overflow-y: auto;
|
| 1348 |
+
overflow-x: hidden;
|
| 1349 |
+
-webkit-overflow-scrolling: touch;
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
.app-container {
|
| 1353 |
+
height: auto;
|
| 1354 |
+
min-height: 100vh;
|
| 1355 |
+
}
|
| 1356 |
+
|
| 1357 |
+
.history-container {
|
| 1358 |
+
overflow: visible;
|
| 1359 |
+
}
|
| 1360 |
+
|
| 1361 |
+
.grid-layout {
|
| 1362 |
+
grid-template-columns: 1fr;
|
| 1363 |
+
gap: 12px;
|
| 1364 |
+
}
|
| 1365 |
|
| 1366 |
.public-gallery-section {
|
| 1367 |
padding: 20px;
|
|
|
|
| 1478 |
}
|
| 1479 |
|
| 1480 |
/* --- Tablet Responsive Design --- */
|
| 1481 |
+
@media (min-width: 768px) and (max-width: 1024px) {
|
| 1482 |
.grid-layout {
|
| 1483 |
grid-template-columns: repeat(2, 1fr);
|
| 1484 |
gap: 16px;
|
|
|
|
| 1500 |
}
|
| 1501 |
|
| 1502 |
header {
|
| 1503 |
+
margin-top: var(--input-offset, 190px);
|
| 1504 |
}
|
| 1505 |
|
| 1506 |
body.has-preview header {
|
| 1507 |
+
margin-top: var(--input-offset, 360px);
|
| 1508 |
}
|
| 1509 |
+
}
|
| 1510 |
|
| 1511 |
/* --- Large Screen Optimization --- */
|
| 1512 |
@media (min-width: 1200px) {
|