From 161516ffad6999dc24db7e5ed8e1ebd815ff1ec1 Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Sun, 9 Mar 2025 21:31:52 +0100 Subject: Add embed pages --- src/App.tsx | 4 + src/pages/chart/ChartEmbedPage.tsx | 162 +++++++++++++++++++++++++++++++++++++ src/pages/embed/EmbedPage.tsx | 36 +++++++++ src/styles.css | 39 +++++++++ 4 files changed, 241 insertions(+) create mode 100644 src/pages/chart/ChartEmbedPage.tsx create mode 100644 src/pages/embed/EmbedPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 64c411c..da689fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,8 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router' import HomePage from './pages/HomePage' import ChartPage from './pages/chart/ChartPage' +import EmbedPage from './pages/embed/EmbedPage' +import ChartEmbedPage from './pages/chart/ChartEmbedPage' import './styles.css' function App() { @@ -9,6 +11,8 @@ function App() { } /> } /> + } /> + } /> ) diff --git a/src/pages/chart/ChartEmbedPage.tsx b/src/pages/chart/ChartEmbedPage.tsx new file mode 100644 index 0000000..4b485b7 --- /dev/null +++ b/src/pages/chart/ChartEmbedPage.tsx @@ -0,0 +1,162 @@ +import { useEffect, useRef } from 'react'; +import cw from 'cw'; +import '../../styles.css'; + +declare global { + interface Window { + cw: any; + } +} + +const ChartEmbedPage = () => { + const actxRef = useRef(null); + + // Add meta tag for WebView compatibility and set page title + useEffect(() => { + // Set viewport for better mobile display + const viewportMeta = document.createElement('meta'); + viewportMeta.name = 'viewport'; + viewportMeta.content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'; + document.head.appendChild(viewportMeta); + + // Set canonical URL + const canonicalLink = document.createElement('link'); + canonicalLink.rel = 'canonical'; + canonicalLink.href = 'https://morsehero.com/charts/embed'; + document.head.appendChild(canonicalLink); + + // Set page title + document.title = 'Morse Code Chart | Embed'; + + return () => { + document.head.removeChild(viewportMeta); + document.head.removeChild(canonicalLink); + }; + }, []); + + // Initialize audio context + const initAudio = () => { + if (actxRef.current) return; // Already initialized + + try { + actxRef.current = cw.initAudioContext({ tone: 600 }); + } catch (error) { + console.error('Failed to initialize audio context:', error); + } + }; + + // Play Morse code for a character + const playCharacter = (character: string) => { + // Initialize audio on first user interaction + if (!actxRef.current) { + initAudio(); + } + + if (actxRef.current) { + // Remove 'playing' class from all items + document.querySelectorAll('.morse-item').forEach(item => { + item.classList.remove('playing'); + }); + + // Add 'playing' class to the clicked item + const clickedItem = document.querySelector(`.morse-item[data-char="${character}"]`); + if (clickedItem) { + clickedItem.classList.add('playing'); + } + + cw.play(character, { + wpm: 20, + actx: actxRef.current + }); + + // Remove the 'playing' class after the audio finishes + setTimeout(() => { + if (clickedItem) { + clickedItem.classList.remove('playing'); + } + }, 2000); + } + }; + + // Add click event listeners to all morse items + useEffect(() => { + const handleMorseItemClick = (e: Event) => { + const target = e.currentTarget as HTMLElement; + const character = target.getAttribute('data-char'); + if (character) { + playCharacter(character); + } + }; + + const morseItems = document.querySelectorAll('.morse-item'); + morseItems.forEach(item => { + item.addEventListener('click', handleMorseItemClick); + }); + + return () => { + morseItems.forEach(item => { + item.removeEventListener('click', handleMorseItemClick); + }); + }; + }, []); + + return ( +
+

Morse Code Reference

+ +
+

Letters

+
+ {[...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'].map(char => ( +
+
{char}
+
{getMorseCode(char)}
+
+ ))} +
+
+ +
+

