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

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +86 -135
app_enhanced.py CHANGED
@@ -539,83 +539,77 @@ class EnhancedComicGenerator:
539
  .panel img.pannable { cursor: grab; }
540
  .panel img.panning { cursor: grabbing; }
541
  .speech-bubble { position: absolute; display: flex; justify-content: center; align-items: center; width: 150px; height: 80px; min-width: 50px; min-height: 30px; box-sizing: border-box; z-index: 10; cursor: move; overflow: visible; font-size: 13px; font-weight: bold; text-align: center; font-family: 'Comic Neue', cursive; }
542
- .bubble-text { padding: 0.8em; word-wrap: break-word; width: 100%; height: 100%; box-sizing: border-box; }
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 >>> */
547
  .speech-bubble.speech {
548
- --r: 1em; /* radius */
549
- --b: var(--tail-size, 2.5em); /* tail base size */
550
- --c1: var(--bubble-border-color, #4A4A4A);
551
- --c2: var(--bubble-fill-color, white);
552
  padding: 0;
553
  border: none;
554
- color: var(--bubble-text-color, #333);
555
- background: none;
556
-
557
- /* Dynamic clip-path for tail cutout */
558
- clip-path: polygon(
559
- var(--r) 0, calc(100% - var(--r)) 0, 100% var(--r), 100% calc(100% - var(--r)),
560
- calc(100% - var(--r)) 100%,
561
- calc(clamp(var(--r), var(--tail-pos, 50%), calc(100% - var(--r))) + var(--b)/2) 100%,
562
- clamp(var(--r), var(--tail-pos, 50%), calc(100% - var(--r))) calc(100% + var(--b)/2),
563
- calc(clamp(var(--r), var(--tail-pos, 50%), calc(100% - var(--r))) - var(--b)/2) 100%,
564
- var(--r) 100%, 0 calc(100% - var(--r)), 0 var(--r)
565
- );
566
-
567
- border-image: conic-gradient(var(--c1) 0 0) fill 1/2px; /* Inner border */
568
  }
569
- .speech-bubble.speech::before {
 
570
  content: '';
571
  position: absolute;
572
- inset: 0;
573
- border-radius: var(--r);
574
- background: var(--c2); /* Main fill color */
575
- z-index: -1;
576
  }
577
 
578
- /* --- 4-WAY ROTATION --- */
579
- .speech-bubble.speech.tail-top { transform: rotate(180deg); }
580
- .speech-bubble.speech.tail-left { transform: rotate(90deg); }
581
- .speech-bubble.speech.tail-right { transform: rotate(-90deg); }
582
- /* The text needs to be counter-rotated to stay upright */
583
- .speech-bubble.speech.tail-top .bubble-text,
584
- .speech-bubble.speech.tail-left .bubble-text,
585
- .speech-bubble.speech.tail-right .bubble-text {
586
- transform: rotate(var(--un-rotate, 0deg));
587
  }
588
-
589
- /* --- Other Bubble Types --- */
590
- .speech-bubble.thought {
591
- background: var(--bubble-fill-color, white);
592
- color: var(--bubble-text-color, #333);
593
- border: 2px dashed var(--bubble-border-color, #555);
594
- border-radius: 50%;
595
- padding: 0;
596
  }
597
- .thought-dot {
598
- position: absolute;
599
- background-color: var(--bubble-fill-color, white);
600
- border: 2px dashed var(--bubble-border-color, #555);
601
- border-radius: 50%;
602
- z-index: -1;
 
 
 
 
 
 
 
 
 
603
  }
604
- /* Tail controls for Thought Bubble */
605
- .speech-bubble.thought.tail-bottom .thought-dot-1 { bottom: -30%; left: var(--tail-pos, 50%); width: var(--tail-size, 20px); height: var(--tail-size, 20px); transform: translateX(-50%); }
606
- .speech-bubble.thought.tail-bottom .thought-dot-2 { bottom: -50%; left: calc(var(--tail-pos, 50%) - 15%); width: calc(var(--tail-size, 20px) * 0.6); height: calc(var(--tail-size, 20px) * 0.6); transform: translateX(-50%); }
607
- .speech-bubble.thought.tail-top .thought-dot-1 { top: -30%; left: var(--tail-pos, 50%); width: var(--tail-size, 20px); height: var(--tail-size, 20px); transform: translateX(-50%); }
608
- .speech-bubble.thought.tail-top .thought-dot-2 { top: -50%; left: calc(var(--tail-pos, 50%) - 15%); width: calc(var(--tail-size, 20px) * 0.6); height: calc(var(--tail-size, 20px) * 0.6); transform: translateX(-50%); }
609
- .speech-bubble.thought.tail-right .thought-dot-1 { right: -30%; top: var(--tail-pos, 50%); width: var(--tail-size, 20px); height: var(--tail-size, 20px); transform: translateY(-50%); }
610
- .speech-bubble.thought.tail-right .thought-dot-2 { right: -50%; top: calc(var(--tail-pos, 50%) - 15%); width: calc(var(--tail-size, 20px) * 0.6); height: calc(var(--tail-size, 20px) * 0.6); transform: translateY(-50%); }
611
- .speech-bubble.thought.tail-left .thought-dot-1 { left: -30%; top: var(--tail-pos, 50%); width: var(--tail-size, 20px); height: var(--tail-size, 20px); transform: translateY(-50%); }
612
- .speech-bubble.thought.tail-left .thought-dot-2 { left: -50%; top: calc(var(--tail-pos, 50%) - 15%); width: calc(var(--tail-size, 20px) * 0.6); height: calc(var(--tail-size, 20px) * 0.6); transform: translateY(-50%); }
613
  /* <<< MODIFICATION END >>> */
614
 
 
615
  .speech-bubble.reaction { background: #FFD700; border: 3px solid #E53935; color: #D32F2F; font-weight: 900; text-transform: uppercase; width: 180px; clip-path: polygon(0% 25%, 17% 21%, 17% 0%, 31% 16%, 50% 4%, 69% 16%, 83% 0%, 83% 21%, 100% 25%, 85% 45%, 95% 62%, 82% 79%, 100% 97%, 79% 89%, 60% 98%, 46% 82%, 27% 95%, 15% 78%, 5% 62%, 15% 45%); }
616
  .speech-bubble.narration { background: #FAFAFA; border: 2px solid #BDBDBD; color: #424242; border-radius: 3px; }
617
  .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%; }
618
 
 
 
 
 
 
 
 
 
 
 
619
  .resize-handle { position: absolute; width: 10px; height: 10px; background: #2196F3; border: 1px solid white; border-radius: 50%; display: none; z-index: 11; }
620
  .speech-bubble.selected .resize-handle { display: block; }
621
  .resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
@@ -637,8 +631,6 @@ class EnhancedComicGenerator:
637
  .color-picker-grid div { text-align: center; }
638
  .color-picker-grid label { font-size: 11px; }
639
  .color-picker-grid input[type="color"] { height: 25px; padding: 2px; }
640
- .slider-group { margin-top: 5px; }
641
- .slider-group label { font-size: 11px; margin-right: 5px; }
642
  </style>
643
  </head>
644
  <body>
@@ -673,17 +665,10 @@ class EnhancedComicGenerator:
673
  <button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
674
  <button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
675
  </div>
 
676
  <div class="control-group" id="tail-controls" style="display: none;">
677
  <label>Tail Controls</label>
678
- <div class="slider-group">
679
- <label for="tail-pos-slider">Position</label>
680
- <input type="range" id="tail-pos-slider" min="10" max="90" value="50" step="1">
681
- </div>
682
- <div class="slider-group">
683
- <label for="tail-size-slider">Size</label>
684
- <input type="range" id="tail-size-slider" min="10" max="40" value="25" step="1">
685
- </div>
686
- <button onclick="flipBubbleTail()" class="secondary-button">🔄 Flip Edge</button>
687
  </div>
688
  <div class="control-group">
689
  <label>Panel Tools (Select Panel):</label>
@@ -776,18 +761,6 @@ class EnhancedComicGenerator:
776
  currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
777
  }
778
  });
779
-
780
- document.getElementById('tail-pos-slider').addEventListener('input', (e) => {
781
- if (currentlySelectedBubble) {
782
- currentlySelectedBubble.style.setProperty('--tail-pos', e.target.value + '%');
783
- }
784
- });
785
- document.getElementById('tail-size-slider').addEventListener('input', (e) => {
786
- if (currentlySelectedBubble) {
787
- // For speech bubble, it's the base width 'b'. For thought, it's the dot size.
788
- currentlySelectedBubble.style.setProperty('--tail-size', e.target.value + 'px');
789
- }
790
- });
791
 
792
  document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
793
  document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
@@ -831,7 +804,7 @@ class EnhancedComicGenerator:
831
  }
832
 
833
  function applyBubbleType(bubble, type) {
834
- bubble.innerHTML = bubble.querySelector('.bubble-text').outerHTML; // Clear old pseudo-elements
835
  let classesToKeep = 'speech-bubble';
836
  if (bubble.classList.contains('selected')) classesToKeep += ' selected';
837
 
@@ -841,19 +814,14 @@ class EnhancedComicGenerator:
841
 
842
  if (type === 'speech') {
843
  bubble.classList.add('tail-bottom');
844
- bubble.dataset.tailEdge = 'bottom';
845
- bubble.style.setProperty('--un-rotate', '0deg'); // Reset rotation
846
  }
847
  if (type === 'thought') {
848
- bubble.classList.add('tail-bottom');
849
- bubble.dataset.tailEdge = 'bottom';
850
- // Add the dots for the thought bubble tail
851
- const dot1 = document.createElement('div');
852
- dot1.className = 'thought-dot thought-dot-1';
853
- const dot2 = document.createElement('div');
854
- dot2.className = 'thought-dot thought-dot-2';
855
- bubble.appendChild(dot1);
856
- bubble.appendChild(dot2);
857
  }
858
  }
859
 
@@ -868,28 +836,28 @@ class EnhancedComicGenerator:
868
  currentlySelectedBubble.style.fontFamily = font;
869
  }
870
 
871
- function flipBubbleTail() {
 
872
  if (!currentlySelectedBubble) return;
873
- const type = currentlySelectedBubble.dataset.type;
874
- if (type !== 'speech' && type !== 'thought') return;
875
 
876
- const edges = ['bottom', 'right', 'top', 'left'];
877
- const currentEdge = currentlySelectedBubble.dataset.tailEdge || 'bottom';
878
- let currentEdgeIndex = edges.indexOf(currentEdge);
879
-
880
- currentlySelectedBubble.classList.remove('tail-' + edges[currentEdgeIndex]);
881
- let nextEdgeIndex = (currentEdgeIndex + 1) % edges.length;
882
-
883
- currentlySelectedBubble.classList.add('tail-' + edges[nextEdgeIndex]);
884
- currentlySelectedBubble.dataset.tailEdge = edges[nextEdgeIndex];
885
-
886
- // Handle counter-rotation for speech bubble text
887
- if (type === 'speech') {
888
- let rotation = '0deg';
889
- if (edges[nextEdgeIndex] === 'top') rotation = '180deg';
890
- if (edges[nextEdgeIndex] === 'left') rotation = '-90deg';
891
- if (edges[nextEdgeIndex] === 'right') rotation = '90deg';
892
- currentlySelectedBubble.style.setProperty('--un-rotate', rotation);
 
893
  }
894
  }
895
 
@@ -927,13 +895,10 @@ class EnhancedComicGenerator:
927
  document.getElementById('zoom-slider').disabled = true;
928
  bubbleControls.forEach(id => document.getElementById(id).disabled = false);
929
 
930
- const type = currentlySelectedBubble.dataset.type;
931
- if (type === 'speech' || type === 'thought') {
 
932
  tailControls.style.display = 'block';
933
- const currentSize = parseFloat(styles.getPropertyValue('--tail-size')) || (type === 'speech' ? 40 : 20) ; // 40px base, 20px dot
934
- document.getElementById('tail-size-slider').value = currentSize;
935
- const currentPos = parseInt(styles.getPropertyValue('--tail-pos')) || 50;
936
- document.getElementById('tail-pos-slider').value = currentPos;
937
  } else {
938
  tailControls.style.display = 'none';
939
  }
@@ -976,25 +941,11 @@ class EnhancedComicGenerator:
976
  offset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
977
  }
978
 
979
- // <<< MODIFICATION: Drag function now constrains movement to fix edge bug >>>
980
  function drag(e) {
981
  if (!draggedBubble) return;
982
- const parentPanel = draggedBubble.parentElement;
983
- const parentRect = parentPanel.getBoundingClientRect();
984
-
985
- let x = e.clientX - parentRect.left - offset.x;
986
- let y = e.clientY - parentRect.top - offset.y;
987
-
988
- // Constrain X position
989
- x = Math.max(0, x); // Min-x
990
- x = Math.min(parentRect.width - draggedBubble.offsetWidth, x); // Max-x
991
-
992
- // Constrain Y position
993
- y = Math.max(0, y); // Min-y
994
- y = Math.min(parentRect.height - draggedBubble.offsetHeight, y); // Max-y
995
-
996
- draggedBubble.style.left = `${x}px`;
997
- draggedBubble.style.top = `${y}px`;
998
  }
999
 
1000
  function stopDrag() { draggedBubble = null; }
 
539
  .panel img.pannable { cursor: grab; }
540
  .panel img.panning { cursor: grabbing; }
541
  .speech-bubble { position: absolute; display: flex; justify-content: center; align-items: center; width: 150px; height: 80px; min-width: 50px; min-height: 30px; box-sizing: border-box; z-index: 10; cursor: move; overflow: visible; font-size: 13px; font-weight: bold; text-align: center; font-family: 'Comic Neue', cursive; }
542
+ .bubble-text { padding: 0.8em; word-wrap: break-word; }
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
 
598
+ .speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
599
  .speech-bubble.reaction { background: #FFD700; border: 3px solid #E53935; color: #D32F2F; font-weight: 900; text-transform: uppercase; width: 180px; clip-path: polygon(0% 25%, 17% 21%, 17% 0%, 31% 16%, 50% 4%, 69% 16%, 83% 0%, 83% 21%, 100% 25%, 85% 45%, 95% 62%, 82% 79%, 100% 97%, 79% 89%, 60% 98%, 46% 82%, 27% 95%, 15% 78%, 5% 62%, 15% 45%); }
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; }
607
+ .thought-dot-2 { width: 12px; height: 12px; bottom: -32px; left: 5px; }
608
+ .speech-bubble.flipped .thought-dot-1 { left: auto; right: 15px; }
609
+ .speech-bubble.flipped .thought-dot-2 { left: auto; right: 5px; }
610
+ .speech-bubble.flipped-vertical .thought-dot-1 { bottom: auto; top: -20px; }
611
+ .speech-bubble.flipped-vertical .thought-dot-2 { bottom: auto; top: -32px; }
612
+
613
  .resize-handle { position: absolute; width: 10px; height: 10px; background: #2196F3; border: 1px solid white; border-radius: 50%; display: none; z-index: 11; }
614
  .speech-bubble.selected .resize-handle { display: block; }
615
  .resize-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
 
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
  <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
  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);});
 
804
  }
805
 
806
  function applyBubbleType(bubble, type) {
807
+ bubble.querySelectorAll('.thought-dot').forEach(el => el.remove());
808
  let classesToKeep = 'speech-bubble';
809
  if (bubble.classList.contains('selected')) classesToKeep += ' selected';
810
 
 
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');
822
+ dot.className = `thought-dot thought-dot-${i}`;
823
+ bubble.appendChild(dot);
824
+ }
 
 
 
 
825
  }
826
  }
827
 
 
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
 
 
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
  }
 
941
  offset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
942
  }
943
 
 
944
  function drag(e) {
945
  if (!draggedBubble) return;
946
+ const parentRect = draggedBubble.parentElement.getBoundingClientRect();
947
+ draggedBubble.style.left = `${e.clientX - parentRect.left - offset.x}px`;
948
+ draggedBubble.style.top = `${e.clientY - parentRect.top - offset.y}px`;
 
 
 
 
 
 
 
 
 
 
 
 
 
949
  }
950
 
951
  function stopDrag() { draggedBubble = null; }