Update app_enhanced.py
Browse files- app_enhanced.py +85 -72
app_enhanced.py
CHANGED
|
@@ -543,55 +543,30 @@ class EnhancedComicGenerator:
|
|
| 543 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 544 |
.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.95); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
|
| 545 |
|
| 546 |
-
/* <<< MODIFICATION START: New
|
| 547 |
.speech-bubble.speech {
|
| 548 |
background: var(--bubble-fill-color, white);
|
| 549 |
color: var(--bubble-text-color, #333);
|
| 550 |
-
border-radius: 25px;
|
| 551 |
padding: 0;
|
| 552 |
border: none;
|
| 553 |
-
box-shadow: 0
|
| 554 |
}
|
| 555 |
|
| 556 |
.speech-bubble.speech::after {
|
| 557 |
content: '';
|
| 558 |
position: absolute;
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
bottom: -24px;
|
| 571 |
-
}
|
| 572 |
-
.speech-bubble.speech.tail-top::after {
|
| 573 |
-
border-left: 12px solid transparent;
|
| 574 |
-
border-right: 24px solid transparent;
|
| 575 |
-
border-bottom: 12px solid var(--bubble-fill-color, white);
|
| 576 |
-
border-top: 20px solid transparent;
|
| 577 |
-
right: 32px;
|
| 578 |
-
top: -24px;
|
| 579 |
-
}
|
| 580 |
-
.speech-bubble.speech.tail-right::after {
|
| 581 |
-
border-top: 24px solid transparent;
|
| 582 |
-
border-bottom: 12px solid transparent;
|
| 583 |
-
border-left: 12px solid var(--bubble-fill-color, white);
|
| 584 |
-
border-right: 20px solid transparent;
|
| 585 |
-
top: 18px;
|
| 586 |
-
right: -24px;
|
| 587 |
-
}
|
| 588 |
-
.speech-bubble.speech.tail-left::after {
|
| 589 |
-
border-top: 12px solid transparent;
|
| 590 |
-
border-bottom: 24px solid transparent;
|
| 591 |
-
border-right: 12px solid var(--bubble-fill-color, white);
|
| 592 |
-
border-left: 20px solid transparent;
|
| 593 |
-
top: 28px;
|
| 594 |
-
left: -24px;
|
| 595 |
}
|
| 596 |
/* <<< MODIFICATION END >>> */
|
| 597 |
|
|
@@ -600,7 +575,6 @@ class EnhancedComicGenerator:
|
|
| 600 |
.speech-bubble.narration { background: #FAFAFA; border: 2px solid #BDBDBD; color: #424242; border-radius: 3px; }
|
| 601 |
.speech-bubble.idea { background: linear-gradient(180deg,#FFFDD0 0%, #FFF8B5 100%); border: 2px solid #FFA500; color: #6a4b00; border-radius: 40% 60% 40% 60% / 60% 40% 60% 40%; }
|
| 602 |
|
| 603 |
-
/* Rotation logic for Thought Bubble dots */
|
| 604 |
.speech-bubble.thought::after { display: none; }
|
| 605 |
.thought-dot { position: absolute; background-color: white; border: 2px solid #555; border-radius: 50%; z-index: -1; }
|
| 606 |
.thought-dot-1 { width: 20px; height: 20px; bottom: -20px; left: 15px; }
|
|
@@ -631,6 +605,8 @@ class EnhancedComicGenerator:
|
|
| 631 |
.color-picker-grid div { text-align: center; }
|
| 632 |
.color-picker-grid label { font-size: 11px; }
|
| 633 |
.color-picker-grid input[type="color"] { height: 25px; padding: 2px; }
|
|
|
|
|
|
|
| 634 |
</style>
|
| 635 |
</head>
|
| 636 |
<body>
|
|
@@ -665,10 +641,21 @@ class EnhancedComicGenerator:
|
|
| 665 |
<button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
|
| 666 |
<button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
|
| 667 |
</div>
|
| 668 |
-
<!-- <<< MODIFICATION: Tail
|
| 669 |
<div class="control-group" id="tail-controls" style="display: none;">
|
| 670 |
<label>Tail Controls</label>
|
| 671 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
</div>
|
| 673 |
<div class="control-group">
|
| 674 |
<label>Panel Tools (Select Panel):</label>
|
|
@@ -761,6 +748,21 @@ class EnhancedComicGenerator:
|
|
| 761 |
currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
|
| 762 |
}
|
| 763 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 764 |
|
| 765 |
document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
|
| 766 |
document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
|
|
@@ -789,6 +791,8 @@ class EnhancedComicGenerator:
|
|
| 789 |
bubbleDiv.style.left = data.left;
|
| 790 |
bubbleDiv.style.top = data.top;
|
| 791 |
applyBubbleType(bubbleDiv, 'speech');
|
|
|
|
|
|
|
| 792 |
return bubbleDiv;
|
| 793 |
}
|
| 794 |
|
|
@@ -812,10 +816,6 @@ class EnhancedComicGenerator:
|
|
| 812 |
bubble.classList.add(type);
|
| 813 |
bubble.dataset.type = type;
|
| 814 |
|
| 815 |
-
if (type === 'speech') {
|
| 816 |
-
bubble.classList.add('tail-bottom');
|
| 817 |
-
bubble.dataset.tailPos = '0';
|
| 818 |
-
}
|
| 819 |
if (type === 'thought') {
|
| 820 |
for (let i = 1; i <= 2; i++) {
|
| 821 |
const dot = document.createElement('div');
|
|
@@ -835,30 +835,39 @@ class EnhancedComicGenerator:
|
|
| 835 |
if (!currentlySelectedBubble) return;
|
| 836 |
currentlySelectedBubble.style.fontFamily = font;
|
| 837 |
}
|
| 838 |
-
|
| 839 |
-
// <<< MODIFICATION: rotateBubbleTail now handles both Speech and Thought bubbles >>>
|
| 840 |
-
function rotateBubbleTail() {
|
| 841 |
-
if (!currentlySelectedBubble) return;
|
| 842 |
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
if (
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
}
|
| 863 |
|
| 864 |
function selectPanel(panel) {
|
|
@@ -894,11 +903,15 @@ class EnhancedComicGenerator:
|
|
| 894 |
|
| 895 |
document.getElementById('zoom-slider').disabled = true;
|
| 896 |
bubbleControls.forEach(id => document.getElementById(id).disabled = false);
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
const bubbleType = currentlySelectedBubble.dataset.type;
|
| 900 |
-
if (bubbleType === 'speech' || bubbleType === 'thought') {
|
| 901 |
tailControls.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 902 |
} else {
|
| 903 |
tailControls.style.display = 'none';
|
| 904 |
}
|
|
|
|
| 543 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 544 |
.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.95); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
|
| 545 |
|
| 546 |
+
/* <<< MODIFICATION START: New Advanced CSS for Speech Bubble with Sliders >>> */
|
| 547 |
.speech-bubble.speech {
|
| 548 |
background: var(--bubble-fill-color, white);
|
| 549 |
color: var(--bubble-text-color, #333);
|
| 550 |
+
border-radius: 25px;
|
| 551 |
padding: 0;
|
| 552 |
border: none;
|
| 553 |
+
box-shadow: 0 1px 4px rgba(0,0,0,0.25);
|
| 554 |
}
|
| 555 |
|
| 556 |
.speech-bubble.speech::after {
|
| 557 |
content: '';
|
| 558 |
position: absolute;
|
| 559 |
+
background: var(--bubble-fill-color, white);
|
| 560 |
+
|
| 561 |
+
/* Use CSS variables for dynamic control */
|
| 562 |
+
width: var(--tail-size, 25px);
|
| 563 |
+
height: var(--tail-size, 25px);
|
| 564 |
+
top: var(--tail-top);
|
| 565 |
+
left: var(--tail-left);
|
| 566 |
+
|
| 567 |
+
/* The magic happens here */
|
| 568 |
+
transform: var(--tail-rotation, rotate(45deg));
|
| 569 |
+
clip-path: polygon(var(--tail-shape, 50%) 0, 100% 100%, 0 100%);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
}
|
| 571 |
/* <<< MODIFICATION END >>> */
|
| 572 |
|
|
|
|
| 575 |
.speech-bubble.narration { background: #FAFAFA; border: 2px solid #BDBDBD; color: #424242; border-radius: 3px; }
|
| 576 |
.speech-bubble.idea { background: linear-gradient(180deg,#FFFDD0 0%, #FFF8B5 100%); border: 2px solid #FFA500; color: #6a4b00; border-radius: 40% 60% 40% 60% / 60% 40% 60% 40%; }
|
| 577 |
|
|
|
|
| 578 |
.speech-bubble.thought::after { display: none; }
|
| 579 |
.thought-dot { position: absolute; background-color: white; border: 2px solid #555; border-radius: 50%; z-index: -1; }
|
| 580 |
.thought-dot-1 { width: 20px; height: 20px; bottom: -20px; left: 15px; }
|
|
|
|
| 605 |
.color-picker-grid div { text-align: center; }
|
| 606 |
.color-picker-grid label { font-size: 11px; }
|
| 607 |
.color-picker-grid input[type="color"] { height: 25px; padding: 2px; }
|
| 608 |
+
.slider-group { margin-top: 5px; }
|
| 609 |
+
.slider-group label { font-size: 11px; margin-right: 5px; }
|
| 610 |
</style>
|
| 611 |
</head>
|
| 612 |
<body>
|
|
|
|
| 641 |
<button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
|
| 642 |
<button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
|
| 643 |
</div>
|
| 644 |
+
<!-- <<< MODIFICATION: New Tail Controls Section >>> -->
|
| 645 |
<div class="control-group" id="tail-controls" style="display: none;">
|
| 646 |
<label>Tail Controls</label>
|
| 647 |
+
<div class="slider-group">
|
| 648 |
+
<label for="tail-pos-slider">Position</label>
|
| 649 |
+
<input type="range" id="tail-pos-slider" min="0" max="100" value="12" step="1">
|
| 650 |
+
</div>
|
| 651 |
+
<div class="slider-group">
|
| 652 |
+
<label for="tail-size-slider">Size</label>
|
| 653 |
+
<input type="range" id="tail-size-slider" min="15" max="50" value="25" step="1">
|
| 654 |
+
</div>
|
| 655 |
+
<div class="slider-group">
|
| 656 |
+
<label for="tail-shape-slider">Shape</label>
|
| 657 |
+
<input type="range" id="tail-shape-slider" min="0" max="100" value="50" step="1">
|
| 658 |
+
</div>
|
| 659 |
</div>
|
| 660 |
<div class="control-group">
|
| 661 |
<label>Panel Tools (Select Panel):</label>
|
|
|
|
| 748 |
currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
|
| 749 |
}
|
| 750 |
});
|
| 751 |
+
|
| 752 |
+
// <<< MODIFICATION: Event listeners for new tail sliders >>>
|
| 753 |
+
document.getElementById('tail-pos-slider').addEventListener('input', (e) => {
|
| 754 |
+
if (currentlySelectedBubble) updateTailPosition(e.target.value);
|
| 755 |
+
});
|
| 756 |
+
document.getElementById('tail-size-slider').addEventListener('input', (e) => {
|
| 757 |
+
if (currentlySelectedBubble) {
|
| 758 |
+
currentlySelectedBubble.style.setProperty('--tail-size', e.target.value + 'px');
|
| 759 |
+
}
|
| 760 |
+
});
|
| 761 |
+
document.getElementById('tail-shape-slider').addEventListener('input', (e) => {
|
| 762 |
+
if (currentlySelectedBubble) {
|
| 763 |
+
currentlySelectedBubble.style.setProperty('--tail-shape', e.target.value + '%');
|
| 764 |
+
}
|
| 765 |
+
});
|
| 766 |
|
| 767 |
document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
|
| 768 |
document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
|
|
|
|
| 791 |
bubbleDiv.style.left = data.left;
|
| 792 |
bubbleDiv.style.top = data.top;
|
| 793 |
applyBubbleType(bubbleDiv, 'speech');
|
| 794 |
+
// Set initial tail position on creation
|
| 795 |
+
updateTailPosition(12, bubbleDiv); // 12 is bottom-left quadrant
|
| 796 |
return bubbleDiv;
|
| 797 |
}
|
| 798 |
|
|
|
|
| 816 |
bubble.classList.add(type);
|
| 817 |
bubble.dataset.type = type;
|
| 818 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
if (type === 'thought') {
|
| 820 |
for (let i = 1; i <= 2; i++) {
|
| 821 |
const dot = document.createElement('div');
|
|
|
|
| 835 |
if (!currentlySelectedBubble) return;
|
| 836 |
currentlySelectedBubble.style.fontFamily = font;
|
| 837 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
|
| 839 |
+
// <<< MODIFICATION: Function to handle circular tail positioning >>>
|
| 840 |
+
function updateTailPosition(value, bubble = currentlySelectedBubble) {
|
| 841 |
+
if (!bubble) return;
|
| 842 |
+
bubble.dataset.tailCircularPos = value;
|
| 843 |
+
const val = parseInt(value);
|
| 844 |
+
let top, left, rotation;
|
| 845 |
+
|
| 846 |
+
if (val >= 0 && val < 25) { // Bottom Edge
|
| 847 |
+
const mappedVal = (val / 25) * 100;
|
| 848 |
+
top = '100%';
|
| 849 |
+
left = `${mappedVal}%`;
|
| 850 |
+
rotation = '45deg';
|
| 851 |
+
} else if (val >= 25 && val < 50) { // Right Edge
|
| 852 |
+
const mappedVal = 100 - ((val - 25) / 25) * 100;
|
| 853 |
+
top = `${mappedVal}%`;
|
| 854 |
+
left = '100%';
|
| 855 |
+
rotation = '135deg';
|
| 856 |
+
} else if (val >= 50 && val < 75) { // Top Edge
|
| 857 |
+
const mappedVal = 100 - ((val - 50) / 25) * 100;
|
| 858 |
+
top = '0%';
|
| 859 |
+
left = `${mappedVal}%`;
|
| 860 |
+
rotation = '225deg';
|
| 861 |
+
} else { // Left Edge
|
| 862 |
+
const mappedVal = ((val - 75) / 25) * 100;
|
| 863 |
+
top = `${mappedVal}%`;
|
| 864 |
+
left = '0%';
|
| 865 |
+
rotation = '315deg';
|
| 866 |
}
|
| 867 |
+
|
| 868 |
+
bubble.style.setProperty('--tail-top', top);
|
| 869 |
+
bubble.style.setProperty('--tail-left', left);
|
| 870 |
+
bubble.style.setProperty('--tail-rotation', `rotate(${rotation})`);
|
| 871 |
}
|
| 872 |
|
| 873 |
function selectPanel(panel) {
|
|
|
|
| 903 |
|
| 904 |
document.getElementById('zoom-slider').disabled = true;
|
| 905 |
bubbleControls.forEach(id => document.getElementById(id).disabled = false);
|
| 906 |
+
|
| 907 |
+
if (currentlySelectedBubble.dataset.type === 'speech') {
|
|
|
|
|
|
|
| 908 |
tailControls.style.display = 'block';
|
| 909 |
+
const currentSize = parseInt(styles.getPropertyValue('--tail-size')) || 25;
|
| 910 |
+
document.getElementById('tail-size-slider').value = currentSize;
|
| 911 |
+
const currentShape = parseInt(styles.getPropertyValue('--tail-shape')) || 50;
|
| 912 |
+
document.getElementById('tail-shape-slider').value = currentShape;
|
| 913 |
+
const currentPos = currentlySelectedBubble.dataset.tailCircularPos || 12;
|
| 914 |
+
document.getElementById('tail-pos-slider').value = currentPos;
|
| 915 |
} else {
|
| 916 |
tailControls.style.display = 'none';
|
| 917 |
}
|