Readme and technical documentation

This commit is contained in:
2025-12-27 22:46:58 +01:00
parent d4415c516e
commit 5ce02498c1
6 changed files with 222 additions and 60 deletions

View File

@@ -511,6 +511,28 @@ button {
color: var(--color-text-muted);
}
.link-button {
background: none;
border: none;
padding: 0;
color: var(--color-primary);
cursor: pointer;
font-family: inherit;
font-size: inherit;
text-decoration: none;
transition: color 0.2s;
}
.link-button:hover {
color: var(--color-primary-dark);
text-decoration: underline;
}
.separator {
margin: 0 8px;
color: #666;
}
.app-subtitle a {
color: var(--color-primary);
text-decoration: none;

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react';
import { SheetMusic } from './components/SheetMusic';
import { Controls, type AppSettings } from './components/Controls';
import { SettingsModal } from './components/SettingsModal';
import { OpenSourceModal } from './components/OpenSourceModal';
import { usePitchDetector } from './hooks/usePitchDetector';
import { useMetronome } from './hooks/useMetronome';
import { useAudioPlayer } from './hooks/useAudioPlayer';
@@ -66,6 +67,7 @@ function App() {
const [revealed, setRevealed] = useState(false);
const [virtualNote, setVirtualNote] = useState<number | null>(null);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isOpenSourceModalOpen, setIsOpenSourceModalOpen] = useState(false);
const currentTuning = TUNINGS[settings.tuningId];
const currentInstrumentDef = INSTRUMENT_DEFINITIONS[settings.instrument];
@@ -575,6 +577,10 @@ function App() {
currentPitch={pitchData ? { note: pitchData.note, cents: pitchData.cents } : null}
/>
<div className="app-subtitle">
<button className="link-button" onClick={() => setIsOpenSourceModalOpen(true)}>Open Source Libraries</button>
<span className="separator"></span>
<a href="https://github.com/9x/sheetmusictrainer" target="_blank" rel="noopener noreferrer">GitHub</a>
<span className="separator"></span>
<a href="http://jensmohrmann.de" target="_blank" rel="noopener noreferrer">jensmohrmann.de</a>
</div>
</footer>
@@ -633,6 +639,11 @@ function App() {
settings={settings}
onUpdateSettings={setSettings}
/>
<OpenSourceModal
isOpen={isOpenSourceModalOpen}
onClose={() => setIsOpenSourceModalOpen(false)}
/>
</div >
);
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { X } from 'lucide-react';
import '../styles/OpenSourceModal.css';
interface OpenSourceModalProps {
isOpen: boolean;
onClose: () => void;
}
const LIBRARIES = [
{ name: 'React', url: 'https://react.dev/', description: 'The library for web and native user interfaces' },
{ name: 'TypeScript', url: 'https://www.typescriptlang.org/', description: 'JavaScript with syntax for types' },
{ name: 'Vite', url: 'https://vitejs.dev/', description: 'Next Generation Frontend Tooling' },
{ name: 'VexFlow', url: 'https://www.vexflow.com/', description: 'Music notation rendering for the web' },
{ name: 'Pitchfinder', url: 'https://github.com/peterkhayes/pitchfinder', description: 'Pitch detection algorithms' },
{ name: 'Lucide React', url: 'https://lucide.dev/', description: 'Beautiful & consistent icon toolkit' },
];
export const OpenSourceModal: React.FC<OpenSourceModalProps> = ({ isOpen, onClose }) => {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content open-source-modal" onClick={e => e.stopPropagation()}>
<div className="modal-header">
<h2>Open Source Libraries</h2>
<button className="close-button" onClick={onClose} aria-label="Close">
<X size={24} />
</button>
</div>
<div className="modal-body">
<p>This project relies on these amazing open source libraries:</p>
<ul className="library-list">
{LIBRARIES.map((lib) => (
<li key={lib.name} className="library-item">
<a href={lib.url} target="_blank" rel="noopener noreferrer" className="library-name">
{lib.name}
</a>
<span className="library-desc">{lib.description}</span>
</li>
))}
</ul>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,48 @@
.open-source-modal {
max-width: 500px;
width: 90%;
}
.library-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 16px;
}
.library-item {
display: flex;
flex-direction: column;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: 12px;
}
.library-item:last-child {
border-bottom: none;
}
.library-name {
font-weight: 600;
color: #646cff;
/* Vite purple or accent color */
text-decoration: none;
font-size: 1.1rem;
margin-bottom: 4px;
}
.library-name:hover {
text-decoration: underline;
}
.library-desc {
color: #ccc;
font-size: 0.9rem;
}
@media (prefers-color-scheme: light) {
.library-item {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
}