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