diff --git a/css/style.css b/css/style.css index 8de0bf2..e143abe 100644 --- a/css/style.css +++ b/css/style.css @@ -104,3 +104,157 @@ html, body { font-size: 12px; letter-spacing: 0.02em; } + +/* Mobile on-screen keyboard */ +.mobile-keyboard { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(180deg, rgba(10, 13, 18, 0.98) 0%, rgba(7, 8, 11, 0.98) 100%); + backdrop-filter: blur(12px); + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding: 12px 8px 12px 8px; + display: none; + flex-direction: column; + gap: 8px; + z-index: 1000; + box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.7); +} + +.mobile-keyboard.active { + display: flex; +} + +.keyboard-row { + display: flex; + gap: 6px; + justify-content: center; +} + +.keyboard-key { + flex: 1; + max-width: 46px; + height: 44px; + background: linear-gradient(180deg, #1a2233 0%, #0f1621 100%); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 6px; + color: #d7e7ff; + font-family: ui-monospace, SFMono-Regular, monospace; + font-size: 16px; + font-weight: 600; + cursor: pointer; + user-select: none; + display: flex; + align-items: center; + justify-content: center; + touch-action: manipulation; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); +} + +.keyboard-key:active { + background: linear-gradient(180deg, #0f1621 0%, #1a2233 100%); + transform: translateY(1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); +} + +.keyboard-key.wide { + flex: 2; + max-width: none; +} + +.keyboard-key.space { + flex: 4; + max-width: none; +} + +.keyboard-key.enter { + background: linear-gradient(180deg, #0a84ff 0%, #0766cc 100%); + border-color: rgba(10, 132, 255, 0.4); +} + +.keyboard-key.enter:active { + background: linear-gradient(180deg, #0766cc 0%, #0a84ff 100%); +} + +.keyboard-key.backspace { + background: linear-gradient(180deg, #cc3333 0%, #992222 100%); + border-color: rgba(204, 51, 51, 0.4); +} + +.keyboard-key.backspace:active { + background: linear-gradient(180deg, #992222 0%, #cc3333 100%); +} + +/* Mobile-specific adjustments */ +@media (max-width: 768px) { + .wrap { + padding: 12px; + padding-bottom: 0; + } + + .crt-shell { + width: 100%; + max-width: 100%; + aspect-ratio: 4 / 3; + border-radius: 12px; + padding: 8px; + box-shadow: + 0 8px 24px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.06) inset; + } + + .crt-overlay { + inset: 8px; + border-radius: 8px; + } + + #crt { + border-radius: 8px; + } + + .hint { + margin-top: 8px; + font-size: 10px; + } +} + +@media (max-width: 480px) { + .wrap { + padding: 8px; + padding-bottom: 0; + } + + .crt-shell { + border-radius: 8px; + padding: 6px; + } + + .crt-overlay { + inset: 6px; + border-radius: 6px; + } + + #crt { + border-radius: 6px; + } + + .keyboard-key { + max-width: 38px; + height: 38px; + font-size: 14px; + } +} + +/* Landscape mobile adjustments */ +@media (max-width: 900px) and (max-height: 500px) { + .wrap { + padding: 8px; + } + + .crt-shell { + width: auto; + height: calc(100vh - 16px); + aspect-ratio: 4 / 3; + } +} diff --git a/index.html b/index.html index 8b1ae8f..a3d6868 100644 --- a/index.html +++ b/index.html @@ -24,6 +24,59 @@ + +
+
+ + + + + + + + + + +
+
+ + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + +
+
+ + +
+
+ diff --git a/js/main.js b/js/main.js index f8320ae..e6c62cf 100644 --- a/js/main.js +++ b/js/main.js @@ -7,17 +7,80 @@ if (!canvas) { // Fail clearly if the element is missing throw new Error('Canvas element with id "crt" was not found.'); // Throw a helpful error } // End guard +// Detect if we're on a touch device +const isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); + +// Mobile keyboard handling +const mobileKeyboard = document.getElementById("mobile-keyboard"); +let keyboardVisible = false; + +function showMobileKeyboard() { + if (isTouchDevice && mobileKeyboard) { + mobileKeyboard.classList.add("active"); + keyboardVisible = true; + } +} + +function hideMobileKeyboard() { + if (isTouchDevice && mobileKeyboard) { + mobileKeyboard.classList.remove("active"); + keyboardVisible = false; + } +} + +// Hook into terminal's input mode +const originalSetInputMode = TeletextTerminal.prototype.setInputMode; +TeletextTerminal.prototype.setInputMode = function(on) { + originalSetInputMode.call(this, on); + if (on) { + showMobileKeyboard(); + } else { + hideMobileKeyboard(); + } +}; + const term = new TeletextTerminal(canvas, { // Create a terminal instance cols: 40, // Teletext columns rows: 25, // Teletext rows scale: 3 // Upscaling factor (keeps the chunky look) }); // End terminal creation +// Wire up mobile keyboard buttons +if (isTouchDevice && mobileKeyboard) { + mobileKeyboard.addEventListener("click", (e) => { + const key = e.target.closest(".keyboard-key"); + if (!key) return; + + const keyValue = key.dataset.key; + if (!keyValue) return; + + // Simulate keyboard events + if (keyValue === "enter") { + term._pushKey({ type: "enter" }); + } else if (keyValue === "backspace") { + term._pushKey({ type: "backspace" }); + } else { + term._pushKey({ type: "char", ch: keyValue }); + } + + // Visual feedback + key.style.transform = "scale(0.95)"; + setTimeout(() => { + key.style.transform = ""; + }, 100); + }); +} + term.start(); // Start the render loop -// Make it easy to “wake” key handling on click (handy on Safari). // Note +// Make it easy to "wake" key handling on click (handy on Safari). // Note canvas.addEventListener("pointerdown", () => { // On click/tap canvas.focus(); // Focus the canvas (helps some browsers deliver key events) + + // On mobile, tapping the canvas acts like pressing Enter to advance + if (isTouchDevice && !keyboardVisible) { + term._pushKey({ type: "enter" }); + } }); // End listener // Run the story with top-level error handling. // Note