Numbers

+
+ {[...'1234567890'].map(char => ( +
+
{char}
+
{getMorseCode(char)}
+
+ ))} +
+
+ +
+

Special Characters

+
+ {[...'.=,?/'].map(char => ( +
+
{char}
+
{getMorseCode(char)}
+
+ ))} +
+
+
+ ); +}; + +// Helper function to get morse code representation +const getMorseCode = (char: string) => { + const morseMap: { [key: string]: string } = { + 'A': '·−', 'B': '−···', 'C': '−·−·', 'D': '−··', 'E': '·', + 'F': '··−·', 'G': '−−·', 'H': '····', 'I': '··', 'J': '·−−−', + 'K': '−·−', 'L': '·−··', 'M': '−−', 'N': '−·', 'O': '−−−', + 'P': '·−−·', 'Q': '−−·−', 'R': '·−·', 'S': '···', 'T': '−', + 'U': '··−', 'V': '···−', 'W': '·−−', 'X': '−··−', 'Y': '−·−−', + 'Z': '−−··', '0': '−−−−−', '1': '·−−−−', '2': '··−−−', '3': '···−−', + '4': '····−', '5': '·····', '6': '−····', '7': '−−···', '8': '−−−··', + '9': '−−−−·', '.': '·−·−·−', ',': '−−··−−', '?': '··−−··', '/': '−··−·', '=': '−···−' + }; + + return morseMap[char] || ''; +}; + +export default ChartEmbedPage; diff --git a/src/pages/embed/EmbedPage.tsx b/src/pages/embed/EmbedPage.tsx new file mode 100644 index 0000000..15a28ad --- /dev/null +++ b/src/pages/embed/EmbedPage.tsx @@ -0,0 +1,36 @@ +import { useEffect } from 'react'; +import MorseGame from '../../components/MorseGame'; +import '../../styles.css'; + +const EmbedPage = () => { + // Add meta tag for WebView compatibility + useEffect(() => { + // Set viewport for better mobile display + const viewportMeta = document.createElement('meta'); + viewportMeta.name = 'viewport'; + viewportMeta.content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'; + document.head.appendChild(viewportMeta); + + // Set canonical URL + const canonicalLink = document.createElement('link'); + canonicalLink.rel = 'canonical'; + canonicalLink.href = 'https://morsehero.com/embed'; + document.head.appendChild(canonicalLink); + + // Set page title + document.title = 'Morse Hero | Embed'; + + return () => { + document.head.removeChild(viewportMeta); + document.head.removeChild(canonicalLink); + }; + }, []); + + return ( +
+ +
+ ); +}; + +export default EmbedPage; diff --git a/src/styles.css b/src/styles.css index 1a84f4d..e590bba 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1127,6 +1127,7 @@ body.game-active #startScreen { border: 1px solid var(--card-border); backdrop-filter: blur(10px); animation: fadeIn 0.5s ease-out; + text-align: left; } .chart-section h2 { @@ -1634,6 +1635,44 @@ body.game-active #startScreen { box-shadow: var(--shadow-sm); } +/* Embed styles */ +.embed-container { + max-width: 100%; + padding: 10px; + margin: 0 auto; + box-sizing: border-box; +} + +.embed-title { + font-size: 1.5rem; + text-align: center; + margin-bottom: 1rem; +} + +/* Responsive adjustments for embedded views */ +@media (max-width: 480px) { + .embed-container .morse-grid { + grid-template-columns: repeat(3, 1fr); + } + + .embed-container .chart-section { + margin-bottom: 1rem; + } + + .embed-container .chart-section h2 { + font-size: 1.2rem; + margin-bottom: 0.5rem; + } + + .embed-container .game-container { + padding: 10px; + } + + .embed-container .options { + grid-template-columns: repeat(2, 1fr); + } +} + @media (max-width: 768px) { .morse-grid { grid-template-columns: repeat(3, 1fr); -- cgit v1.3.1