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:
+40
-49
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user