Add FAQ modal and link in header.
This commit is contained in:
+131
-1
@@ -73,6 +73,103 @@
|
|||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
text-shadow: var(--glow-primary);
|
text-shadow: var(--glow-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: calc(var(--spacing-unit) * 2);
|
||||||
|
margin-bottom: calc(var(--spacing-unit) * 2);
|
||||||
|
}
|
||||||
|
.topbar h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.faq-link {
|
||||||
|
margin-left: auto;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(255,255,255,0.14);
|
||||||
|
color: rgba(255,255,255,0.85);
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.faq-link:hover:not(:disabled) {
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border-color: rgba(255,255,255,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FAQ modal */
|
||||||
|
.modal-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.65);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.modal-backdrop.open { display: flex; }
|
||||||
|
.modal {
|
||||||
|
width: min(720px, 100%);
|
||||||
|
background: rgba(20,20,20,0.98);
|
||||||
|
border: 1px solid rgba(255,255,255,0.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 30px 120px rgba(0,0,0,0.75);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.modal header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||||
|
}
|
||||||
|
.modal header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(255,255,255,0.9);
|
||||||
|
}
|
||||||
|
.modal header .spacer { margin-left: auto; }
|
||||||
|
.modal header button {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255,255,255,0.06);
|
||||||
|
border: 1px solid rgba(255,255,255,0.12);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.modal main {
|
||||||
|
padding: 14px 16px 16px 16px;
|
||||||
|
}
|
||||||
|
.faq-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.faq-item {
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 12px;
|
||||||
|
background: rgba(0,0,0,0.18);
|
||||||
|
}
|
||||||
|
.faq-item h4 {
|
||||||
|
margin: 0 0 6px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(0,255,136,0.9);
|
||||||
|
}
|
||||||
|
.faq-item p {
|
||||||
|
margin: 0;
|
||||||
|
color: rgba(255,255,255,0.75);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
.faq-item code {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255,255,255,0.85);
|
||||||
|
}
|
||||||
.controls {
|
.controls {
|
||||||
margin: calc(var(--spacing-unit) * 3) 0;
|
margin: calc(var(--spacing-unit) * 3) 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -662,7 +759,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>🎵 MOTIF</h1>
|
<div class="topbar">
|
||||||
|
<h1>🎵 MOTIF</h1>
|
||||||
|
<button id="faqBtn" class="faq-link" type="button">FAQ</button>
|
||||||
|
</div>
|
||||||
<p>Procedural Music Synthesis from MIDI Structure</p>
|
<p>Procedural Music Synthesis from MIDI Structure</p>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@@ -782,6 +882,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="faqModalBackdrop" class="modal-backdrop" role="dialog" aria-modal="true" aria-labelledby="faqTitle">
|
||||||
|
<div class="modal" role="document">
|
||||||
|
<header>
|
||||||
|
<h3 id="faqTitle">FAQ</h3>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button id="faqCloseBtn" type="button">Close</button>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="faq-grid">
|
||||||
|
<div class="faq-item">
|
||||||
|
<h4>What is this?</h4>
|
||||||
|
<p>A hobby project exploring <strong>MIDI → retro synthesis</strong>. Search a song, preview its MIDI, then generate a chiptune-style variation.</p>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<h4>How was it built?</h4>
|
||||||
|
<p><strong>Vite + TypeScript</strong> frontend using <strong>Web Audio</strong> (procedural synth) plus a <strong>Soundfont</strong> piano preview. Backend is a small <strong>Express</strong> API (search/fetch/parse) deployed on Vercel.</p>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<h4>Why do I need “Enable Audio” on iOS?</h4>
|
||||||
|
<p>iOS Safari requires a user tap before audio can play. Tap <strong>Enable Audio</strong>, then press <strong>Generate & Play</strong>. If it’s still silent: disable Silent Mode / raise volume / disconnect Bluetooth.</p>
|
||||||
|
</div>
|
||||||
|
<div class="faq-item">
|
||||||
|
<h4>How can we develop it further?</h4>
|
||||||
|
<p>Add more MIDI sources, better matching, and style presets (ambient / dance / ominous). Then ship a compact <code>/embed</code> widget and an SDK so other sites can drop in “pixelised music”.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+30
@@ -52,6 +52,11 @@ class MotifApp {
|
|||||||
private copyEmbedBtn: HTMLButtonElement | null = null;
|
private copyEmbedBtn: HTMLButtonElement | null = null;
|
||||||
private copyToast: HTMLElement | null = null;
|
private copyToast: HTMLElement | null = null;
|
||||||
|
|
||||||
|
// FAQ modal
|
||||||
|
private faqBtn!: HTMLButtonElement;
|
||||||
|
private faqBackdrop!: HTMLElement;
|
||||||
|
private faqCloseBtn!: HTMLButtonElement;
|
||||||
|
|
||||||
private searchResults: any[] = [];
|
private searchResults: any[] = [];
|
||||||
private selectedResultIndex = 0;
|
private selectedResultIndex = 0;
|
||||||
private currentMIDI: { events: NoteEvent[], metadata: any } | null = null;
|
private currentMIDI: { events: NoteEvent[], metadata: any } | null = null;
|
||||||
@@ -116,6 +121,11 @@ class MotifApp {
|
|||||||
this.embedCodeEl = document.getElementById('embedCode');
|
this.embedCodeEl = document.getElementById('embedCode');
|
||||||
this.copyEmbedBtn = document.getElementById('copyEmbedBtn') as HTMLButtonElement | null;
|
this.copyEmbedBtn = document.getElementById('copyEmbedBtn') as HTMLButtonElement | null;
|
||||||
this.copyToast = document.getElementById('copyToast');
|
this.copyToast = document.getElementById('copyToast');
|
||||||
|
|
||||||
|
// FAQ modal
|
||||||
|
this.faqBtn = document.getElementById('faqBtn') as HTMLButtonElement;
|
||||||
|
this.faqBackdrop = document.getElementById('faqModalBackdrop')!;
|
||||||
|
this.faqCloseBtn = document.getElementById('faqCloseBtn') as HTMLButtonElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupEventListeners(): void {
|
private setupEventListeners(): void {
|
||||||
@@ -159,6 +169,26 @@ class MotifApp {
|
|||||||
const enable = () => void this.handleEnableAudio();
|
const enable = () => void this.handleEnableAudio();
|
||||||
this.enableAudioBtn.addEventListener('click', enable);
|
this.enableAudioBtn.addEventListener('click', enable);
|
||||||
this.enableAudioBtn.addEventListener('touchend', enable, { passive: true });
|
this.enableAudioBtn.addEventListener('touchend', enable, { passive: true });
|
||||||
|
|
||||||
|
// FAQ
|
||||||
|
this.faqBtn.addEventListener('click', () => this.openFaq());
|
||||||
|
this.faqCloseBtn.addEventListener('click', () => this.closeFaq());
|
||||||
|
this.faqBackdrop.addEventListener('click', (e) => {
|
||||||
|
if (e.target === this.faqBackdrop) this.closeFaq();
|
||||||
|
});
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') this.closeFaq();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private openFaq(): void {
|
||||||
|
this.faqBackdrop.classList.add('open');
|
||||||
|
// focus close for keyboard users
|
||||||
|
this.faqCloseBtn.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeFaq(): void {
|
||||||
|
this.faqBackdrop.classList.remove('open');
|
||||||
}
|
}
|
||||||
|
|
||||||
private isIOSLike(): boolean {
|
private isIOSLike(): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user