General styles fixes
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -55,7 +55,6 @@
|
|||||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.28.6",
|
"@babel/code-frame": "^7.28.6",
|
||||||
"@babel/generator": "^7.28.6",
|
"@babel/generator": "^7.28.6",
|
||||||
@@ -1430,7 +1429,6 @@
|
|||||||
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -1472,7 +1470,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -1578,7 +1575,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -1800,7 +1796,6 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -2487,7 +2482,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -2565,7 +2559,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -2799,7 +2792,6 @@
|
|||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -2921,7 +2913,6 @@
|
|||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
63
src/App.css
63
src/App.css
@@ -98,6 +98,7 @@ body {
|
|||||||
rgba(181, 234, 215, 0.3) 0%,
|
rgba(181, 234, 215, 0.3) 0%,
|
||||||
rgba(255, 255, 255, 0.9) 100%
|
rgba(255, 255, 255, 0.9) 100%
|
||||||
);
|
);
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-card {
|
.preview-card {
|
||||||
@@ -171,19 +172,20 @@ body {
|
|||||||
.settings-grid {
|
.settings-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 1.5rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-item {
|
.setting-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-label {
|
.setting-label {
|
||||||
@@ -192,7 +194,8 @@ body {
|
|||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.35rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-hint {
|
.setting-hint {
|
||||||
@@ -203,7 +206,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
padding: 0.875rem 1rem;
|
padding: 0.875rem 1.125rem;
|
||||||
border: 2px solid var(--soft-gray);
|
border: 2px solid var(--soft-gray);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -211,6 +214,7 @@ body {
|
|||||||
background: white;
|
background: white;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field:focus {
|
.input-field:focus {
|
||||||
@@ -222,13 +226,14 @@ body {
|
|||||||
.slider-container {
|
.slider-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1.25rem;
|
||||||
|
padding-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 8px;
|
height: 10px;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
var(--pastel-pink) 0%,
|
var(--pastel-pink) 0%,
|
||||||
@@ -241,46 +246,51 @@ body {
|
|||||||
.slider::-webkit-slider-thumb {
|
.slider::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 24px;
|
width: 28px;
|
||||||
height: 24px;
|
height: 28px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: white;
|
background: white;
|
||||||
border: 3px solid var(--soft-purple);
|
border: 3px solid var(--soft-purple);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 2px 8px rgba(92, 74, 114, 0.2);
|
box-shadow: 0 3px 10px rgba(92, 74, 114, 0.25);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider::-webkit-slider-thumb:hover {
|
.slider::-webkit-slider-thumb:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.15);
|
||||||
box-shadow: 0 4px 12px rgba(92, 74, 114, 0.3);
|
box-shadow: 0 4px 14px rgba(92, 74, 114, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider::-moz-range-thumb {
|
.slider::-moz-range-thumb {
|
||||||
width: 24px;
|
width: 28px;
|
||||||
height: 24px;
|
height: 28px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: white;
|
background: white;
|
||||||
border: 3px solid var(--soft-purple);
|
border: 3px solid var(--soft-purple);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 2px 8px rgba(92, 74, 114, 0.2);
|
box-shadow: 0 3px 10px rgba(92, 74, 114, 0.25);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider::-moz-range-thumb:hover {
|
.slider::-moz-range-thumb:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.15);
|
||||||
box-shadow: 0 4px 12px rgba(92, 74, 114, 0.3);
|
box-shadow: 0 4px 14px rgba(92, 74, 114, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-value {
|
.slider-value {
|
||||||
min-width: 40px;
|
min-width: 48px;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.625rem 1.125rem;
|
||||||
background: var(--soft-purple);
|
background: var(--soft-purple);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1rem;
|
font-size: 1.05rem;
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2px 8px rgba(92, 74, 114, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.textarea {
|
.textarea {
|
||||||
@@ -373,11 +383,21 @@ body {
|
|||||||
|
|
||||||
.preview-container {
|
.preview-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
background: var(--soft-gray);
|
background: var(--soft-gray);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-inner {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-container svg {
|
.preview-container svg {
|
||||||
@@ -385,6 +405,7 @@ body {
|
|||||||
box-shadow:
|
box-shadow:
|
||||||
0 8px 32px rgba(92, 74, 114, 0.12),
|
0 8px 32px rgba(92, 74, 114, 0.12),
|
||||||
0 2px 8px rgba(92, 74, 114, 0.06);
|
0 2px 8px rgba(92, 74, 114, 0.06);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile responsive */
|
/* Mobile responsive */
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState, useRef } from "react";
|
||||||
|
import "../App.css";
|
||||||
|
|
||||||
const DEMO_TABS = `
|
const DEMO_TABS = `
|
||||||
4 6 -5 5 -4 4 3
|
4 6 -5 5 -4 4 3
|
||||||
@@ -16,16 +17,16 @@ const HarmonicaTabGenerator = () => {
|
|||||||
const parseInput = (text) => {
|
const parseInput = (text) => {
|
||||||
const lines = text.split("\n");
|
const lines = text.split("\n");
|
||||||
return lines.map((line) => {
|
return lines.map((line) => {
|
||||||
// Check if line contains tab notation (numbers with optional minus signs, quotes for bends, and spaces)
|
// Check if line contains tab notation (numbers with optional minus signs, quotes/backticks for bends, and spaces)
|
||||||
const isTabLine =
|
const isTabLine =
|
||||||
/^[\s\d-'"]+$/.test(line.trim()) && line.trim().length > 0;
|
/^[\s\d\-'"`'']+$/.test(line.trim()) && line.trim().length > 0;
|
||||||
|
|
||||||
let processedContent = line;
|
let processedContent = line;
|
||||||
|
|
||||||
// If it's a tab line, add + signs to positive numbers (but preserve bend notation)
|
// If it's a tab line, add + signs to positive numbers (but preserve bend notation)
|
||||||
if (isTabLine) {
|
if (isTabLine) {
|
||||||
processedContent = line.replace(
|
processedContent = line.replace(
|
||||||
/(\s|^)(\d+)(['"]?)/g,
|
/(\s|^)(\d+)(['"`'']?)/g,
|
||||||
(match, space, number, bend) => {
|
(match, space, number, bend) => {
|
||||||
return space + "+" + number + bend;
|
return space + "+" + number + bend;
|
||||||
},
|
},
|
||||||
@@ -73,15 +74,32 @@ const HarmonicaTabGenerator = () => {
|
|||||||
const parsedLines = parseInput(input);
|
const parsedLines = parseInput(input);
|
||||||
const columns = calculateLayout(parsedLines);
|
const columns = calculateLayout(parsedLines);
|
||||||
|
|
||||||
|
// Calculate the width of each column based on content
|
||||||
|
const calculateColumnWidth = (column) => {
|
||||||
|
let maxLength = 0;
|
||||||
|
column.forEach((line) => {
|
||||||
|
if (line.content.trim()) {
|
||||||
|
// Adjusted for letterSpacing of 0.5 instead of 2
|
||||||
|
// Monospace at 28px is about 16.8px per char + 0.5px spacing
|
||||||
|
const charWidth = line.isTab ? 17.3 : 7;
|
||||||
|
const length = line.content.length * charWidth;
|
||||||
|
maxLength = Math.max(maxLength, length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Math.max(maxLength, 200); // Minimum 200px per column
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnWidths = columns.map(calculateColumnWidth);
|
||||||
|
const totalContentWidth = columnWidths.reduce((sum, width) => sum + width, 0);
|
||||||
|
|
||||||
// SVG dimensions and styling
|
// SVG dimensions and styling
|
||||||
const lineHeight = 40;
|
const lineHeight = 40;
|
||||||
const fontSize = 28;
|
const fontSize = 28;
|
||||||
const annotationFontSize = 12;
|
const annotationFontSize = 12;
|
||||||
const titleFontSize = 36;
|
const titleFontSize = 36;
|
||||||
const titleHeight = 80;
|
const titleHeight = 80;
|
||||||
const columnWidth = 400;
|
|
||||||
const padding = 40;
|
const padding = 40;
|
||||||
const columnGap = 60;
|
const columnGap = 80;
|
||||||
|
|
||||||
const maxLinesInAnyColumn = Math.max(
|
const maxLinesInAnyColumn = Math.max(
|
||||||
...columns.map((col) => {
|
...columns.map((col) => {
|
||||||
@@ -93,9 +111,7 @@ const HarmonicaTabGenerator = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const svgWidth =
|
const svgWidth =
|
||||||
columns.length * columnWidth +
|
totalContentWidth + (columns.length - 1) * columnGap + padding * 2;
|
||||||
(columns.length - 1) * columnGap +
|
|
||||||
padding * 2;
|
|
||||||
const svgHeight =
|
const svgHeight =
|
||||||
maxLinesInAnyColumn * lineHeight + padding * 2 + titleHeight;
|
maxLinesInAnyColumn * lineHeight + padding * 2 + titleHeight;
|
||||||
|
|
||||||
@@ -166,7 +182,7 @@ const HarmonicaTabGenerator = () => {
|
|||||||
<span className="instruction-number">2</span>
|
<span className="instruction-number">2</span>
|
||||||
<div className="instruction-content">
|
<div className="instruction-content">
|
||||||
<strong>Auto-formatting</strong> - Positive numbers get a + sign
|
<strong>Auto-formatting</strong> - Positive numbers get a + sign
|
||||||
automatically. Use ' or " for bends (e.g., -3', 4")
|
automatically. Bends: ' " ` ' (e.g., -3', 4")
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li className="instruction-item">
|
<li className="instruction-item">
|
||||||
@@ -247,7 +263,8 @@ const HarmonicaTabGenerator = () => {
|
|||||||
Example:
|
Example:
|
||||||
4 6 -5 5 -4 4 3
|
4 6 -5 5 -4 4 3
|
||||||
-3' 6 7 -6' 6 5
|
-3' 6 7 -6' 6 5
|
||||||
Use ' or " for bends"
|
|
||||||
|
Add quotes after numbers for bends"
|
||||||
rows={12}
|
rows={12}
|
||||||
/>
|
/>
|
||||||
<div className="button-group">
|
<div className="button-group">
|
||||||
@@ -261,7 +278,7 @@ Use ' or " for bends"
|
|||||||
<p className="tip">
|
<p className="tip">
|
||||||
💡 <strong>Pro tip:</strong> Mix tab lines with text for
|
💡 <strong>Pro tip:</strong> Mix tab lines with text for
|
||||||
annotations. Tab lines contain only numbers, spaces, and bend
|
annotations. Tab lines contain only numbers, spaces, and bend
|
||||||
markers (' or ").
|
markers (' " ` ').
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -298,7 +315,11 @@ Use ' or " for bends"
|
|||||||
{/* Render columns */}
|
{/* Render columns */}
|
||||||
{columns.map((column, colIndex) => {
|
{columns.map((column, colIndex) => {
|
||||||
let yOffset = padding + titleHeight;
|
let yOffset = padding + titleHeight;
|
||||||
const xOffset = padding + colIndex * (columnWidth + columnGap);
|
// Calculate xOffset based on cumulative widths of previous columns
|
||||||
|
let xOffset = padding;
|
||||||
|
for (let i = 0; i < colIndex; i++) {
|
||||||
|
xOffset += columnWidths[i] + columnGap;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g key={colIndex}>
|
<g key={colIndex}>
|
||||||
@@ -317,7 +338,7 @@ Use ' or " for bends"
|
|||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
fontWeight="600"
|
fontWeight="600"
|
||||||
fill="#3d5a6c"
|
fill="#3d5a6c"
|
||||||
letterSpacing="2"
|
letterSpacing="0.5"
|
||||||
>
|
>
|
||||||
{line.content}
|
{line.content}
|
||||||
</text>
|
</text>
|
||||||
@@ -346,6 +367,14 @@ Use ' or " for bends"
|
|||||||
})}
|
})}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="button-group">
|
||||||
|
<button onClick={exportAsPNG} className="button button-primary">
|
||||||
|
📥 Download PNG
|
||||||
|
</button>
|
||||||
|
<button onClick={exportAsSVG} className="button button-secondary">
|
||||||
|
📥 Download SVG
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user