17 Commits

Author SHA1 Message Date
1b76c1df3a format and comments fix 2025-11-29 23:09:07 +01:00
4cb50a4276 add label to any shape 2025-11-28 23:21:28 +01:00
11c2222536 UPDATE: change text with contextmenu click 2025-11-22 11:03:04 +01:00
1541d62bb5 FIX: hide color palette 2025-11-22 10:36:44 +01:00
cd13d21e5f Update index.html 2025-11-21 23:27:46 +01:00
d8e3dc8cee de facto done 2025-11-21 10:46:57 +01:00
7f7a4a1ae6 resizing works 2025-11-21 10:43:38 +01:00
e078914ed2 resizing works 2025-11-21 10:42:52 +01:00
9234986b6f FIX: css shape fix 2025-11-20 22:40:04 +01:00
4bbd92920e resize works, but not ideal 2025-11-19 23:26:01 +01:00
2adc151e8e not bad already 2025-11-19 23:11:47 +01:00
1bf1e1b389 Dodano funkcje przybliżania, oddalania i
przewijania planszy.
2025-11-18 22:14:09 +01:00
49f631f854 Update README.md 2025-11-15 12:22:53 +01:00
b1a2dac5e4 Merge remote-tracking branch 'origin/AndriiFunctionality' into AndriiFunctionality 2025-11-14 19:40:01 +01:00
44a52e0627 implementing functions 5,6,7,14 2025-11-14 19:39:48 +01:00
1459d4ab8d Update README.md 2025-11-14 19:38:34 +01:00
ff88da0079 first steps 2025-11-14 15:40:07 +01:00
4 changed files with 40438 additions and 236 deletions

View File

@@ -8,29 +8,29 @@ Funkcjonalności aplikacji koncentrują się na tworzeniu i modyfikacji kształt
## Funkcje:
### Kształty
1. Tworzenie prostokąta
2. Tworzenie rombu
3. Tworzenie elipsy
- [x] 1. Tworzenie prostokąta
- [x] 2. Tworzenie rombu
- [x] 3. Tworzenie elipsy
### Akcje na kształtach
4. Zmiana rozmiaru kształtów.
5. Przeciągnięcie kzstałtu po planszy
6. Usunięcie kształtu
7. Zaznaczenie kształtu
8. Kopiowanie kształtu
9. Wklejanie kształtu
10. Zmiana kolorów kształtów
11. Połączenie kształtów (strzałka ->)
12. Usunięcie kształtów
- [x] 4. Zmiana rozmiaru kształtów.
- [x] 5. Przeciągnięcie kzstałtu po planszy
- [x] 6. Usunięcie kształtu
- [x] 7. Zaznaczenie kształtu
- [x] 8. Kopiowanie kształtu
- [x] 9. Wklejanie kształtu
- [x] 10. Zmiana kolorów kształtów
- [x] 11. Połączenie kształtów (strzałka ->)
- [x] 12. Usunięcie kształtów
### Tekst
13. Dodanie tekstu
14. Usunięcie tekstu
- [x] 13. Dodanie tekstu
- [x] 14. Usunięcie tekstu
### Plansza i akcje na niej
15. Przybliżenie planszy
16. Oddalenie planszy
17. Przewijanie planszy
- [x] 15. Przybliżenie planszy
- [x] 16. Oddalenie planszy
- [x] 17. Przewijanie planszy
*P.S. Jak będzie brakować funkcjonalności:*
- Obracanie kształtu

View File

