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

View File

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

View File

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

View File

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