Improve mobile UI

This commit is contained in:
2025-12-30 10:24:25 +01:00
parent 6f67769974
commit 96b236acee
4 changed files with 214 additions and 47 deletions

View File

@@ -10,7 +10,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: var(--spacing-sm) var(--spacing-xl); padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--color-surface); background-color: var(--color-surface);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
@@ -23,6 +23,12 @@
color: var(--color-primary); color: var(--color-primary);
} }
@media (max-width: 400px) {
.logo {
display: none;
}
}
.streak-badge { .streak-badge {
background-color: var(--color-primary-dark); background-color: var(--color-primary-dark);
color: white; color: white;
@@ -588,6 +594,22 @@ button {
transition: all 0.2s; transition: all 0.2s;
} }
@media (max-width: 450px) {
.toggle-option {
padding: 4px 8px;
font-size: 0.75rem;
}
/* Hide help button in favor of settings on small screens */
.help-btn {
display: none;
}
.header-controls {
gap: 4px;
}
}
.toggle-option.active { .toggle-option.active {
background-color: var(--color-surface); background-color: var(--color-surface);
color: var(--color-primary); color: var(--color-primary);
@@ -636,27 +658,20 @@ button {
justify-content: center; justify-content: center;
/* Center vertically */ /* Center vertically */
gap: 0; gap: 0;
height: 100vh; /* Allow scrolling */
/* Fixed viewport height */ height: auto;
overflow: hidden; min-height: 100vh;
/* No scrolling for main game loop */ overflow-y: auto;
position: relative; display: block;
} }
.card.sheet-music-card { .card.sheet-music-card {
max-width: 95vw; max-width: 100vw;
height: 100%; width: 100vw;
max-height: none; min-height: 100vh;
flex-direction: column; margin: 0;
flex-wrap: nowrap; border-radius: 0;
justify-content: center; align-items: stretch;
align-items: center;
padding: 0;
width: 100%;
background: transparent;
box-shadow: none;
border: none;
flex-shrink: 0;
} }
/* Staff Row: Horizontal Layout for Staff + Note Name */ /* Staff Row: Horizontal Layout for Staff + Note Name */
@@ -691,9 +706,8 @@ button {
/* Scale UP */ /* Scale UP */
transform-origin: center center; transform-origin: center center;
justify-content: center; justify-content: center;
margin-bottom: -10px; margin-bottom: 0;
/* Pull fretboard closer */ padding-top: 10px;
padding-top: 0;
z-index: 1; z-index: 1;
} }
@@ -704,19 +718,16 @@ button {
/* Hint card container (Fretboard + Note Name) */ /* Hint card container (Fretboard + Note Name) */
.hint-card { .hint-card {
margin-top: 0; margin-top: 0;
flex: 1 1 auto; flex: 1;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
padding: 0 10px; padding: 0;
background: transparent;
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: center; justify-content: flex-start;
align-items: center; align-items: center;
gap: 8px;
min-height: 0; min-height: 0;
max-height: 200px; height: auto;
/* Cap fretboard height */
} }
/* The note name inside hint card */ /* The note name inside hint card */
@@ -734,9 +745,15 @@ button {
.fretboard-container { .fretboard-container {
transform: none; transform: none;
max-width: none; max-width: none;
width: 100%;
flex-grow: 1; flex-grow: 1;
height: 100%; display: flex;
/* Ensure it fills the space but respects container limits */ align-items: center;
justify-content: center;
background: #2a2a2a;
/* distinct bg for full width feel */
min-height: 200px;
padding: 10px 0;
} }
/* Hide Instruction Text ("Play note above") */ /* Hide Instruction Text ("Play note above") */
@@ -780,27 +797,78 @@ button {
/* Break flex height lock */ /* Break flex height lock */
} }
/* Action Buttons - floating or tucked */
.action-row { .action-row {
margin-top: 0 !important; margin-top: 20px !important;
width: 100%; width: 100%;
padding-bottom: 5px; padding: 20px;
flex-shrink: 0; position: static;
position: absolute; /* Flow naturally below */
bottom: 2px; display: flex;
z-index: 20; flex-direction: row;
pointer-events: none;
/* Pass through clicks */
justify-content: center; justify-content: center;
gap: 20px; gap: 16px;
background: var(--color-bg);
pointer-events: auto;
} }
.action-row button { .action-row button {
/* Reset styles for scrolling row */
pointer-events: auto; pointer-events: auto;
/* Enable buttons */ width: auto;
background: rgba(255, 255, 255, 0.9); height: auto;
border: 1px solid #ccc; background: transparent;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); border: none;
box-shadow: none;
border-radius: 0;
padding: 10px;
display: flex;
flex-direction: column;
/* Stack icon/text */
align-items: center;
color: var(--color-text-main);
}
/* Specific Floating Skip Button logic */
.skip-button {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 999;
background-color: var(--color-primary) !important;
color: white !important;
width: 56px !important;
height: 56px !important;
border-radius: 50% !important;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
border: none !important;
}
/* Hide text in floating button, show icon */
.skip-button svg {
width: 24px !important;
height: 24px !important;
margin: 0;
}
.skip-button:active {
transform: scale(0.95);
}
/* Show text labels in scrollable row for others */
.action-row button span,
.action-row button text {
display: block;
font-size: 0.7rem;
margin-top: 4px;
}
/* Adjust icon sizes */
.action-row button svg {
width: 20px;
height: 20px;
} }
/* Restore Zen Toggle in Landscape Mobile */ /* Restore Zen Toggle in Landscape Mobile */

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { Logo } from './components/Logo'; import { Logo } from './components/Logo';
import { LandscapeSuggestion } from './components/LandscapeSuggestion';
import { SheetMusic } from './components/SheetMusic'; import { SheetMusic } from './components/SheetMusic';
@@ -399,6 +400,7 @@ function App() {
return ( return (
<div className={`app-container ${settings.zenMode ? 'zen-mode' : ''}`}> <div className={`app-container ${settings.zenMode ? 'zen-mode' : ''}`}>
<LandscapeSuggestion />
{!settings.zenMode && ( {!settings.zenMode && (
<header className="app-header"> <header className="app-header">
<div className="app-header-left"> <div className="app-header-left">
@@ -422,7 +424,7 @@ function App() {
</div> </div>
<button <button
className="icon-button" className="icon-button help-btn"
onClick={() => setShowHelp(true)} onClick={() => setShowHelp(true)}
title="Shortcuts Help" title="Shortcuts Help"
> >

View File

@@ -0,0 +1,29 @@
import React, { useState, useEffect } from 'react';
import { Smartphone } from 'lucide-react';
import '../styles/landscape-suggestion.css';
export const LandscapeSuggestion: React.FC = () => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const checkOrientation = () => {
const isMobile = window.innerWidth <= 768;
const isPortrait = window.innerHeight > window.innerWidth;
setIsVisible(isMobile && isPortrait);
};
checkOrientation();
window.addEventListener('resize', checkOrientation);
return () => window.removeEventListener('resize', checkOrientation);
}, []);
if (!isVisible) return null;
return (
<div className="landscape-suggestion">
<Smartphone size={24} className="rotate-icon" />
<span>For the best experience, please rotate your device to landscape.</span>
<button className="dismiss-btn" onClick={() => setIsVisible(false)}>×</button>
</div>
);
};

View File

@@ -0,0 +1,68 @@
.landscape-suggestion {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.85);
color: white;
padding: 12px 20px;
border-radius: 30px;
display: flex;
align-items: center;
gap: 12px;
z-index: 9999;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
width: 90%;
max-width: 400px;
animation: slideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1);
font-size: 0.9rem;
line-height: 1.4;
}
.rotate-icon {
animation: rotate 2s infinite ease-in-out;
flex-shrink: 0;
}
.dismiss-btn {
background: none;
border: none;
color: rgba(255, 255, 255, 0.6);
font-size: 1.5rem;
cursor: pointer;
margin-left: auto;
padding: 0 4px;
line-height: 1;
}
.dismiss-btn:hover {
color: white;
}
@keyframes slideUp {
from {
transform: translate(-50%, 100%);
opacity: 0;
}
to {
transform: translate(-50%, 0);
opacity: 1;
}
}
@keyframes rotate {
0%,
100% {
transform: rotate(0deg);
}
25% {
transform: rotate(90deg);
}
75% {
transform: rotate(90deg);
}
}