Compare commits
4 Commits
7fc1c2c201
...
cf43adf63b
| Author | SHA1 | Date | |
|---|---|---|---|
| cf43adf63b | |||
| 58e9b4fa60 | |||
| 328c8fe98a | |||
| 5d5a75a735 |
@@ -62,6 +62,7 @@ export default async function SpecialPage({ params }: PageProps) {
|
|||||||
<Game
|
<Game
|
||||||
dailyPuzzle={dailyPuzzle}
|
dailyPuzzle={dailyPuzzle}
|
||||||
genre={decodedName}
|
genre={decodedName}
|
||||||
|
isSpecial={true}
|
||||||
maxAttempts={dailyPuzzle?.maxAttempts}
|
maxAttempts={dailyPuzzle?.maxAttempts}
|
||||||
unlockSteps={dailyPuzzle?.unlockSteps}
|
unlockSteps={dailyPuzzle?.unlockSteps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+49
-20
@@ -17,18 +17,20 @@ interface GameProps {
|
|||||||
coverImage: string | null;
|
coverImage: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
genre?: string | null;
|
genre?: string | null;
|
||||||
|
isSpecial?: boolean;
|
||||||
maxAttempts?: number;
|
maxAttempts?: number;
|
||||||
unlockSteps?: number[];
|
unlockSteps?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_UNLOCK_STEPS = [2, 4, 7, 11, 16, 30, 60];
|
const DEFAULT_UNLOCK_STEPS = [2, 4, 7, 11, 16, 30, 60];
|
||||||
|
|
||||||
export default function Game({ dailyPuzzle, genre = null, maxAttempts = 7, unlockSteps = DEFAULT_UNLOCK_STEPS }: GameProps) {
|
export default function Game({ dailyPuzzle, genre = null, isSpecial = false, maxAttempts = 7, unlockSteps = DEFAULT_UNLOCK_STEPS }: GameProps) {
|
||||||
const { gameState, statistics, addGuess } = useGameState(genre, maxAttempts);
|
const { gameState, statistics, addGuess } = useGameState(genre, maxAttempts);
|
||||||
const [hasWon, setHasWon] = useState(false);
|
const [hasWon, setHasWon] = useState(false);
|
||||||
const [hasLost, setHasLost] = useState(false);
|
const [hasLost, setHasLost] = useState(false);
|
||||||
const [shareText, setShareText] = useState('Share Result');
|
const [shareText, setShareText] = useState('🔗 Share');
|
||||||
const [lastAction, setLastAction] = useState<'GUESS' | 'SKIP' | null>(null);
|
const [lastAction, setLastAction] = useState<'GUESS' | 'SKIP' | null>(null);
|
||||||
|
const [isProcessingGuess, setIsProcessingGuess] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gameState && dailyPuzzle) {
|
if (gameState && dailyPuzzle) {
|
||||||
@@ -52,6 +54,9 @@ export default function Game({ dailyPuzzle, genre = null, maxAttempts = 7, unloc
|
|||||||
if (!gameState) return <div>Loading state...</div>;
|
if (!gameState) return <div>Loading state...</div>;
|
||||||
|
|
||||||
const handleGuess = (song: any) => {
|
const handleGuess = (song: any) => {
|
||||||
|
if (isProcessingGuess) return; // Prevent multiple guesses
|
||||||
|
|
||||||
|
setIsProcessingGuess(true);
|
||||||
setLastAction('GUESS');
|
setLastAction('GUESS');
|
||||||
if (song.id === dailyPuzzle.songId) {
|
if (song.id === dailyPuzzle.songId) {
|
||||||
addGuess(song.title, true);
|
addGuess(song.title, true);
|
||||||
@@ -65,6 +70,8 @@ export default function Game({ dailyPuzzle, genre = null, maxAttempts = 7, unloc
|
|||||||
sendGotifyNotification(maxAttempts, 'lost', dailyPuzzle.id, genre);
|
sendGotifyNotification(maxAttempts, 'lost', dailyPuzzle.id, genre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Reset after a short delay to allow UI update
|
||||||
|
setTimeout(() => setIsProcessingGuess(false), 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSkip = () => {
|
const handleSkip = () => {
|
||||||
@@ -82,7 +89,7 @@ export default function Game({ dailyPuzzle, genre = null, maxAttempts = 7, unloc
|
|||||||
|
|
||||||
const unlockedSeconds = unlockSteps[Math.min(gameState.guesses.length, unlockSteps.length - 1)];
|
const unlockedSeconds = unlockSteps[Math.min(gameState.guesses.length, unlockSteps.length - 1)];
|
||||||
|
|
||||||
const handleShare = () => {
|
const handleShare = async () => {
|
||||||
let emojiGrid = '';
|
let emojiGrid = '';
|
||||||
const totalGuesses = maxAttempts;
|
const totalGuesses = maxAttempts;
|
||||||
|
|
||||||
@@ -104,26 +111,48 @@ export default function Game({ dailyPuzzle, genre = null, maxAttempts = 7, unloc
|
|||||||
|
|
||||||
const speaker = hasWon ? '🔉' : '🔇';
|
const speaker = hasWon ? '🔉' : '🔇';
|
||||||
const genreText = genre ? `Genre: ${genre}\n` : '';
|
const genreText = genre ? `Genre: ${genre}\n` : '';
|
||||||
const text = `Hördle #${dailyPuzzle.id}\n${genreText}\n${speaker}${emojiGrid}\n\n#Hördle #Music\n\nhttps://hoerdle.elpatron.me`;
|
|
||||||
|
|
||||||
// Fallback method for copying to clipboard
|
// Generate URL with genre/special path
|
||||||
const textarea = document.createElement('textarea');
|
let shareUrl = 'https://hoerdle.elpatron.me';
|
||||||
textarea.value = text;
|
if (genre) {
|
||||||
textarea.style.position = 'fixed';
|
if (isSpecial) {
|
||||||
textarea.style.opacity = '0';
|
shareUrl += `/special/${encodeURIComponent(genre)}`;
|
||||||
document.body.appendChild(textarea);
|
} else {
|
||||||
textarea.select();
|
shareUrl += `/${encodeURIComponent(genre)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = `Hördle #${dailyPuzzle.id}\n${genreText}\n${speaker}${emojiGrid}\n\n#Hördle #Music\n\n${shareUrl}`;
|
||||||
|
|
||||||
|
// Try native Web Share API only on mobile devices
|
||||||
|
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
if (isMobile && navigator.share) {
|
||||||
|
try {
|
||||||
|
await navigator.share({
|
||||||
|
title: `Hördle #${dailyPuzzle.id}`,
|
||||||
|
text: text,
|
||||||
|
});
|
||||||
|
setShareText('✓ Shared!');
|
||||||
|
setTimeout(() => setShareText('🔗 Share'), 2000);
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
// User cancelled or error - fall through to clipboard
|
||||||
|
if ((err as Error).name !== 'AbortError') {
|
||||||
|
console.error('Share failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Copy to clipboard
|
||||||
try {
|
try {
|
||||||
document.execCommand('copy');
|
await navigator.clipboard.writeText(text);
|
||||||
setShareText('Copied!');
|
setShareText('✓ Copied!');
|
||||||
setTimeout(() => setShareText('Share Result'), 2000);
|
setTimeout(() => setShareText('🔗 Share'), 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to copy:', err);
|
console.error('Clipboard failed:', err);
|
||||||
setShareText('Copy failed');
|
setShareText('✗ Failed');
|
||||||
setTimeout(() => setShareText('Share Result'), 2000);
|
setTimeout(() => setShareText('🔗 Share'), 2000);
|
||||||
} finally {
|
|
||||||
document.body.removeChild(textarea);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -163,7 +192,7 @@ export default function Game({ dailyPuzzle, genre = null, maxAttempts = 7, unloc
|
|||||||
|
|
||||||
{!hasWon && !hasLost && (
|
{!hasWon && !hasLost && (
|
||||||
<>
|
<>
|
||||||
<GuessInput onGuess={handleGuess} disabled={false} />
|
<GuessInput onGuess={handleGuess} disabled={isProcessingGuess} />
|
||||||
{gameState.guesses.length < 6 ? (
|
{gameState.guesses.length < 6 ? (
|
||||||
<button
|
<button
|
||||||
onClick={handleSkip}
|
onClick={handleSkip}
|
||||||
|
|||||||
Reference in New Issue
Block a user