Make game creation wizard fit viewport without scrolling

Replace scrollable form content with responsive sizing that
automatically scales elements to fit available viewport height.

CSS improvements:
- Disable scrolling: overflow-y:auto → overflow:hidden in form-content
- Implement fluid typography with clamp() for titles, labels, buttons
- Add responsive spacing using clamp() for margins and padding
- Scale progress dots from 10px-16px based on viewport height
- Reduce button dimensions (60px min-width, 36px min-height)
- Enable element shrinking with flex-shrink:1 and min-height:0

Component cleanup:
- Remove auto-focus useEffect from Player1/2/3Step components
- Prevents unwanted layout shifts on wizard mount

Benefits:
- All elements visible without scrolling
- Responsive design scales smoothly across viewport sizes
- Cleaner UX with no scrollbars in form wizard
- Better space utilization on small screens
This commit is contained in:
Frank Schwenk
2025-11-07 14:30:39 +01:00
parent 076d6ced36
commit 99be99d120
4 changed files with 30 additions and 36 deletions

View File

@@ -21,10 +21,10 @@
min-height: 0; min-height: 0;
} }
.screen-title { .screen-title {
font-size: var(--font-size-xxl); font-size: clamp(1.25rem, 3vh, 1.5rem);
font-weight: 700; font-weight: 700;
color: var(--color-text); color: var(--color-text);
margin-bottom: var(--space-xl); margin-bottom: clamp(0.5rem, 2vh, 2rem);
letter-spacing: 0.5px; letter-spacing: 0.5px;
text-align: center; text-align: center;
} }
@@ -34,6 +34,8 @@
gap: var(--space-lg); gap: var(--space-lg);
width: 100%; width: 100%;
margin-bottom: var(--space-xl); margin-bottom: var(--space-xl);
flex-shrink: 1;
min-height: 0;
} }
.player-input { .player-input {
background: var(--color-background); background: var(--color-background);
@@ -42,6 +44,9 @@
border: 2px solid var(--color-border); border: 2px solid var(--color-border);
transition: border-color var(--transition-base); transition: border-color var(--transition-base);
position: relative; position: relative;
flex-shrink: 1;
min-height: 0;
overflow: hidden;
} }
.player-input:focus-within { .player-input:focus-within {
border-color: var(--color-primary); border-color: var(--color-primary);
@@ -49,9 +54,9 @@
} }
.player-input label { .player-input label {
display: block; display: block;
margin-bottom: var(--space-md); margin-bottom: clamp(0.5rem, 2vh, 1rem);
color: var(--color-text); color: var(--color-text);
font-size: var(--font-size-lg); font-size: clamp(1rem, 2.5vh, 1.125rem);
font-weight: 600; font-weight: 600;
} }
.name-input-container { .name-input-container {
@@ -135,15 +140,16 @@
.form-header { .form-header {
flex-shrink: 0; flex-shrink: 0;
padding: var(--space-xl) var(--space-lg) var(--space-md) var(--space-lg); padding: clamp(0.5rem, 2vh, 2rem) var(--space-lg) clamp(0.25rem, 1vh, 1rem) var(--space-lg);
} }
.form-content { .form-content {
flex: 1; flex: 1;
overflow-y: auto; overflow: hidden;
overflow-x: hidden;
padding: 0 var(--space-lg); padding: 0 var(--space-lg);
min-height: 0; min-height: 0;
display: flex;
flex-direction: column;
} }
.form-footer { .form-footer {
@@ -154,12 +160,12 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: var(--space-md); gap: clamp(0.5rem, 1.5vw, 1rem);
margin-bottom: var(--space-lg); margin-bottom: clamp(0.5rem, 2vh, 1.5rem);
} }
.progress-dot { .progress-dot {
width: 16px; width: clamp(10px, 2vh, 16px);
height: 16px; height: clamp(10px, 2vh, 16px);
border-radius: 50%; border-radius: 50%;
background: var(--color-border); background: var(--color-border);
opacity: 0.4; opacity: 0.4;
@@ -172,18 +178,27 @@
transform: scale(1.2); transform: scale(1.2);
box-shadow: 0 0 0 4px var(--color-primary-light); box-shadow: 0 0 0 4px var(--color-primary-light);
} }
.quick-pick-container {
flex: 1;
overflow: hidden;
min-height: 0;
display: flex;
flex-direction: column;
}
.quick-pick-btn { .quick-pick-btn {
min-width: 80px; min-width: 60px;
min-height: var(--touch-target-comfortable); min-height: 36px;
font-size: var(--font-size-base); font-size: clamp(0.75rem, 2vw, 1rem);
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: var(--color-secondary); background: var(--color-secondary);
color: var(--color-text); color: var(--color-text);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
cursor: pointer; cursor: pointer;
padding: var(--space-sm) var(--space-md); padding: 0.4rem 0.8rem;
transition: all var(--transition-base); transition: all var(--transition-base);
font-weight: 500; font-weight: 500;
flex-shrink: 0;
} }
.quick-pick-btn:hover, .quick-pick-btn:focus { .quick-pick-btn:hover, .quick-pick-btn:focus {
background: var(--color-secondary-hover); background: var(--color-secondary-hover);

View File

@@ -18,13 +18,6 @@ export const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const el = inputRef.current;
if (el) {
el.focus();
}
}, []);
useEffect(() => { useEffect(() => {
if (!player1) { if (!player1) {
setFilteredNames(playerNameHistory); setFilteredNames(playerNameHistory);

View File

@@ -15,13 +15,6 @@ export const Player2Step = ({ playerNameHistory, onNext, onCancel, initialValue
const [filteredNames, setFilteredNames] = useState(playerNameHistory); const [filteredNames, setFilteredNames] = useState(playerNameHistory);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const el = inputRef.current;
if (el) {
el.focus();
}
}, []);
useEffect(() => { useEffect(() => {
if (!player2) { if (!player2) {
setFilteredNames(playerNameHistory); setFilteredNames(playerNameHistory);

View File

@@ -14,13 +14,6 @@ export const Player3Step = ({ playerNameHistory, onNext, onCancel, initialValue
const [filteredNames, setFilteredNames] = useState(playerNameHistory); const [filteredNames, setFilteredNames] = useState(playerNameHistory);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const el = inputRef.current;
if (el) {
el.focus();
}
}, []);
useEffect(() => { useEffect(() => {
if (!player3) { if (!player3) {
setFilteredNames(playerNameHistory); setFilteredNames(playerNameHistory);