Update app_enhanced.py
Browse files- app_enhanced.py +11 -50
app_enhanced.py
CHANGED
|
@@ -91,6 +91,7 @@ INDEX_HTML = '''
|
|
| 91 |
.file-label:hover { background: #34495e; }
|
| 92 |
.submit-btn { width: 100%; padding: 15px; background: #e67e22; color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; transition: 0.2s; }
|
| 93 |
.submit-btn:hover { background: #d35400; }
|
|
|
|
| 94 |
.restore-btn { margin-top: 15px; background: #27ae60; color: white; padding: 10px; width: 100%; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; display: none; }
|
| 95 |
|
| 96 |
.loader { width: 120px; height: 20px; background: radial-gradient(circle 10px, #e67e22 100%, transparent 0); background-size: 20px 20px; animation: ball 1s infinite linear; margin: 20px auto; }
|
|
@@ -101,11 +102,11 @@ INDEX_HTML = '''
|
|
| 101 |
.comic-grid { display: grid; grid-template-columns: 285px 285px; grid-template-rows: 185px 185px; gap: 10px; width: 100%; height: 100%; padding: 10px; box-sizing: border-box; }
|
| 102 |
.panel { position: relative; overflow: hidden; border: 2px solid #000; background: #eee; cursor: pointer; }
|
| 103 |
.panel.selected { outline: 3px solid #2196F3; outline-offset: -3px; }
|
| 104 |
-
.panel img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.1s
|
| 105 |
.panel img.pannable { cursor: grab; }
|
| 106 |
.panel img.panning { cursor: grabbing; }
|
| 107 |
|
| 108 |
-
/* --- SPEECH BUBBLE (SHARK FIN) --- */
|
| 109 |
.speech-bubble {
|
| 110 |
position: absolute; display: flex; justify-content: center; align-items: center;
|
| 111 |
width: 150px; height: 80px; min-width: 60px; min-height: 40px; box-sizing: border-box;
|
|
@@ -116,65 +117,24 @@ INDEX_HTML = '''
|
|
| 116 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 117 |
.speech-bubble textarea { position: absolute; top:0; left:0; width:100%; height:100%; box-sizing:border-box; border:1px solid #4CAF50; background:rgba(255,255,255,0.9); text-align:center; padding:8px; z-index:100; resize:none; }
|
| 118 |
|
| 119 |
-
/* The specific CSS request from user */
|
| 120 |
.speech-bubble.speech {
|
| 121 |
-
--b: 3em;
|
| 122 |
-
--h: 1.8em; /* tail height */
|
| 123 |
-
--t: 0.6; /* thickness */
|
| 124 |
-
--p: var(--tail-pos, 50%); /* slider pos */
|
| 125 |
-
--r: 1.2em; /* radius */
|
| 126 |
--c: var(--bubble-fill-color, #4ECDC4);
|
| 127 |
-
|
| 128 |
-
background: var(--c);
|
| 129 |
-
color: var(--bubble-text-color, #fff);
|
| 130 |
-
padding: 1em;
|
| 131 |
-
position: absolute;
|
| 132 |
-
|
| 133 |
-
/* Exact border radius formula provided */
|
| 134 |
border-radius: var(--r) var(--r) min(var(--r), calc(100% - var(--p) - (1 - var(--t)) * var(--b) / 2)) min(var(--r), calc(var(--p) - (1 - var(--t)) * var(--b) / 2)) / var(--r);
|
| 135 |
}
|
| 136 |
-
|
| 137 |
-
/*
|
| 138 |
-
TAIL IMPLEMENTATION:
|
| 139 |
-
Using 'radial-gradient' background instead of mask.
|
| 140 |
-
This visually creates the EXACT concave shape requested but is 100% export-safe.
|
| 141 |
-
*/
|
| 142 |
.speech-bubble.speech:before {
|
| 143 |
content: ""; position: absolute; width: var(--b); height: var(--h);
|
| 144 |
-
/* Gradient mimics the mask curve */
|
| 145 |
background: radial-gradient(100% 100% at 100% 0, transparent calc(var(--t) * 100% - 1px), var(--c) calc(var(--t) * 100%));
|
| 146 |
border-bottom-left-radius: 100%; pointer-events: none; z-index: 1;
|
| 147 |
}
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
/* Bottom (Default) */
|
| 152 |
-
.speech-bubble.speech.tail-bottom:before {
|
| 153 |
-
top: 99%; left: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
/* Top */
|
| 157 |
-
.speech-bubble.speech.tail-top {
|
| 158 |
-
border-radius: min(var(--r), calc(var(--p) - (1 - var(--t)) * var(--b) / 2)) min(var(--r), calc(100% - var(--p) - (1 - var(--t)) * var(--b) / 2)) var(--r) var(--r) / var(--r);
|
| 159 |
-
}
|
| 160 |
-
.speech-bubble.speech.tail-top:before {
|
| 161 |
-
bottom: 99%; left: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 162 |
-
transform: scaleY(-1);
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
/* Left */
|
| 166 |
.speech-bubble.speech.tail-left { border-radius: var(--r); }
|
| 167 |
-
.speech-bubble.speech.tail-left:before {
|
| 168 |
-
right: 99%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 169 |
-
transform: rotate(90deg); transform-origin: top right;
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
/* Right */
|
| 173 |
.speech-bubble.speech.tail-right { border-radius: var(--r); }
|
| 174 |
-
.speech-bubble.speech.tail-right:before {
|
| 175 |
-
left: 99%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 176 |
-
transform: rotate(-90deg); transform-origin: top left;
|
| 177 |
-
}
|
| 178 |
|
| 179 |
.speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
|
| 180 |
.speech-bubble.thought::after { display:none; }
|
|
@@ -205,6 +165,7 @@ INDEX_HTML = '''
|
|
| 205 |
<span id="fn">No file selected</span>
|
| 206 |
<button class="submit-btn" onclick="upload()">Generate Comic</button>
|
| 207 |
<button id="restore-btn" class="restore-btn" onclick="restoreSession()">📂 Restore Unsaved Session</button>
|
|
|
|
| 208 |
<div class="loading-view" id="loading-view" style="display:none;">
|
| 209 |
<div class="loader"></div>
|
| 210 |
<p id="status-text">Starting...</p>
|
|
|
|
| 91 |
.file-label:hover { background: #34495e; }
|
| 92 |
.submit-btn { width: 100%; padding: 15px; background: #e67e22; color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; transition: 0.2s; }
|
| 93 |
.submit-btn:hover { background: #d35400; }
|
| 94 |
+
|
| 95 |
.restore-btn { margin-top: 15px; background: #27ae60; color: white; padding: 10px; width: 100%; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; display: none; }
|
| 96 |
|
| 97 |
.loader { width: 120px; height: 20px; background: radial-gradient(circle 10px, #e67e22 100%, transparent 0); background-size: 20px 20px; animation: ball 1s infinite linear; margin: 20px auto; }
|
|
|
|
| 102 |
.comic-grid { display: grid; grid-template-columns: 285px 285px; grid-template-rows: 185px 185px; gap: 10px; width: 100%; height: 100%; padding: 10px; box-sizing: border-box; }
|
| 103 |
.panel { position: relative; overflow: hidden; border: 2px solid #000; background: #eee; cursor: pointer; }
|
| 104 |
.panel.selected { outline: 3px solid #2196F3; outline-offset: -3px; }
|
| 105 |
+
.panel img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.1s; }
|
| 106 |
.panel img.pannable { cursor: grab; }
|
| 107 |
.panel img.panning { cursor: grabbing; }
|
| 108 |
|
| 109 |
+
/* --- SPEECH BUBBLE (EXACT SHARK FIN CSS) --- */
|
| 110 |
.speech-bubble {
|
| 111 |
position: absolute; display: flex; justify-content: center; align-items: center;
|
| 112 |
width: 150px; height: 80px; min-width: 60px; min-height: 40px; box-sizing: border-box;
|
|
|
|
| 117 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 118 |
.speech-bubble textarea { position: absolute; top:0; left:0; width:100%; height:100%; box-sizing:border-box; border:1px solid #4CAF50; background:rgba(255,255,255,0.9); text-align:center; padding:8px; z-index:100; resize:none; }
|
| 119 |
|
|
|
|
| 120 |
.speech-bubble.speech {
|
| 121 |
+
--b: 3em; --h: 1.8em; --t: 0.6; --p: var(--tail-pos, 50%); --r: 1.2em;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
--c: var(--bubble-fill-color, #4ECDC4);
|
| 123 |
+
background: var(--c); color: var(--bubble-text-color, #fff); padding: 1em; position: absolute;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
border-radius: var(--r) var(--r) min(var(--r), calc(100% - var(--p) - (1 - var(--t)) * var(--b) / 2)) min(var(--r), calc(var(--p) - (1 - var(--t)) * var(--b) / 2)) / var(--r);
|
| 125 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
.speech-bubble.speech:before {
|
| 127 |
content: ""; position: absolute; width: var(--b); height: var(--h);
|
|
|
|
| 128 |
background: radial-gradient(100% 100% at 100% 0, transparent calc(var(--t) * 100% - 1px), var(--c) calc(var(--t) * 100%));
|
| 129 |
border-bottom-left-radius: 100%; pointer-events: none; z-index: 1;
|
| 130 |
}
|
| 131 |
+
.speech-bubble.speech.tail-bottom:before { top: 99%; left: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b))); }
|
| 132 |
+
.speech-bubble.speech.tail-top { border-radius: min(var(--r), calc(var(--p) - (1 - var(--t)) * var(--b) / 2)) min(var(--r), calc(100% - var(--p) - (1 - var(--t)) * var(--b) / 2)) var(--r) var(--r) / var(--r); }
|
| 133 |
+
.speech-bubble.speech.tail-top:before { bottom: 99%; left: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b))); transform: scaleY(-1); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
.speech-bubble.speech.tail-left { border-radius: var(--r); }
|
| 135 |
+
.speech-bubble.speech.tail-left:before { right: 99%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b))); transform: rotate(90deg); transform-origin: top right; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
.speech-bubble.speech.tail-right { border-radius: var(--r); }
|
| 137 |
+
.speech-bubble.speech.tail-right:before { left: 99%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b))); transform: rotate(-90deg); transform-origin: top left; }
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
.speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
|
| 140 |
.speech-bubble.thought::after { display:none; }
|
|
|
|
| 165 |
<span id="fn">No file selected</span>
|
| 166 |
<button class="submit-btn" onclick="upload()">Generate Comic</button>
|
| 167 |
<button id="restore-btn" class="restore-btn" onclick="restoreSession()">📂 Restore Unsaved Session</button>
|
| 168 |
+
|
| 169 |
<div class="loading-view" id="loading-view" style="display:none;">
|
| 170 |
<div class="loader"></div>
|
| 171 |
<p id="status-text">Starting...</p>
|