krz/daybyday
iOS app for children to learn days of the week, months, and seasons with audio assist.
clone: git clone https://gitbay.org/krz/daybyday.git
main: DayByDay/SeasonCard.swift · raw
1//
2// SeasonCard.swift
3// DayByDay
4//
5
6import SwiftUI
7
8/// A single large, colorful card representing one season.
9/// Tapping the card plays a bounce animation and speaks the season name aloud.
10struct SeasonCard: View {
11 let season: Season
12 var isHighlighted = false
13 @State private var isTapped = false
14
15 private var cardScale: CGFloat {
16 if isTapped { return 1.12 }
17 if isHighlighted { return 1.04 }
18 return 1.0
19 }
20
21 var body: some View {
22 ZStack {
23 RoundedRectangle(cornerRadius: 32, style: .continuous)
24 .fill(season.color.gradient)
25 .shadow(color: season.color.opacity(0.4), radius: 8, y: 4)
26
27 VStack(spacing: 16) {
28 Image(systemName: season.symbol)
29 .font(.system(size: 64))
30 .foregroundStyle(.white)
31 .symbolRenderingMode(.hierarchical)
32
33 Text(season.name)
34 .font(.system(size: 32, weight: .bold, design: .rounded))
35 .foregroundStyle(.white)
36 }
37 }
38 .overlay {
39 if isHighlighted {
40 RoundedRectangle(cornerRadius: 32, style: .continuous)
41 .strokeBorder(.white, lineWidth: 5)
42 }
43 }
44 .shadow(color: isHighlighted ? .white.opacity(0.8) : .clear, radius: 12)
45 .scaleEffect(cardScale)
46 .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
47 .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
48 .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
49 .onTapGesture {
50 isTapped = true
51 SpeechSynthesizer.shared.speak(season.name)
52 DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
53 isTapped = false
54 }
55 }
56 .accessibilityLabel(season.name)
57 .accessibilityAddTraits(.isButton)
58 }
59}
60
61// MARK: - Season model
62
63enum Season: Int, CaseIterable, Identifiable {
64 case spring, summer, fall, winter
65
66 var id: Int { rawValue }
67
68 /// The season for today's date.
69 /// Spring: March–May, Summer: June–August,
70 /// Fall: September–November, Winter: December–February.
71 static var current: Season {
72 let month = Calendar.current.component(.month, from: Date())
73 switch month {
74 case 3...5: return .spring
75 case 6...8: return .summer
76 case 9...11: return .fall
77 default: return .winter
78 }
79 }
80
81 var name: String {
82 switch self {
83 case .spring: "Spring"
84 case .summer: "Summer"
85 case .fall: "Fall"
86 case .winter: "Winter"
87 }
88 }
89
90 var symbol: String {
91 switch self {
92 case .spring: "leaf.fill"
93 case .summer: "sun.max.fill"
94 case .fall: "wind"
95 case .winter: "snowflake"
96 }
97 }
98
99 var color: Color {
100 switch self {
101 case .spring: Color(red: 0.45, green: 0.8, blue: 0.5) // fresh green
102 case .summer: Color(red: 1.0, green: 0.6, blue: 0.15) // sunny orange
103 case .fall: Color(red: 0.85, green: 0.45, blue: 0.2) // autumn orange
104 case .winter: Color(red: 0.4, green: 0.65, blue: 0.95) // cool blue
105 }
106 }
107}
108
109#Preview {
110 HStack(spacing: 24) {
111 SeasonCard(season: .spring, isHighlighted: true)
112 .frame(width: 200, height: 260)
113 SeasonCard(season: .summer)
114 .frame(width: 200, height: 260)
115 }
116 .padding()
117}