@@ -10,170 +10,536 @@
</head>
<body>
<div class="toolbar">
<p>Kształty</p>
<div class="shape-wrapper toolbar-wrapper">
<div id="shape-rectangle" class="shape" data-type="rectangle"></div>
<div id="shape-ellipse" class="shape" data-type="ellipse"></div>
<div id="shape-diamond" class="shape" data-type="diamond"></div>
<div id="shape-text" class="shape" data-type="text">Tekst</div>
<div class="toolbar">
<p>Kształty - przeciągnij i upuść</p>
<div class="shape-wrapper toolbar-wrapper">
<div id="shape-rectangle" class="shape" data-type="rectangle"></div>
<div id="shape-ellipse" class="shape" data-type="ellipse"></div>
<div id="shape-diamond" class="shape" data-type="diamond"></div>
<div id="shape-text" class="shape" data-type="text">Tekst</div>
</div>
<p>Akcje</p>
<div class="tools-wrapper toolbar-wrapper">
<button id="btn-delete">Usuń</button>
<button id="btn-copy">Kopiuj</button>
<button id="btn-paste">Wklej</button>
<button id="btn-connect">Połącz</button>
<div class="color-wrapper">
<button id="btn-change-color">Kolor</button>
<div id="color-palette"
style="position:absolute; display:none; padding:8px; background:#fff; border:1px solid #ccc; border-radius:6px; gap:6px; z-index:2000;"></div>
</div>
</div>
<div class="canvas-container">
<div id="paper"></div>
<p>Zoom</p>
<div class="zoom-wrapper toolbar-wrapper">
<button id="zoom-in">+</button>
<button id="zoom-out">-</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@joint/core@4.0.1/dist/joint.js"></script>
<script>
$(document).ready(function () {
//init paper - Start
const graph = new joint.dia.Graph();
const paper = new joint.dia.Paper({
el: $('#paper'),
model: graph,
width: window.innerWidth,
height: window.innerHeight,
gridSize: 20,
});
//init paper - END
<div id="resize-handle" style="position: absolute; display: none; border: 2px dashed #4A90E2; z-index: 10;"></div>
<div class="canvas-container">
<div id="paper"></div>
</div>
//create shape - START
function createRectangle(paperX, paperY) {
const rect = new joint.shapes.standard.Rectangle({
position: { x: paperX - 40, y: paperY - 24 },
size: { width: 80, height: 48 },
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
}
}
});
graph.addCell(rect);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
<script src="joint.js"></script>
<script>
let selectedElement = null;
let activeElementView = null;
let clipboard = null;
function createDiamond(paperX, paperY) {
const rect = new joint.shapes.standard.Rectangle({
position: { x: paperX - 24, y: paperY - 24 },
size: { width: 48, height: 48 },
angle: 45,
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
}
}
});
graph.addCell(rect);
}
function createEllipse(paperX, paperY) {
const rect = new joint.shapes.standard.Ellipse({
position: { x: paperX - 40, y: paperY - 24 },
size: { width: 80, height: 48 },
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
}
}
});
graph.addCell(rect);
}
function createText(paperX, paperY) {
const textBlock = new joint.shapes.standard.TextBlock({
position: { x: paperX - 40, y: paperY - 24 },
size: { width: 80, height: 48 },
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1,
strokeDasharray: '5,5'
},
label: {
text: 'Tekst...',
fill: '#000000'
}
}
});
graph.addCell(textBlock);
}
//create shape - END
//init draggalble - START
$('.shape').draggable({
helper: function () {
const $clone = $(this).clone();
$clone.css({
opacity: 0.8,
cursor: 'grabbing',
borderColor: '#000',
color: '#000',
});
return $clone;
},
cursor: 'move',
revert: 'invalid',
appendTo: 'body',
zIndex: 1000
});
$('#paper').droppable({
accept: '.shape',
drop: function (event, ui) {
const shapeType = ui.helper.data('type');
const containerOffset = $(this).offset();
const x = event.pageX - containerOffset.left;
const y = event.pageY - containerOffset.top;
const scale = paper.scale();
const translate = paper.translate();
const paperX = (x - translate.tx) / scale.sx;
const paperY = (y - translate.ty) / scale.sy;
if (shapeType === 'rectangle') {
createRectangle(paperX, paperY);
} else if (shapeType === 'ellipse') {
createEllipse(paperX, paperY);
} else if (shapeType === 'diamond') {
createDiamond(paperX, paperY);
} else if (shapeType === 'text') {
createText(paperX, paperY);
}
}
});
//init draggable - END
//add text to text block - START
paper.on('element:pointerdblclick', function (elementView) {
if (elementView.model.attributes.type === 'standard.TextBlock') {
const currentText = elementView.model.attr('label/text');
const newText = prompt('Edytuj tekst:', currentText);
if (newText != null) {
elementView.model.attr('label/text', newText);
}
}
});
//add text to text block - END
$(document).ready(function () {
const graph = new joint.dia.Graph();
const paper = new joint.dia.Paper({
el: $('#paper'),
model: graph,
width: window.innerWidth,
height: window.innerHeight,
gridSize: 20,
preventContextMenu: false,
});
</script>
const $resizeHandle = $('#resize-handle');
// --- DODANE FUNKCJE START ---
let connectMode = false;
$('#btn-connect').click(function () {
if (!selectedElement) return;
connectMode = !connectMode;
$(this).css('background', connectMode ? '#e3f2fd' : '#fff');
});
const paletteColors = ['#FF4B4B', '#FFA500', '#FFD700', '#4CAF50', '#00BCD4', '#2196F3', '#9C27B0', '#795548', '#000000'];
const $palette = $('#color-palette');
$palette.css({gridTemplateColumns: 'repeat(3,28px)'});
paletteColors.forEach(c => {
// ZMIANA TUTAJ: Dodajemy width, height, border i cursor
const tile = $('<div></div>').css({
background: c,
width: '28px', // Dodane
height: '28px', // Dodane
border: '1px solid #444', // Dodane dla widoczności
cursor: 'pointer' // Dodane dla kursora rączki
});
tile.on('click', function () {
if (selectedElement) {
try {
// Ustawianie koloru
selectedElement.attr('body/fill', c);
if (selectedElement.attributes.type === 'standard.TextBlock') {
selectedElement.attr('label/fill', c);
}
} catch (e) { /* ignoruj */
}
}
$palette.hide();
});
$palette.append(tile);
});
$('#btn-change-color').on('click', function () {
$palette.toggleClass('active');
});
$(document).on('click', function (e) {
if (!$(e.target).closest('#color-palette,#btn-change-color').length)
if ($palette.hasClass('active'))
$palette.toggleClass('active');
});
paper.on('element:pointerclick', function (elementView) {
if (connectMode && selectedElement && selectedElement !== elementView.model) {
const link = new joint.shapes.standard.Link();
link.source(selectedElement);
link.target(elementView.model);
graph.addCell(link);
connectMode = false;
$('#btn-connect').css('background', '#fff');
}
});
// --- DODANE FUNKCJE KONIEC ---
// obwódka i znaczek usuwanie
function createToolsView() {
return new joint.dia.ToolsView({
tools: [
new joint.elementTools.Boundary({
padding: 20,
}),
new joint.elementTools.Remove({
offset: {x: -10, y: -10},
focusOpacity: 0.5,
action: function () {
deleteShape();
}
})
]
});
}
function createDiamond(paperX, paperY) {
const diamond = new joint.shapes.standard.Polygon({
position: {x: paperX - 24, y: paperY - 24},
size: {width: 48, height: 48},
attrs: {
body: {
refPoints: '0.5,0 1,0.5 0.5,1 0,0.5', // Diamond shape points
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
}
}
});
graph.addCell(diamond);
const elementView = diamond.findView(paper);
elementView.addTools(createToolsView());
}
function createEllipse(paperX, paperY) {
const rect = new joint.shapes.standard.Ellipse({
position: {x: paperX - 40, y: paperY - 24},
size: {width: 80, height: 48},
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
}
}
});
graph.addCell(rect);
const elementView = rect.findView(paper);
elementView.addTools(createToolsView());
}
function createText(paperX, paperY) {
const textBlock = new joint.shapes.standard.TextBlock({
position: {x: paperX - 40, y: paperY - 24},
size: {width: 80, height: 48},
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1,
strokeDasharray: '5,5'
},
label: {
text: 'Tekst...',
fill: '#000000'
}
}
});
graph.addCell(textBlock);
const elementView = textBlock.findView(paper);
elementView.addTools(createToolsView());
}
//create shape - END
//init draggalble - START
$('.shape').draggable({
helper: function () {
const $clone = $(this).clone();
$clone.css({
opacity: 0.8,
cursor: 'grabbing',
borderColor: '#000',
color: '#000',
});
return $clone;
},
cursor: 'move',
revert: 'invalid',
appendTo: 'body',
zIndex: 1000
});
$('#paper').droppable({
accept: '.shape',
drop: function (event, ui) {
const shapeType = ui.helper.data('type');
const containerOffset = $(this).offset();
const x = event.pageX - containerOffset.left;
const y = event.pageY - containerOffset.top;
const scale = paper.scale();
const translate = paper.translate();
const paperX = (x - translate.tx) / scale.sx;
const paperY = (y - translate.ty) / scale.sy;
activeElementView = null;
selectedElement = null;
hideResizeHandle();
if (shapeType === 'rectangle') {
createRectangle(paperX, paperY);
} else if (shapeType === 'ellipse') {
createEllipse(paperX, paperY);
} else if (shapeType === 'diamond') {
createDiamond(paperX, paperY);
} else if (shapeType === 'text') {
createText(paperX, paperY);
}
const cells = graph.getCells();
if (cells.length > 0) {
const lastCell = cells[cells.length - 1];
const lastView = lastCell.findView(paper);
if (lastView && typeof lastView.hideTools === 'function') {
lastView.hideTools();
}
}
}
});
//init draggable - END
function getViewportCenter() {
const container = $('.canvas-container');
const scale = paper.scale();
const translate = paper.translate();
return {
x: (container.width() / 2 - translate.tx) / scale.sx,
y: (container.height() / 2 - translate.ty) / scale.sy
};
}
function copyShape() {
if (selectedElement) {
clipboard = selectedElement.clone();
}
}
function pasteShape() {
if (clipboard) {
const center = getViewportCenter();
const pasted = clipboard.clone();
pasted.position(center.x + 20, center.y + 20);
graph.addCell(pasted);
const pastedView = pasted.findView(paper);
if (pastedView) {
pastedView.addTools(createToolsView());
pastedView.showTools();
if (activeElementView && activeElementView !== pastedView) {
activeElementView.hideTools();
}
activeElementView = pastedView;
}
selectedElement = pasted;
showResizeHandle(pastedView);
}
}
function deleteShape() {
if (selectedElement) {
selectedElement.remove();
selectedElement = null;
activeElementView = null;
hideResizeHandle();
}
}
$('#btn-delete').click(function () {
deleteShape()
});
$('#btn-copy').click(function () {
copyShape();
});
$('#btn-paste').click(function () {
pasteShape();
});
paper.on('element:pointerclick', function (elementView) {
if (activeElementView && activeElementView !== elementView) {
activeElementView.hideTools();
}
elementView.showTools();
activeElementView = elementView;
selectedElement = elementView.model;
showResizeHandle(elementView);
});
let panStart = null;
paper.on('blank:pointerdown', function (evt) {
if (activeElementView) {
if (typeof activeElementView.hideTools === 'function') {
activeElementView.hideTools();
}
if (typeof activeElementView.unhighlight === 'function') {
activeElementView.unhighlight();
}
activeElementView = null;
}
selectedElement = null;
hideResizeHandle();
//ruszanie caloscia
panStart = {
x: evt.clientX,
y: evt.clientY,
tx: paper.translate().tx,
ty: paper.translate().ty
};
paper.$el.css('cursor', 'grabbing');
});
$(document).on('mousemove', function (evt) {
if (panStart) {
const dx = evt.clientX - panStart.x;
const dy = evt.clientY - panStart.y;
paper.translate(panStart.tx + dx, panStart.ty + dy);
showResizeHandle(activeElementView);
}
});
$(document).on('mouseup', function () {
if (panStart) {
panStart = null;
paper.$el.css('cursor', 'default');
}
});
//add text to text block - START
paper.on('element:contextmenu', function (elementView, ev) {
if (elementView.model.attributes.type === 'standard.TextBlock') {
ev.preventDefault();
const currentText = elementView.model.attr('label/text');
const newText = prompt('Edytuj tekst:', currentText);
if (newText != null) {
elementView.model.attr('label/text', newText);
}
} else if (elementView.model.attributes.type !== 'standard.TextBlock') {
ev.preventDefault();
const currentText = elementView.model.attr('label/text') || '';
const newText = prompt('Edytuj tekst:', currentText);
if (newText != null) {
elementView.model.attr('label/text', newText);
}
}
});
$(document).on('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') {
e.preventDefault();
copyShape();
return;
}
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'v') {
e.preventDefault();
pasteShape();
return;
}
if (e.key === 'Delete' && selectedElement) {
deleteShape();
}
});
//add text to text block - END
// resize
function showResizeHandle(elementView) {
if (!elementView) return;
const bbox = elementView.model.getBBox();
const clientRect = paper.localToClientRect(bbox);
const angle = elementView.model.get('angle') || 0;
$resizeHandle.css({
left: clientRect.x,
top: clientRect.y,
width: clientRect.width,
height: clientRect.height,
transform: `rotate(${angle}deg)`,
transformOrigin: 'center center'
}).show();
if ($resizeHandle.data('ui-resizable')) {
$resizeHandle.resizable('destroy');
}
let originalBBox;
$resizeHandle.resizable({
handles: 'n, e, s, w, ne, se, sw, nw',
start: function () {
if (activeElementView) {
activeElementView.hideTools();
}
originalBBox = selectedElement.getBBox();
},
resize: function (event, ui) {
if (selectedElement && originalBBox) {
const scale = paper.scale();
const newWidth = ui.size.width / scale.sx;
const newHeight = ui.size.height / scale.sy;
const newX = originalBBox.x + (ui.position.left - ui.originalPosition.left) / scale.sx;
const newY = originalBBox.y + (ui.position.top - ui.originalPosition.top) / scale.sy;
if (selectedElement.get('angle')) {
selectedElement.resize(newWidth, newHeight, {direction: 'center'});
} else {
selectedElement.position(newX, newY);
selectedElement.resize(newWidth, newHeight);
}
}
},
stop: function () {
if (activeElementView) {
activeElementView.showTools();
showResizeHandle(activeElementView);
}
originalBBox = null;
}
});
}
function hideResizeHandle() {
if ($resizeHandle.data('ui-resizable')) {
$resizeHandle.resizable('destroy');
}
$resizeHandle.hide();
}
//create shape - START
function createRectangle(paperX, paperY) {
const rect = new joint.shapes.standard.Rectangle({
position: {x: paperX - 40, y: paperY - 24},
size: {width: 80, height: 48},
attrs: {
body: {
fill: 'transparent',
stroke: '#000000',
strokeWidth: 1
},
label: {
text: ''
}
}
});
graph.addCell(rect);
const elementView = rect.findView(paper);
elementView.addTools(createToolsView());
}
// Zoom
const zoomStep = 0.2;
const minScale = 0.2;
const maxScale = 3.0;
function applyZoom(newScale, ox, oy) {
paper.scale(newScale, newScale, ox, oy);
showResizeHandle(activeElementView);
}
$('#zoom-in').click(function () {
const currentScale = paper.scale().sx;
const newScale = Math.min(maxScale, currentScale + zoomStep);
applyZoom(newScale);
});
$('#zoom-out').click(function () {
const currentScale = paper.scale().sx;
const newScale = Math.max(minScale, currentScale - zoomStep);
applyZoom(newScale);
});
// Zoom za pomoca kolka myszy
paper.on('blank:mousewheel cell:mousewheel', function (evt, x, y, delta) {
evt.preventDefault();
const oldScale = paper.scale().sx;
const zoomFactor = 0.2;
let newScale = oldScale * (1 + delta * zoomFactor);
newScale = Math.max(minScale, Math.min(maxScale, newScale));
applyZoom(newScale, x, y);
});
});
</script>
</body>
</html>

