crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 13ddb51c1bf5430cdf6cfd3217d373bb289a4361
parent 1982598c70df1facb40725d1200563c3b44e38f1
Author: Michael Camilleri <[email protected]>
Date:   Sat,  6 Jun 2026 15:52:44 +0900

Shrink the Puzzle Header at large Dynamic Type

Prior to this commit, the Puzzle Header sat at a fixed 80pt regardless
of text size. As Dynamic Type scaled up, the Clue Bar below the grid
grew to fit its (must-read) two-line clue while the header held its
budget, so the grid absorbed the entire squeeze — leaving little room
for the puzzle on small screens at large type.

Make the header height a decreasing function of dynamic type size: full
80pt at or below the default size, then shedding 6pt per step down to a
48pt floor. The title/scoreboard/credits are the least important text on
screen, so the header yields its height and hands that space back to the
grid; its text simply truncates within the smaller box. Because the
header and clue bar are both driven by the same Dynamic Type signal, the
header shrinks in lockstep as the Clue Bar grows, with no measurement or
plumbing between the two views.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

Diffstat:
MCrossmate/Views/PuzzleView.swift | 19++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/Crossmate/Views/PuzzleView.swift b/Crossmate/Views/PuzzleView.swift @@ -1165,6 +1165,7 @@ private struct PuzzleHeader: View { let gameID: UUID let isEngagementLive: Bool @Environment(AnnouncementCenter.self) private var announcements + @Environment(\.dynamicTypeSize) private var dynamicTypeSize @State private var selection: Page = .title /// Holds off looking at the announcement queue for a moment after /// open, so the title is the only thing on screen during the @@ -1208,6 +1209,22 @@ private struct PuzzleHeader: View { return result } + /// Above the default text size the clue bar below the grid grows to fit + /// the (must-read) clue, squeezing the grid. The title/scoreboard/credits + /// shown here are the least important text on screen, so the header yields + /// its own height as type scales up — shedding a few points per step down + /// to a legible-enough floor — and hands that space back to the grid. The + /// text inside just truncates within the smaller box. At or below the + /// default size the comfortable full height is preserved. + private var headerHeight: CGFloat { + let sizes = DynamicTypeSize.allCases + guard let current = sizes.firstIndex(of: dynamicTypeSize), + let baseline = sizes.firstIndex(of: .large) + else { return 80 } + let stepsAboveDefault = max(0, current - baseline) + return max(48, 80 - CGFloat(stepsAboveDefault) * 6) + } + var body: some View { let visibleAnnouncement = announcementsArmed ? announcements.current(forGame: gameID) @@ -1231,7 +1248,7 @@ private struct PuzzleHeader: View { .transition(.move(edge: .bottom).combined(with: .opacity)) } } - .frame(height: 80) + .frame(height: headerHeight) .padding(.bottom, 14) .animation(.easeInOut(duration: 0.3), value: visibleAnnouncement) .animation(.easeInOut(duration: 0.2), value: isEngagementLive)