Fix iOS clipboard, update FAQ style, rename button text

- Fix iOS Safari clipboard by using textarea with proper selection
- Add visible "Link copied!" confirmation next to Copy link button
- Update FAQ modal styling to match Game Boy light palette
- Change "Use this →" to "Use" in search results

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
b1rdmania
2025-12-29 12:48:51 +00:00
parent 86192f94b9
commit aac5d9d95b
2 changed files with 84 additions and 64 deletions
+40 -49
View File
@@ -137,11 +137,11 @@
right: calc(var(--spacing-unit) * 2); right: calc(var(--spacing-unit) * 2);
} }
/* FAQ modal */ /* FAQ modal - Game Boy style */
.modal-backdrop { .modal-backdrop {
position: fixed; position: fixed;
inset: 0; inset: 0;
background: rgba(0,0,0,0.65); background: rgba(0,0,0,0.5);
display: none; display: none;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -151,10 +151,8 @@
.modal-backdrop.open { display: flex; } .modal-backdrop.open { display: flex; }
.modal { .modal {
width: min(720px, 100%); width: min(720px, 100%);
background: rgba(20,20,20,0.98); background: var(--color-surface);
border: 1px solid rgba(255,255,255,0.12); border: var(--pixel-border) solid var(--gb-dark);
border-radius: 12px;
box-shadow: 0 30px 120px rgba(0,0,0,0.75);
overflow: hidden; overflow: hidden;
} }
.modal header { .modal header {
@@ -162,14 +160,15 @@
align-items: center; align-items: center;
gap: 12px; gap: 12px;
padding: 14px 16px; padding: 14px 16px;
border-bottom: 1px solid rgba(255,255,255,0.08); border-bottom: 2px solid var(--gb-dark);
background: var(--gb-dark);
} }
.modal header h3 { .modal header h3 {
margin: 0; margin: 0;
font-size: 14px; font-size: 10px;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: rgba(255,255,255,0.9); color: var(--gb-lightest);
} }
.modal header .faq-header-links { .modal header .faq-header-links {
display: flex; display: flex;
@@ -182,32 +181,21 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 6px 10px; padding: 6px 10px;
border-radius: 999px; border: 2px solid var(--gb-light);
border: 1px solid rgba(255,255,255,0.12); background: var(--gb-darkest);
background: rgba(0,0,0,0.18); color: var(--gb-lightest);
color: rgba(255,255,255,0.82);
text-decoration: none; text-decoration: none;
font-size: 12px; font-size: 8px;
line-height: 1; line-height: 1;
box-shadow: none;
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
} }
.modal header .faq-header-links a:hover { .modal header .faq-header-links a:hover {
border-color: rgba(255,255,255,0.22); background: var(--gb-light);
box-shadow: 0 10px 30px rgba(0,0,0,0.45); color: var(--gb-darkest);
background: rgba(0,0,0,0.28);
}
.modal header .faq-header-links a:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(0,255,136,0.25), 0 12px 34px rgba(0,0,0,0.55);
border-color: rgba(0,255,136,0.45);
} }
.modal header .faq-header-links .dot { .modal header .faq-header-links .dot {
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 999px; background: var(--gb-lightest);
background: rgba(0,255,136,0.85);
box-shadow: 0 0 14px rgba(0,255,136,0.22);
flex: 0 0 auto; flex: 0 0 auto;
} }
.modal header .faq-header-links .label { .modal header .faq-header-links .label {
@@ -217,8 +205,7 @@
white-space: nowrap; white-space: nowrap;
} }
.modal header .faq-header-links .handle { .modal header .faq-header-links .handle {
color: rgba(0,255,136,0.9); color: var(--gb-lightest);
font-weight: 600;
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.modal header { .modal header {
@@ -227,19 +214,23 @@
} }
.modal header .faq-header-links a { .modal header .faq-header-links a {
padding: 6px 9px; padding: 6px 9px;
font-size: 12px; font-size: 8px;
} }
} }
.modal header .spacer { margin-left: auto; } .modal header .spacer { margin-left: auto; }
.modal header button { .modal header button {
padding: 8px 10px; padding: 8px 12px;
border-radius: 10px; background: var(--gb-light);
background: rgba(255,255,255,0.06); border: 2px solid var(--gb-lightest);
border: 1px solid rgba(255,255,255,0.12); color: var(--gb-darkest);
box-shadow: none; font-size: 8px;
}
.modal header button:hover {
background: var(--gb-lightest);
} }
.modal main { .modal main {
padding: 14px 16px 16px 16px; padding: 16px;
background: var(--gb-lightest);
} }
.faq-grid { .faq-grid {
display: grid; display: grid;
@@ -247,25 +238,24 @@
gap: 12px; gap: 12px;
} }
.faq-item { .faq-item {
border: 1px solid rgba(255,255,255,0.08); border: 2px solid var(--gb-dark);
border-radius: 12px; padding: 12px;
padding: 12px 12px; background: var(--color-surface);
background: rgba(0,0,0,0.18);
} }
.faq-item h4 { .faq-item h4 {
margin: 0 0 6px 0; margin: 0 0 8px 0;
font-size: 13px; font-size: 8px;
color: rgba(0,255,136,0.9); color: var(--gb-darkest);
} }
.faq-item p { .faq-item p {
margin: 0; margin: 0;
color: rgba(255,255,255,0.75); color: var(--gb-dark);
font-size: 13px; font-size: 8px;
line-height: 1.45; line-height: 2;
} }
.faq-item code { .faq-item code {
font-size: 12px; font-size: 8px;
color: rgba(255,255,255,0.85); color: var(--gb-darkest);
} }
.controls { .controls {
margin: calc(var(--spacing-unit) * 3) auto calc(var(--spacing-unit) * 2) auto; margin: calc(var(--spacing-unit) * 3) auto calc(var(--spacing-unit) * 2) auto;
@@ -1098,8 +1088,9 @@
</div> </div>
</div> </div>
<div style="margin-top: 14px;"> <div style="margin-top: 14px; display: flex; align-items: center; gap: 12px;">
<button id="copyLinkBtn" type="button" disabled>Copy link</button> <button id="copyLinkBtn" type="button" disabled>Copy link</button>
<span id="copyLinkConfirm" style="display: none; font-size: 8px; color: var(--gb-lightest);">Link copied!</span>
</div> </div>
</div> </div>
+44 -15
View File
@@ -58,6 +58,7 @@ class MotifApp {
private iosAudioState!: HTMLElement; private iosAudioState!: HTMLElement;
private copyLinkBtn!: HTMLButtonElement; private copyLinkBtn!: HTMLButtonElement;
private copyLinkConfirm!: HTMLElement;
// Embed snippet UI // Embed snippet UI
private embedSection: HTMLElement | null = null; private embedSection: HTMLElement | null = null;
@@ -134,6 +135,7 @@ class MotifApp {
this.motifDuration = document.getElementById('motifDuration')!; this.motifDuration = document.getElementById('motifDuration')!;
this.copyLinkBtn = document.getElementById('copyLinkBtn') as HTMLButtonElement; this.copyLinkBtn = document.getElementById('copyLinkBtn') as HTMLButtonElement;
this.copyLinkConfirm = document.getElementById('copyLinkConfirm') as HTMLElement;
// iOS audio unlock UI (Motif) // iOS audio unlock UI (Motif)
this.iosAudioBanner = document.getElementById('iosAudioBanner')!; this.iosAudioBanner = document.getElementById('iosAudioBanner')!;
@@ -353,7 +355,7 @@ class MotifApp {
<td class="duration-col">${this.formatDuration(result.parsed?.durationSec)}</td> <td class="duration-col">${this.formatDuration(result.parsed?.durationSec)}</td>
<td class="action-col"> <td class="action-col">
<div style="display:flex; gap: 10px; justify-content: flex-end; align-items:center;"> <div style="display:flex; gap: 10px; justify-content: flex-end; align-items:center;">
<button type="button" class="row-use-btn">Use this →</button> <button type="button" class="row-use-btn">Use</button>
</div> </div>
</td> </td>
`; `;
@@ -677,20 +679,41 @@ class MotifApp {
} }
private async copyToClipboard(text: string): Promise<void> { private async copyToClipboard(text: string): Promise<void> {
if (navigator.clipboard?.writeText) { // iOS Safari needs the textarea fallback - clipboard API is unreliable
await navigator.clipboard.writeText(text); // Try textarea approach first for better iOS compatibility
return;
}
const ta = document.createElement('textarea'); const ta = document.createElement('textarea');
ta.value = text; ta.value = text;
ta.style.position = 'fixed'; ta.style.position = 'fixed';
ta.style.left = '-9999px'; ta.style.left = '0';
ta.style.top = '0'; ta.style.top = '0';
ta.style.opacity = '0';
ta.style.pointerEvents = 'none';
ta.setAttribute('readonly', ''); // Prevent keyboard on iOS
document.body.appendChild(ta); document.body.appendChild(ta);
ta.focus();
ta.select(); // iOS specific selection
document.execCommand('copy'); const range = document.createRange();
range.selectNodeContents(ta);
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
ta.setSelectionRange(0, text.length); // iOS needs this
let success = false;
try {
success = document.execCommand('copy');
} catch {
success = false;
}
document.body.removeChild(ta); document.body.removeChild(ta);
// Fallback to modern API if execCommand failed
if (!success && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
}
} }
private async handleCopyLink(): Promise<void> { private async handleCopyLink(): Promise<void> {
@@ -739,13 +762,19 @@ class MotifApp {
try { try {
this.copyLinkBtn.disabled = true; this.copyLinkBtn.disabled = true;
await this.copyToClipboard(shareUrl); await this.copyToClipboard(shareUrl);
const prev = this.copyLinkBtn.textContent || 'Copy link'; // Show confirmation
this.copyLinkBtn.textContent = 'Copied'; this.copyLinkConfirm.style.display = 'inline';
this.copyLinkConfirm.textContent = 'Link copied!';
window.setTimeout(() => { window.setTimeout(() => {
this.copyLinkBtn.textContent = prev; this.copyLinkConfirm.style.display = 'none';
}, 900); }, 3000);
} catch { } catch (err) {
// keep quiet; clipboard can fail in some contexts // Show error feedback
this.copyLinkConfirm.style.display = 'inline';
this.copyLinkConfirm.textContent = 'Copy failed';
window.setTimeout(() => {
this.copyLinkConfirm.style.display = 'none';
}, 3000);
} finally { } finally {
this.copyLinkBtn.disabled = false; this.copyLinkBtn.disabled = false;
} }