jhh6576 commited on
Commit
0b4627b
·
verified ·
1 Parent(s): dc67e4a

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. 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 Artistic Speech Bubble CSS >>> */
547
  .speech-bubble.speech {
548
  background: var(--bubble-fill-color, white);
549
  color: var(--bubble-text-color, #333);
550
- border-radius: 25px; /* Softer radius */
551
  padding: 0;
552
  border: none;
553
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
554
  }
555
 
556
  .speech-bubble.speech::after {
557
  content: '';
558
  position: absolute;
559
- width: 0;
560
- height: 0;
561
- }
562
-
563
- /* 4-WAY TAIL ROTATION CLASSES */
564
- .speech-bubble.speech.tail-bottom::after {
565
- border-left: 24px solid transparent;
566
- border-right: 12px solid transparent;
567
- border-top: 12px solid var(--bubble-fill-color, white);
568
- border-bottom: 20px solid transparent;
569
- left: 32px;
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 controls are now simpler, just one button >>> -->
669
  <div class="control-group" id="tail-controls" style="display: none;">
670
  <label>Tail Controls</label>
671
- <button onclick="rotateBubbleTail()" class="secondary-button">🔄 Rotate Tail</button>
 
 
 
 
 
 
 
 
 
 
 
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
- const bubbleType = currentlySelectedBubble.dataset.type;
844
-
845
- if (bubbleType === 'speech') {
846
- const positions = ['tail-bottom', 'tail-right', 'tail-top', 'tail-left'];
847
- let currentPos = parseInt(currentlySelectedBubble.dataset.tailPos || 0);
848
- currentlySelectedBubble.classList.remove(positions[currentPos]);
849
- let nextPos = (currentPos + 1) % positions.length;
850
- currentlySelectedBubble.classList.add(positions[nextPos]);
851
- currentlySelectedBubble.dataset.tailPos = nextPos;
852
- } else if (bubbleType === 'thought') {
853
- const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
854
- const isFlippedV = currentlySelectedBubble.classList.contains('flipped-vertical');
855
- if (!isFlippedH && !isFlippedV) { currentlySelectedBubble.classList.add('flipped'); }
856
- else if (isFlippedH && !isFlippedV) { currentlySelectedBubble.classList.add('flipped-vertical'); }
857
- else if (isFlippedH && isFlippedV) { currentlySelectedBubble.classList.remove('flipped'); }
858
- else { currentlySelectedBubble.classList.remove('flipped-vertical'); }
859
- } else {
860
- alert("Tail rotation is only available for Speech and Thought bubbles.");
 
 
 
 
 
 
 
 
 
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
- // <<< MODIFICATION: Show tail controls for both Speech and Thought bubbles >>>
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
  }