Code cleanup

This commit is contained in:
2026-02-12 20:01:17 +01:00
parent f97f31a4df
commit 0978cc6194
2 changed files with 517 additions and 371 deletions

View File

@@ -1,5 +1,9 @@
import React, { useState, useRef } from "react"; import React, { useState, useRef } from "react";
import "../App.css"; import "../styles.css";
// ============================================================================
// CONSTANTS
// ============================================================================
const DEMO_TABS = ` const DEMO_TABS = `
4 6 -5 5 -4 4 3 4 6 -5 5 -4 4 3
@@ -7,29 +11,44 @@ const DEMO_TABS = `
4 4 4 5 5' 5 5 5 -5 5 4 4 4 4 5 5' 5 5 5 -5 5 4
`; `;
const HarmonicaTabGenerator = () => { const SVG_CONFIG = {
const [input, setInput] = useState(DEMO_TABS); lineHeight: 40,
const [title, setTitle] = useState("My Harmonica Tab"); fontSize: 28,
const [maxLinesPerColumn, setMaxLinesPerColumn] = useState(10); annotationFontSize: 12,
const svgRef = useRef(null); titleFontSize: 36,
titleHeight: 80,
padding: 40,
columnGap: 80,
charWidth: 17.3, // For tab lines (monospace)
textCharWidth: 7, // For annotation lines
minColumnWidth: 200,
canvasScale: 2, // For PNG export quality
backgroundColor: "#fffef5",
};
// Parse input and add + signs to positive numbers // ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Parse input text and identify tab lines vs annotation lines
* Adds + signs to positive numbers in tab lines
*/
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/backticks for bends, and spaces) // Tab lines contain only numbers, spaces, minus signs, and bend markers
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) // Add + signs to positive numbers in tab lines
if (isTabLine) { if (isTabLine) {
processedContent = line.replace( processedContent = line.replace(
/(\s|^)(\d+)(['"`'']?)/g, /(\s|^)(\d+)(['"`'']?)/g,
(match, space, number, bend) => { (match, space, number, bend) => space + "+" + number + bend,
return space + "+" + number + bend;
},
); );
} }
@@ -40,8 +59,10 @@ const HarmonicaTabGenerator = () => {
}); });
}; };
// Calculate layout (columns based on max lines per column) /**
const calculateLayout = (parsedLines) => { * Calculate column layout based on max lines per column
*/
const calculateLayout = (parsedLines, maxLinesPerColumn) => {
const tabLines = parsedLines.filter((l) => l.isTab); const tabLines = parsedLines.filter((l) => l.isTab);
const numColumns = Math.ceil(tabLines.length / maxLinesPerColumn); const numColumns = Math.ceil(tabLines.length / maxLinesPerColumn);
@@ -52,10 +73,7 @@ const HarmonicaTabGenerator = () => {
const columnLines = []; const columnLines = [];
let tabCount = 0; let tabCount = 0;
while ( while (currentIndex < parsedLines.length && tabCount < maxLinesPerColumn) {
currentIndex < parsedLines.length &&
tabCount < maxLinesPerColumn
) {
const line = parsedLines[currentIndex]; const line = parsedLines[currentIndex];
columnLines.push(line); columnLines.push(line);
@@ -71,60 +89,89 @@ const HarmonicaTabGenerator = () => {
return columns; return columns;
}; };
const parsedLines = parseInput(input); /**
const columns = calculateLayout(parsedLines); * Calculate the width needed for a column based on its content
*/
// Calculate the width of each column based on content
const calculateColumnWidth = (column) => { const calculateColumnWidth = (column) => {
let maxLength = 0; let maxLength = 0;
column.forEach((line) => { column.forEach((line) => {
if (line.content.trim()) { if (line.content.trim()) {
// Adjusted for letterSpacing of 0.5 instead of 2 const charWidth = line.isTab
// Monospace at 28px is about 16.8px per char + 0.5px spacing ? SVG_CONFIG.charWidth
const charWidth = line.isTab ? 17.3 : 7; : SVG_CONFIG.textCharWidth;
const length = line.content.length * charWidth; const length = line.content.length * charWidth;
maxLength = Math.max(maxLength, length); maxLength = Math.max(maxLength, length);
} }
}); });
return Math.max(maxLength, 200); // Minimum 200px per column
return Math.max(maxLength, SVG_CONFIG.minColumnWidth);
}; };
const columnWidths = columns.map(calculateColumnWidth); /**
* Calculate SVG dimensions based on content
*/
const calculateSVGDimensions = (columns, columnWidths) => {
const totalContentWidth = columnWidths.reduce((sum, width) => sum + width, 0); 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 padding = 40;
const columnGap = 80;
const maxLinesInAnyColumn = Math.max( const maxLinesInAnyColumn = Math.max(
...columns.map((col) => { ...columns.map((col) => {
return col.reduce((sum, line) => { return col.reduce((sum, line) => sum + (line.isTab ? 1 : 0.5), 0);
return sum + (line.isTab ? 1 : 0.5);
}, 0);
}), }),
1, 1,
); );
const svgWidth = const svgWidth =
totalContentWidth + (columns.length - 1) * columnGap + padding * 2; totalContentWidth +
const svgHeight = (columns.length - 1) * SVG_CONFIG.columnGap +
maxLinesInAnyColumn * lineHeight + padding * 2 + titleHeight; SVG_CONFIG.padding * 2;
// Export as PNG const svgHeight =
maxLinesInAnyColumn * SVG_CONFIG.lineHeight +
SVG_CONFIG.padding * 2 +
SVG_CONFIG.titleHeight;
return { svgWidth, svgHeight };
};
// ============================================================================
// MAIN COMPONENT
// ============================================================================
const HarmonicaTabGenerator = () => {
// State
const [input, setInput] = useState(DEMO_TABS);
const [title, setTitle] = useState("My Harmonica Tab");
const [maxLinesPerColumn, setMaxLinesPerColumn] = useState(10);
// Ref for SVG element
const svgRef = useRef(null);
// ============================================================================
// COMPUTED VALUES
// ============================================================================
const parsedLines = parseInput(input);
const columns = calculateLayout(parsedLines, maxLinesPerColumn);
const columnWidths = columns.map(calculateColumnWidth);
const { svgWidth, svgHeight } = calculateSVGDimensions(columns, columnWidths);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Export the SVG as a PNG image
*/
const exportAsPNG = () => { const exportAsPNG = () => {
const svgElement = svgRef.current; const svgElement = svgRef.current;
const svgData = new XMLSerializer().serializeToString(svgElement); const svgData = new XMLSerializer().serializeToString(svgElement);
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
// Set canvas size (2x for better quality) // Set canvas size with 2x scaling for better quality
canvas.width = svgWidth * 2; canvas.width = svgWidth * SVG_CONFIG.canvasScale;
canvas.height = svgHeight * 2; canvas.height = svgHeight * SVG_CONFIG.canvasScale;
const img = new Image(); const img = new Image();
const svgBlob = new Blob([svgData], { const svgBlob = new Blob([svgData], {
@@ -133,7 +180,7 @@ const HarmonicaTabGenerator = () => {
const url = URL.createObjectURL(svgBlob); const url = URL.createObjectURL(svgBlob);
img.onload = () => { img.onload = () => {
ctx.scale(2, 2); ctx.scale(SVG_CONFIG.canvasScale, SVG_CONFIG.canvasScale);
ctx.drawImage(img, 0, 0); ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
@@ -148,7 +195,9 @@ const HarmonicaTabGenerator = () => {
img.src = url; img.src = url;
}; };
// Export as SVG /**
* Export the SVG as an SVG file
*/
const exportAsSVG = () => { const exportAsSVG = () => {
const svgElement = svgRef.current; const svgElement = svgRef.current;
const svgData = new XMLSerializer().serializeToString(svgElement); const svgData = new XMLSerializer().serializeToString(svgElement);
@@ -159,23 +208,67 @@ const HarmonicaTabGenerator = () => {
link.click(); link.click();
}; };
// ============================================================================
// RENDER
// ============================================================================
return ( return (
<div className="gradient-bg"> <div className="gradient-bg">
<div className="container"> <div className="container">
{/* Header */}
<h1 className="title">🎵 Harmonica Tabs Image Generator</h1> <h1 className="title">🎵 Harmonica Tabs Image Generator</h1>
<p className="subtitle"> <p className="subtitle">
Create beautiful, readable images of your harmonica tabs Create beautiful, readable images of your harmonica tabs
</p> </p>
{/* Instructions Section - MOVED TO TOP */} {/* Instructions */}
<InstructionsSection />
{/* Settings */}
<SettingsSection
title={title}
setTitle={setTitle}
maxLinesPerColumn={maxLinesPerColumn}
setMaxLinesPerColumn={setMaxLinesPerColumn}
/>
{/* Input */}
<InputSection
input={input}
setInput={setInput}
exportAsPNG={exportAsPNG}
exportAsSVG={exportAsSVG}
/>
{/* Preview */}
<PreviewSection
svgRef={svgRef}
svgWidth={svgWidth}
svgHeight={svgHeight}
title={title}
columns={columns}
columnWidths={columnWidths}
exportAsPNG={exportAsPNG}
exportAsSVG={exportAsSVG}
/>
</div>
</div>
);
};
// ============================================================================
// SUB-COMPONENTS
// ============================================================================
const InstructionsSection = () => (
<div className="card instructions-card"> <div className="card instructions-card">
<h2 className="section-title">📋 How to Use</h2> <h2 className="section-title">📋 How to Use</h2>
<ul className="instructions"> <ul className="instructions">
<li className="instruction-item"> <li className="instruction-item">
<span className="instruction-number">1</span> <span className="instruction-number">1</span>
<div className="instruction-content"> <div className="instruction-content">
<strong>Enter your tabs</strong> - Type your harmonica tab <strong>Enter your tabs</strong> - Type your harmonica tab numbers
numbers with spaces with spaces
</div> </div>
</li> </li>
<li className="instruction-item"> <li className="instruction-item">
@@ -188,15 +281,14 @@ const HarmonicaTabGenerator = () => {
<li className="instruction-item"> <li className="instruction-item">
<span className="instruction-number">3</span> <span className="instruction-number">3</span>
<div className="instruction-content"> <div className="instruction-content">
<strong>Add annotations</strong> - Plain text appears smaller <strong>Add annotations</strong> - Plain text appears smaller and
and italicized italicized
</div> </div>
</li> </li>
<li className="instruction-item"> <li className="instruction-item">
<span className="instruction-number">4</span> <span className="instruction-number">4</span>
<div className="instruction-content"> <div className="instruction-content">
<strong>Customize layout</strong> - Set your title and rows per <strong>Customize layout</strong> - Set your title and rows per column
column
</div> </div>
</li> </li>
<li className="instruction-item"> <li className="instruction-item">
@@ -207,17 +299,21 @@ const HarmonicaTabGenerator = () => {
</li> </li>
</ul> </ul>
</div> </div>
);
{/* Settings Section */} const SettingsSection = ({
title,
setTitle,
maxLinesPerColumn,
setMaxLinesPerColumn,
}) => (
<div className="card settings-card"> <div className="card settings-card">
<h2 className="section-title">⚙️ Settings</h2> <h2 className="section-title">⚙️ Settings</h2>
<div className="settings-grid"> <div className="settings-grid">
<div className="setting-item"> <div className="setting-item">
<label className="setting-label"> <label className="setting-label">
Tab Title Tab Title
<span className="setting-hint"> <span className="setting-hint">Appears at the top of your image</span>
Appears at the top of your image
</span>
</label> </label>
<input <input
type="text" type="text"
@@ -240,9 +336,7 @@ const HarmonicaTabGenerator = () => {
min="5" min="5"
max="20" max="20"
value={maxLinesPerColumn} value={maxLinesPerColumn}
onChange={(e) => onChange={(e) => setMaxLinesPerColumn(parseInt(e.target.value))}
setMaxLinesPerColumn(parseInt(e.target.value))
}
className="slider" className="slider"
/> />
<span className="slider-value">{maxLinesPerColumn}</span> <span className="slider-value">{maxLinesPerColumn}</span>
@@ -250,8 +344,9 @@ const HarmonicaTabGenerator = () => {
</div> </div>
</div> </div>
</div> </div>
);
{/* Input Section */} const InputSection = ({ input, setInput, exportAsPNG, exportAsSVG }) => (
<div className="card"> <div className="card">
<h2 className="section-title">✏️ Input Your Tabs</h2> <h2 className="section-title">✏️ Input Your Tabs</h2>
<textarea <textarea
@@ -276,13 +371,22 @@ Add quotes after numbers for bends"
</button> </button>
</div> </div>
<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
annotations. Tab lines contain only numbers, spaces, and bend lines contain only numbers, spaces, and bend markers (' " ` ').
markers (' " ` ').
</p> </p>
</div> </div>
);
{/* Preview Section */} const PreviewSection = ({
svgRef,
svgWidth,
svgHeight,
title,
columns,
columnWidths,
exportAsPNG,
exportAsSVG,
}) => (
<div className="card preview-card"> <div className="card preview-card">
<h2 className="section-title">👁️ Preview</h2> <h2 className="section-title">👁️ Preview</h2>
<div className="preview-container"> <div className="preview-container">
@@ -291,18 +395,22 @@ Add quotes after numbers for bends"
width={svgWidth} width={svgWidth}
height={svgHeight} height={svgHeight}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
style={{ background: "#fffef5", display: "block" }} style={{ background: SVG_CONFIG.backgroundColor, display: "block" }}
> >
{/* Background with soft cream color */} {/* Background */}
<rect width={svgWidth} height={svgHeight} fill="#fffef5" /> <rect
width={svgWidth}
height={svgHeight}
fill={SVG_CONFIG.backgroundColor}
/>
{/* Title - centered at top */} {/* Title */}
{title && ( {title && (
<text <text
x={svgWidth / 2} x={svgWidth / 2}
y={padding + titleFontSize} y={SVG_CONFIG.padding + SVG_CONFIG.titleFontSize}
fontFamily="'Segoe UI', 'Arial Rounded MT Bold', Arial, sans-serif" fontFamily="'Segoe UI', 'Arial Rounded MT Bold', Arial, sans-serif"
fontSize={titleFontSize} fontSize={SVG_CONFIG.titleFontSize}
fontWeight="700" fontWeight="700"
fill="#5c4a72" fill="#5c4a72"
textAnchor="middle" textAnchor="middle"
@@ -312,13 +420,14 @@ Add quotes after numbers for bends"
</text> </text>
)} )}
{/* Render columns */} {/* Columns */}
{columns.map((column, colIndex) => { {columns.map((column, colIndex) => {
let yOffset = padding + titleHeight; let yOffset = SVG_CONFIG.padding + SVG_CONFIG.titleHeight;
// Calculate xOffset based on cumulative widths of previous columns
let xOffset = padding; // Calculate x position based on previous columns
let xOffset = SVG_CONFIG.padding;
for (let i = 0; i < colIndex; i++) { for (let i = 0; i < colIndex; i++) {
xOffset += columnWidths[i] + columnGap; xOffset += columnWidths[i] + SVG_CONFIG.columnGap;
} }
return ( return (
@@ -327,15 +436,15 @@ Add quotes after numbers for bends"
const currentY = yOffset; const currentY = yOffset;
if (line.isTab) { if (line.isTab) {
// Render tab line with monospace font - softer color // Tab line - monospace, larger font
yOffset += lineHeight; yOffset += SVG_CONFIG.lineHeight;
return ( return (
<text <text
key={lineIndex} key={lineIndex}
x={xOffset} x={xOffset}
y={currentY} y={currentY}
fontFamily="'Courier New', monospace" fontFamily="'Courier New', monospace"
fontSize={fontSize} fontSize={SVG_CONFIG.fontSize}
fontWeight="600" fontWeight="600"
fill="#3d5a6c" fill="#3d5a6c"
letterSpacing="0.5" letterSpacing="0.5"
@@ -344,15 +453,15 @@ Add quotes after numbers for bends"
</text> </text>
); );
} else if (line.content.trim()) { } else if (line.content.trim()) {
// Render annotation with smaller font - soft gray // Annotation line - smaller, italic
yOffset += lineHeight * 0.6; yOffset += SVG_CONFIG.lineHeight * 0.6;
return ( return (
<text <text
key={lineIndex} key={lineIndex}
x={xOffset} x={xOffset}
y={currentY} y={currentY}
fontFamily="Arial, sans-serif" fontFamily="Arial, sans-serif"
fontSize={annotationFontSize} fontSize={SVG_CONFIG.annotationFontSize}
fontStyle="italic" fontStyle="italic"
fill="#8b7e99" fill="#8b7e99"
> >
@@ -376,9 +485,6 @@ Add quotes after numbers for bends"
</button> </button>
</div> </div>
</div> </div>
</div>
</div>
); );
};
export default HarmonicaTabGenerator; export default HarmonicaTabGenerator;

View File

@@ -1,4 +1,6 @@
/* Game Freak-inspired soft pastel color scheme */ /* ============================================================================
CSS VARIABLES - Color Palette
============================================================================ */
:root { :root {
--cream: #fffef5; --cream: #fffef5;
--soft-purple: #5c4a72; --soft-purple: #5c4a72;
@@ -12,6 +14,9 @@
--accent-mint: #b5ead7; --accent-mint: #b5ead7;
} }
/* ============================================================================
GLOBAL STYLES
============================================================================ */
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -29,6 +34,9 @@ body {
line-height: 1.6; line-height: 1.6;
} }
/* ============================================================================
LAYOUT
============================================================================ */
.gradient-bg { .gradient-bg {
min-height: 100vh; min-height: 100vh;
background: linear-gradient( background: linear-gradient(
@@ -46,6 +54,9 @@ body {
margin: 0 auto; margin: 0 auto;
} }
/* ============================================================================
TYPOGRAPHY
============================================================================ */
.title { .title {
font-size: 2.5rem; font-size: 2.5rem;
font-weight: 800; font-weight: 800;
@@ -64,6 +75,19 @@ body {
font-weight: 500; font-weight: 500;
} }
.section-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--soft-purple);
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* ============================================================================
CARDS
============================================================================ */
.card { .card {
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
@@ -84,6 +108,7 @@ body {
transform: translateY(-2px); transform: translateY(-2px);
} }
/* Card Variants */
.instructions-card { .instructions-card {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
@@ -109,16 +134,9 @@ body {
); );
} }
.section-title { /* ============================================================================
font-size: 1.5rem; INSTRUCTIONS
font-weight: 700; ============================================================================ */
color: var(--soft-purple);
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.instructions { .instructions {
list-style: none; list-style: none;
display: flex; display: flex;
@@ -169,6 +187,9 @@ body {
font-weight: 600; font-weight: 600;
} }
/* ============================================================================
SETTINGS
============================================================================ */
.settings-grid { .settings-grid {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -205,6 +226,9 @@ body {
font-style: italic; font-style: italic;
} }
/* ============================================================================
FORM INPUTS
============================================================================ */
.input-field { .input-field {
padding: 0.875rem 1.125rem; padding: 0.875rem 1.125rem;
border: 2px solid var(--soft-gray); border: 2px solid var(--soft-gray);
@@ -223,6 +247,29 @@ body {
box-shadow: 0 0 0 4px rgba(137, 184, 212, 0.1); box-shadow: 0 0 0 4px rgba(137, 184, 212, 0.1);
} }
.textarea {
width: 100%;
padding: 1rem;
border: 2px solid var(--soft-gray);
border-radius: 16px;
font-family: "Courier New", monospace;
font-size: 1rem;
line-height: 1.8;
resize: vertical;
background: white;
color: var(--text-primary);
transition: all 0.2s ease;
}
.textarea:focus {
outline: none;
border-color: var(--pastel-blue);
box-shadow: 0 0 0 4px rgba(137, 184, 212, 0.1);
}
/* ============================================================================
SLIDER
============================================================================ */
.slider-container { .slider-container {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -293,26 +340,9 @@ body {
box-shadow: 0 2px 8px rgba(92, 74, 114, 0.2); box-shadow: 0 2px 8px rgba(92, 74, 114, 0.2);
} }
.textarea { /* ============================================================================
width: 100%; BUTTONS
padding: 1rem; ============================================================================ */
border: 2px solid var(--soft-gray);
border-radius: 16px;
font-family: "Courier New", monospace;
font-size: 1rem;
line-height: 1.8;
resize: vertical;
background: white;
color: var(--text-primary);
transition: all 0.2s ease;
}
.textarea:focus {
outline: none;
border-color: var(--pastel-blue);
box-shadow: 0 0 0 4px rgba(137, 184, 212, 0.1);
}
.button-group { .button-group {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
@@ -362,25 +392,9 @@ body {
background: linear-gradient(135deg, #99c4dc, var(--pastel-blue)); background: linear-gradient(135deg, #99c4dc, var(--pastel-blue));
} }
.tip { /* ============================================================================
margin-top: 1rem; PREVIEW
padding: 1rem; ============================================================================ */
background: linear-gradient(
135deg,
rgba(255, 154, 162, 0.1) 0%,
rgba(181, 234, 215, 0.1) 100%
);
border-radius: 12px;
color: var(--text-secondary);
font-size: 0.95rem;
border-left: 4px solid var(--accent-coral);
}
.tip strong {
color: var(--soft-purple);
font-weight: 600;
}
.preview-container { .preview-container {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@@ -402,7 +416,54 @@ body {
flex-shrink: 0; flex-shrink: 0;
} }
/* Mobile responsive */ /* ============================================================================
TIP
============================================================================ */
.tip {
margin-top: 1rem;
padding: 1rem;
background: linear-gradient(
135deg,
rgba(255, 154, 162, 0.1) 0%,
rgba(181, 234, 215, 0.1) 100%
);
border-radius: 12px;
color: var(--text-secondary);
font-size: 0.95rem;
border-left: 4px solid var(--accent-coral);
}
.tip strong {
color: var(--soft-purple);
font-weight: 600;
}
/* ============================================================================
SCROLLBAR
============================================================================ */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: var(--soft-gray);
border-radius: 8px;
}
::-webkit-scrollbar-thumb {
background: var(--pastel-blue);
border-radius: 8px;
border: 2px solid var(--soft-gray);
}
::-webkit-scrollbar-thumb:hover {
background: var(--soft-purple);
}
/* ============================================================================
RESPONSIVE
============================================================================ */
@media (max-width: 768px) { @media (max-width: 768px) {
.title { .title {
font-size: 2rem; font-size: 2rem;
@@ -425,24 +486,3 @@ body {
padding: 1rem; padding: 1rem;
} }
} }
/* Scrollbar styling */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: var(--soft-gray);
border-radius: 8px;
}
::-webkit-scrollbar-thumb {
background: var(--pastel-blue);
border-radius: 8px;
border: 2px solid var(--soft-gray);
}
::-webkit-scrollbar-thumb:hover {
background: var(--soft-purple);
}