General styles fixes

This commit is contained in:
2026-02-09 20:31:34 +01:00
parent c5e3dd7681
commit d6a85aef2c
3 changed files with 84 additions and 43 deletions

9
package-lock.json generated
View File

@@ -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"
} }

View File

@@ -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 */

View File

@@ -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 &quot; 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 &quot; 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 &quot; 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 &quot; 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 &quot; 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>