39629
joint.js Normal file

File diff suppressed because it is too large Load Diff

333
style.css
View File

@@ -1,71 +1,278 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.shape-wrapper {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, auto);
gap: 30px 10px;
padding-bottom: 40px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
min-height: 100vh;
overflow: hidden;
}
#shape-rectangle {
width: 80px;
height: 48px;
}
.toolbar {
background: #ffffff;
color: #333;
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
position: absolute;
width: 280px;
left: 0;
top: 0;
height: 100vh;
z-index: 9;
overflow-y: auto;
border-right: 1px solid #e0e0e0;
}
#shape-ellipse {
width: 80px;
height: 48px;
border-radius: 50%;
}
.toolbar p {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #666;
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 2px solid #e0e0e0;
}
#shape-diamond {
height: 48px;
width: 48px;
transform: rotate(45deg) translateX(15px);
}
.toolbar-wrapper {
background: #f9f9f9;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
#shape-text {
width: 80px;
height: 48px;
border-style: dashed;
color: #ffffff;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
.shape-wrapper {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 12px;
min-height: 152px;
justify-content: space-between;
align-content: space-between;
}
.shape {
background: transparent;
border: 1px solid white;
}
.shape {
background: #fff;
border: 2px solid #d0d0d0;
cursor: grab;
transition: border-color 0.2s, box-shadow 0.2s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.toolbar {
background: #3498db;
color: white;
padding: 20px;
display: flex;
flex-wrap: wrap;
flex-direction: column;
gap: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
position: absolute;
width: 300px;
left: 0px;
top: 0px;
height: 100vh;
z-index: 9;
border-top-right-radius: 30px;
border-bottom-right-radius: 30px;
}
.shape:hover {
border-color: #666;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
}
.toolbar-wrapper {
padding-bottom: 30px;
padding: 20px;
}
.shape:active {
cursor: grabbing;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
#shape-rectangle {
width: 80px;
height: 48px;
border-radius: 4px;
}
#shape-ellipse {
width: 80px;
height: 48px;
border-radius: 50%;
}
#shape-diamond {
height: 48px;
width: 48px;
transform: rotate(45deg) translate(3px, -16px);
border-radius: 4px;
}
#shape-text {
width: 80px;
height: 48px;
border-style: dashed;
border-radius: 4px;
color: #666;
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
/* Tools wrapper */
.tools-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0;
}
.tools-wrapper button {
padding: 12px 16px;
font-size: 13px;
font-weight: 500;
border: 1px solid #d0d0d0;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
background: #fff;
color: #333;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.tools-wrapper button:hover {
background: #f5f5f5;
border-color: #999;
}
.tools-wrapper button:active {
background: #e8e8e8;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
#btn-delete {
color: #d32f2f;
border-color: #d32f2f;
}
#btn-delete:hover {
background: #ffebee;
border-color: #c62828;
}
#btn-copy {
color: #1976d2;
border-color: #1976d2;
}
#btn-copy:hover {
background: #e3f2fd;
border-color: #1565c0;
}
#btn-paste {
color: #388e3c;
border-color: #388e3c;
}
#btn-paste:hover {
background: #e8f5e9;
border-color: #2e7d32;
}
/* Zoom wrapper */
.zoom-wrapper {
display: flex;
justify-content: center;
gap: 12px;
padding: 6px;
}
.zoom-wrapper button {
background: #fff;
color: #333;
border: 1px solid #d0d0d0;
border-radius: 6px;
width: 50px;
height: 50px;
font-size: 20px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: background 0.2s, border-color 0.2s;
outline: none;
}
.zoom-wrapper button:hover {
background: #f5f5f5;
border-color: #999;
}
.zoom-wrapper button:active {
background: #e8e8e8;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* Canvas container */
.canvas-container {
position: absolute;
left: 280px;
top: 0;
right: 0;
bottom: 0;
background: #ffffff;
background-image:
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px);
background-size: 20px 20px;
overflow: auto;
}
#paper {
width: 100%;
height: 100%;
}
/* Scrollbar styling */
.toolbar::-webkit-scrollbar {
width: 6px;
}
.toolbar::-webkit-scrollbar-track {
background: #f5f5f5;
}
.toolbar::-webkit-scrollbar-thumb {
background: #d0d0d0;
border-radius: 3px;
}
.toolbar::-webkit-scrollbar-thumb:hover {
background: #999;
}
#resize-handle {
position: absolute;
box-sizing: border-box;
pointer-events: auto;
}
/* Responsive */
@media (max-width: 768px) {
.toolbar {
width: 100%;
height: auto;
border-radius: 0;
}
.canvas-container {
left: 0;
top: auto;
}
}
/* color wrapper */
.color-wrapper{
display: flex;
flex-direction: column;
position: relative;
}
#color-palette{
display:none;
top: 41px;
}
#color-palette.active{
display: grid!important;
}