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

c53a320698c17d98477c0384f1f644da9735f645

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-03-14T14:56:39Z

implement more learning categories
 AlphabetView.swift                                 |  30 +++
 AnimalCard.swift                                   | 103 +++++++++++
 AnimalsView.swift                                  |  30 +++
 BodyPartCard.swift                                 | 114 ++++++++++++
 BodyPartsView.swift                                |  29 +++
 ColorCard.swift                                    |  86 +++++++++
 ColorsView.swift                                   |  30 +++
 DayByDay.xcodeproj/project.pbxproj                 |  93 +++++++++-
 .../LaunchBackground.colorset/Contents.json        |  38 ++++
 DayByDay/ContentView.swift                         | 202 ++++++++++++++++-----
 DayByDay/DayByDayApp.swift                         |  17 ++
 DayByDay/Info.plist                                |  15 ++
 DayByDay/SpeechSynthesizer.swift                   |  37 ++--
 FoodCard.swift                                     | 115 ++++++++++++
 FoodView.swift                                     |  29 +++
 LetterCard.swift                                   |  84 +++++++++
 NumberCard.swift                                   | 136 ++++++++++++++
 NumbersView.swift                                  |  33 ++++
 Screenshots/ipad.png                               | Bin 1350388 -> 1273841 bytes
 Screenshots/iphone.png                             | Bin 1176013 -> 1601412 bytes
 ShapeCard.swift                                    | 130 +++++++++++++
 ShapesView.swift                                   |  30 +++
 TodayView.swift                                    |  84 +++++++++
 WeatherCard.swift                                  | 102 +++++++++++
 WeatherView.swift                                  |  29 +++
 claude.md                                          |  31 +++-
 26 files changed, 1556 insertions(+), 71 deletions(-)

diff --git a/AlphabetView.swift b/AlphabetView.swift
new file mode 100644
index 0000000..64af026
--- /dev/null
+++ b/AlphabetView.swift
@@ -0,0 +1,30 @@
+//
+//  AlphabetView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays all 26 letters A–Z as large, tappable cards in a scrollable grid.
+/// Uses a smaller minimum size to fit more letters across the screen.
+struct AlphabetView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 140, maximum: 300), spacing: 20)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 20) {
+                ForEach(LearnLetter.allCases) { letter in
+                    LetterCard(letter: letter)
+                        .frame(height: 160)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    AlphabetView()
+}
diff --git a/AnimalCard.swift b/AnimalCard.swift
new file mode 100644
index 0000000..2207d45
--- /dev/null
+++ b/AnimalCard.swift
@@ -0,0 +1,103 @@
+//
+//  AnimalCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large, colorful card representing one animal.
+/// Tapping the card plays a bounce animation and speaks the animal name aloud.
+struct AnimalCard: View {
+    let animal: LearnAnimal
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(animal.color.gradient)
+                .shadow(color: animal.color.opacity(0.4), radius: 8, y: 4)
+
+            VStack(spacing: 16) {
+                Image(systemName: animal.symbol)
+                    .font(.system(size: 64))
+                    .foregroundStyle(.white)
+                    .symbolRenderingMode(.hierarchical)
+
+                Text(animal.name)
+                    .font(.system(size: 32, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white)
+            }
+        }
+        .scaleEffect(isTapped ? 1.12 : 1.0)
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(animal.name)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(animal.name)
+        .accessibilityAddTraits(.isButton)
+    }
+}
+
+// MARK: - Animal model
+
+enum LearnAnimal: Int, CaseIterable, Identifiable {
+    case cat, dog, bird, fish, rabbit, turtle, ladybug, ant, lizard
+
+    var id: Int { rawValue }
+
+    var name: String {
+        switch self {
+        case .cat:       "Cat"
+        case .dog:       "Dog"
+        case .bird:      "Bird"
+        case .fish:      "Fish"
+        case .rabbit:    "Rabbit"
+        case .turtle:    "Turtle"
+        case .ladybug:   "Ladybug"
+        case .ant:       "Ant"
+        case .lizard:    "Lizard"
+        }
+    }
+
+    var symbol: String {
+        switch self {
+        case .cat:       "cat.fill"
+        case .dog:       "dog.fill"
+        case .bird:      "bird.fill"
+        case .fish:      "fish.fill"
+        case .rabbit:    "hare.fill"
+        case .turtle:    "tortoise.fill"
+        case .ladybug:   "ladybug.fill"
+        case .ant:       "ant.fill"
+        case .lizard:    "lizard.fill"
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .cat:       Color(red: 0.9, green: 0.55, blue: 0.2)   // orange
+        case .dog:       Color(red: 0.6, green: 0.45, blue: 0.3)   // brown
+        case .bird:      Color(red: 0.3, green: 0.65, blue: 0.9)   // sky blue
+        case .fish:      Color(red: 0.2, green: 0.75, blue: 0.8)   // teal
+        case .rabbit:    Color(red: 0.75, green: 0.6, blue: 0.8)   // lavender
+        case .turtle:    Color(red: 0.35, green: 0.7, blue: 0.4)   // green
+        case .ladybug:   Color(red: 0.9, green: 0.25, blue: 0.25)  // red
+        case .ant:       Color(red: 0.3, green: 0.3, blue: 0.3)    // dark gray
+        case .lizard:    Color(red: 0.45, green: 0.75, blue: 0.35)  // lime
+        }
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        AnimalCard(animal: .cat)
+            .frame(width: 200, height: 260)
+    }
+    .padding()
+}
diff --git a/AnimalsView.swift b/AnimalsView.swift
new file mode 100644
index 0000000..d3c5a2d
--- /dev/null
+++ b/AnimalsView.swift
@@ -0,0 +1,30 @@
+//
+//  AnimalsView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays 12 animals as large, tappable cards in a scrollable grid.
+/// Adapts between 1 and 2 columns depending on available width.
+struct AnimalsView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 300, maximum: 500), spacing: 24)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 24) {
+                ForEach(LearnAnimal.allCases) { animal in
+                    AnimalCard(animal: animal)
+                        .frame(height: 200)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    AnimalsView()
+}
diff --git a/BodyPartCard.swift b/BodyPartCard.swift
new file mode 100644
index 0000000..a3693de
--- /dev/null
+++ b/BodyPartCard.swift
@@ -0,0 +1,114 @@
+//
+//  BodyPartCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large, colorful card representing one body part.
+/// Tapping the card plays a bounce animation and speaks the body part name aloud.
+struct BodyPartCard: View {
+    let bodyPart: LearnBodyPart
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(bodyPart.color.gradient)
+                .shadow(color: bodyPart.color.opacity(0.4), radius: 8, y: 4)
+
+            VStack(spacing: 16) {
+                Image(systemName: bodyPart.symbol)
+                    .font(.system(size: 64))
+                    .foregroundStyle(.white)
+                    .symbolRenderingMode(.hierarchical)
+
+                Text(bodyPart.name)
+                    .font(.system(size: 32, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white)
+            }
+        }
+        .scaleEffect(isTapped ? 1.12 : 1.0)
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(bodyPart.name)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(bodyPart.name)
+        .accessibilityAddTraits(.isButton)
+    }
+}
+
+// MARK: - Body part model
+
+enum LearnBodyPart: Int, CaseIterable, Identifiable {
+    case head, eyes, ears, nose, mouth, hands, fingers, arms, legs, feet, heart, belly
+
+    var id: Int { rawValue }
+
+    var name: String {
+        switch self {
+        case .head:    "Head"
+        case .eyes:    "Eyes"
+        case .ears:    "Ears"
+        case .nose:    "Nose"
+        case .mouth:   "Mouth"
+        case .hands:   "Hands"
+        case .fingers: "Fingers"
+        case .arms:    "Arms"
+        case .legs:    "Legs"
+        case .feet:    "Feet"
+        case .heart:   "Heart"
+        case .belly:   "Belly"
+        }
+    }
+
+    var symbol: String {
+        switch self {
+        case .head:    "brain.head.profile"
+        case .eyes:    "eye.fill"
+        case .ears:    "ear.fill"
+        case .nose:    "nose.fill"
+        case .mouth:   "mouth.fill"
+        case .hands:   "hand.raised.fill"
+        case .fingers: "hand.point.up.fill"
+        case .arms:    "figure.arms.open"
+        case .legs:    "figure.walk"
+        case .feet:    "shoeprints.fill"
+        case .heart:   "heart.fill"
+        case .belly:   "circle.fill"
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .head:    Color(red: 0.9, green: 0.5, blue: 0.3)    // warm orange
+        case .eyes:    Color(red: 0.3, green: 0.65, blue: 0.9)   // blue
+        case .ears:    Color(red: 0.6, green: 0.45, blue: 0.85)  // purple
+        case .nose:    Color(red: 0.9, green: 0.4, blue: 0.5)    // rose
+        case .mouth:   Color(red: 0.9, green: 0.3, blue: 0.35)   // red
+        case .hands:   Color(red: 0.95, green: 0.7, blue: 0.3)   // golden
+        case .fingers: Color(red: 0.35, green: 0.75, blue: 0.5)  // green
+        case .arms:    Color(red: 0.25, green: 0.7, blue: 0.7)   // teal
+        case .legs:    Color(red: 0.5, green: 0.4, blue: 0.8)    // indigo
+        case .feet:    Color(red: 0.7, green: 0.5, blue: 0.3)    // brown
+        case .heart:   Color(red: 0.85, green: 0.3, blue: 0.45)  // deep pink
+        case .belly:   Color(red: 1.0, green: 0.6, blue: 0.4)    // peach
+        }
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        BodyPartCard(bodyPart: .head)
+            .frame(width: 200, height: 260)
+        BodyPartCard(bodyPart: .hands)
+            .frame(width: 200, height: 260)
+    }
+    .padding()
+}
diff --git a/BodyPartsView.swift b/BodyPartsView.swift
new file mode 100644
index 0000000..73019f5
--- /dev/null
+++ b/BodyPartsView.swift
@@ -0,0 +1,29 @@
+//
+//  BodyPartsView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays all 12 body parts as large, tappable cards in a scrollable grid.
+struct BodyPartsView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 280), spacing: 24)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 24) {
+                ForEach(LearnBodyPart.allCases) { part in
+                    BodyPartCard(bodyPart: part)
+                        .frame(height: 200)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    BodyPartsView()
+}
diff --git a/ColorCard.swift b/ColorCard.swift
new file mode 100644
index 0000000..0bbe2b9
--- /dev/null
+++ b/ColorCard.swift
@@ -0,0 +1,86 @@
+//
+//  ColorCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large card filled with a color. Tapping speaks the color name aloud.
+struct ColorCard: View {
+    let learnColor: LearnColor
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(learnColor.color.gradient)
+                .shadow(color: learnColor.color.opacity(0.4), radius: 8, y: 4)
+
+            Text(learnColor.name)
+                .font(.system(size: 36, weight: .bold, design: .rounded))
+                .foregroundStyle(.white)
+                .shadow(color: .black.opacity(0.3), radius: 4, y: 2)
+        }
+        .scaleEffect(isTapped ? 1.12 : 1.0)
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(learnColor.name)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(learnColor.name)
+        .accessibilityAddTraits(.isButton)
+    }
+}
+
+// MARK: - Color model
+
+enum LearnColor: Int, CaseIterable, Identifiable {
+    case red, orange, yellow, green, blue, purple, pink, brown, black, white
+
+    var id: Int { rawValue }
+
+    var name: String {
+        switch self {
+        case .red:    "Red"
+        case .orange: "Orange"
+        case .yellow: "Yellow"
+        case .green:  "Green"
+        case .blue:   "Blue"
+        case .purple: "Purple"
+        case .pink:   "Pink"
+        case .brown:  "Brown"
+        case .black:  "Black"
+        case .white:  "White"
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .red:    Color(red: 0.9, green: 0.2, blue: 0.2)
+        case .orange: Color(red: 1.0, green: 0.55, blue: 0.15)
+        case .yellow: Color(red: 1.0, green: 0.82, blue: 0.15)
+        case .green:  Color(red: 0.2, green: 0.75, blue: 0.35)
+        case .blue:   Color(red: 0.2, green: 0.45, blue: 0.9)
+        case .purple: Color(red: 0.55, green: 0.3, blue: 0.85)
+        case .pink:   Color(red: 0.95, green: 0.4, blue: 0.6)
+        case .brown:  Color(red: 0.55, green: 0.35, blue: 0.2)
+        case .black:  Color(red: 0.15, green: 0.15, blue: 0.15)
+        case .white:  Color(red: 0.92, green: 0.92, blue: 0.92)
+        }
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        ColorCard(learnColor: .red)
+            .frame(width: 200, height: 200)
+        ColorCard(learnColor: .blue)
+            .frame(width: 200, height: 200)
+    }
+    .padding()
+}
diff --git a/ColorsView.swift b/ColorsView.swift
new file mode 100644
index 0000000..9b935e5
--- /dev/null
+++ b/ColorsView.swift
@@ -0,0 +1,30 @@
+//
+//  ColorsView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays 10 colors as large, tappable cards in a scrollable grid.
+/// Adapts between 1 and 2 columns depending on available width.
+struct ColorsView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 300, maximum: 500), spacing: 24)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 24) {
+                ForEach(LearnColor.allCases) { color in
+                    ColorCard(learnColor: color)
+                        .frame(height: 200)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    ColorsView()
+}
diff --git a/DayByDay.xcodeproj/project.pbxproj b/DayByDay.xcodeproj/project.pbxproj
index 3ff18e9..9e2876c 100644
--- a/DayByDay.xcodeproj/project.pbxproj
+++ b/DayByDay.xcodeproj/project.pbxproj
@@ -7,17 +7,64 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		8B1B4FF92F659AD1005C246F /* WeatherCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B4FF82F659AD1005C246F /* WeatherCard.swift */; };
+		8B1B4FFB2F659AD1005C246F /* WeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B4FFA2F659AD1005C246F /* WeatherView.swift */; };
+		8B1B4FFD2F659AD2005C246F /* BodyPartCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B4FFC2F659AD2005C246F /* BodyPartCard.swift */; };
+		8B1B4FFF2F659AD2005C246F /* BodyPartsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B4FFE2F659AD2005C246F /* BodyPartsView.swift */; };
+		8B1B50012F659AD3005C246F /* FoodCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B50002F659AD3005C246F /* FoodCard.swift */; };
+		8B1B50032F659AD4005C246F /* FoodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B50022F659AD4005C246F /* FoodView.swift */; };
+		8B57F7282F63BBB4001F6F5E /* TodayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F7272F63BBB4001F6F5E /* TodayView.swift */; };
+		8B57F72A2F63BBB5001F6F5E /* NumberCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F7292F63BBB5001F6F5E /* NumberCard.swift */; };
+		8B57F72C2F63BBB6001F6F5E /* NumbersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F72B2F63BBB6001F6F5E /* NumbersView.swift */; };
+		8B57F72E2F63BBB7001F6F5E /* ColorCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F72D2F63BBB7001F6F5E /* ColorCard.swift */; };
+		8B57F7302F63BBB8001F6F5E /* ColorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F72F2F63BBB8001F6F5E /* ColorsView.swift */; };
+		8B57F7322F63BBB9001F6F5E /* ShapeCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F7312F63BBB9001F6F5E /* ShapeCard.swift */; };
+		8B57F7342F63BBBA001F6F5E /* ShapesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F7332F63BBBA001F6F5E /* ShapesView.swift */; };
+		8B57F7362F63BBBA001F6F5E /* AnimalCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F7352F63BBBA001F6F5E /* AnimalCard.swift */; };
+		8B57F7382F63BBBB001F6F5E /* AnimalsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F7372F63BBBB001F6F5E /* AnimalsView.swift */; };
+		8B57F73A2F63BBBD001F6F5E /* LetterCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F7392F63BBBD001F6F5E /* LetterCard.swift */; };
+		8B57F73C2F63BBBD001F6F5E /* AlphabetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B57F73B2F63BBBD001F6F5E /* AlphabetView.swift */; };
 		8B7051C12F5F90CD00491D58 /* claude.md in Resources */ = {isa = PBXBuildFile; fileRef = 8B7051C02F5F90C800491D58 /* claude.md */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		8B1B4FF82F659AD1005C246F /* WeatherCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherCard.swift; sourceTree = "<group>"; };
+		8B1B4FFA2F659AD1005C246F /* WeatherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherView.swift; sourceTree = "<group>"; };
+		8B1B4FFC2F659AD2005C246F /* BodyPartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyPartCard.swift; sourceTree = "<group>"; };
+		8B1B4FFE2F659AD2005C246F /* BodyPartsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyPartsView.swift; sourceTree = "<group>"; };
+		8B1B50002F659AD3005C246F /* FoodCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoodCard.swift; sourceTree = "<group>"; };
+		8B1B50022F659AD4005C246F /* FoodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoodView.swift; sourceTree = "<group>"; };
+		8B57F7272F63BBB4001F6F5E /* TodayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayView.swift; sourceTree = "<group>"; };
+		8B57F7292F63BBB5001F6F5E /* NumberCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberCard.swift; sourceTree = "<group>"; };
+		8B57F72B2F63BBB6001F6F5E /* NumbersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumbersView.swift; sourceTree = "<group>"; };
+		8B57F72D2F63BBB7001F6F5E /* ColorCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCard.swift; sourceTree = "<group>"; };
+		8B57F72F2F63BBB8001F6F5E /* ColorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorsView.swift; sourceTree = "<group>"; };
+		8B57F7312F63BBB9001F6F5E /* ShapeCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeCard.swift; sourceTree = "<group>"; };
+		8B57F7332F63BBBA001F6F5E /* ShapesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesView.swift; sourceTree = "<group>"; };
+		8B57F7352F63BBBA001F6F5E /* AnimalCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimalCard.swift; sourceTree = "<group>"; };
+		8B57F7372F63BBBB001F6F5E /* AnimalsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimalsView.swift; sourceTree = "<group>"; };
+		8B57F7392F63BBBD001F6F5E /* LetterCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterCard.swift; sourceTree = "<group>"; };
+		8B57F73B2F63BBBD001F6F5E /* AlphabetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphabetView.swift; sourceTree = "<group>"; };
 		8B7051B22F5F908800491D58 /* DayByDay.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DayByDay.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		8B7051C02F5F90C800491D58 /* claude.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = claude.md; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+		8B5214E62F621E0200E1D35A /* Exceptions for "DayByDay" folder in "DayByDay" target */ = {
+			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+			membershipExceptions = (
+				Info.plist,
+			);
+			target = 8B7051B12F5F908800491D58 /* DayByDay */;
+		};
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
 /* Begin PBXFileSystemSynchronizedRootGroup section */
 		8B7051B42F5F908800491D58 /* DayByDay */ = {
 			isa = PBXFileSystemSynchronizedRootGroup;
+			exceptions = (
+				8B5214E62F621E0200E1D35A /* Exceptions for "DayByDay" folder in "DayByDay" target */,
+			);
 			path = DayByDay;
 			sourceTree = "<group>";
 		};
@@ -40,6 +87,23 @@
 				8B7051C02F5F90C800491D58 /* claude.md */,
 				8B7051B42F5F908800491D58 /* DayByDay */,
 				8B7051B32F5F908800491D58 /* Products */,
+				8B57F7272F63BBB4001F6F5E /* TodayView.swift */,
+				8B57F7292F63BBB5001F6F5E /* NumberCard.swift */,
+				8B57F72B2F63BBB6001F6F5E /* NumbersView.swift */,
+				8B57F72D2F63BBB7001F6F5E /* ColorCard.swift */,
+				8B57F72F2F63BBB8001F6F5E /* ColorsView.swift */,
+				8B57F7312F63BBB9001F6F5E /* ShapeCard.swift */,
+				8B57F7332F63BBBA001F6F5E /* ShapesView.swift */,
+				8B57F7352F63BBBA001F6F5E /* AnimalCard.swift */,
+				8B57F7372F63BBBB001F6F5E /* AnimalsView.swift */,
+				8B57F7392F63BBBD001F6F5E /* LetterCard.swift */,
+				8B57F73B2F63BBBD001F6F5E /* AlphabetView.swift */,
+				8B1B4FF82F659AD1005C246F /* WeatherCard.swift */,
+				8B1B4FFA2F659AD1005C246F /* WeatherView.swift */,
+				8B1B4FFC2F659AD2005C246F /* BodyPartCard.swift */,
+				8B1B4FFE2F659AD2005C246F /* BodyPartsView.swift */,
+				8B1B50002F659AD3005C246F /* FoodCard.swift */,
+				8B1B50022F659AD4005C246F /* FoodView.swift */,
 			);
 			sourceTree = "<group>";
 		};
@@ -126,6 +190,23 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				8B1B50032F659AD4005C246F /* FoodView.swift in Sources */,
+				8B57F72E2F63BBB7001F6F5E /* ColorCard.swift in Sources */,
+				8B57F73C2F63BBBD001F6F5E /* AlphabetView.swift in Sources */,
+				8B57F73A2F63BBBD001F6F5E /* LetterCard.swift in Sources */,
+				8B57F7382F63BBBB001F6F5E /* AnimalsView.swift in Sources */,
+				8B57F72C2F63BBB6001F6F5E /* NumbersView.swift in Sources */,
+				8B57F72A2F63BBB5001F6F5E /* NumberCard.swift in Sources */,
+				8B1B50012F659AD3005C246F /* FoodCard.swift in Sources */,
+				8B1B4FF92F659AD1005C246F /* WeatherCard.swift in Sources */,
+				8B1B4FFB2F659AD1005C246F /* WeatherView.swift in Sources */,
+				8B1B4FFD2F659AD2005C246F /* BodyPartCard.swift in Sources */,
+				8B1B4FFF2F659AD2005C246F /* BodyPartsView.swift in Sources */,
+				8B57F7342F63BBBA001F6F5E /* ShapesView.swift in Sources */,
+				8B57F7362F63BBBA001F6F5E /* AnimalCard.swift in Sources */,
+				8B57F7322F63BBB9001F6F5E /* ShapeCard.swift in Sources */,
+				8B57F7282F63BBB4001F6F5E /* TodayView.swift in Sources */,
+				8B57F7302F63BBB8001F6F5E /* ColorsView.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -259,21 +340,21 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 3;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_FILE = DayByDay/Info.plist;
 				INFOPLIST_KEY_CFBundleDisplayName = "Day By Day";
 				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education";
 				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
-				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
 				INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0;
+				MARKETING_VERSION = 1.2;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -292,21 +373,21 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 3;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_FILE = DayByDay/Info.plist;
 				INFOPLIST_KEY_CFBundleDisplayName = "Day By Day";
 				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education";
 				INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
 				INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
-				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
 				INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0;
+				MARKETING_VERSION = 1.2;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
diff --git a/DayByDay/Assets.xcassets/LaunchBackground.colorset/Contents.json b/DayByDay/Assets.xcassets/LaunchBackground.colorset/Contents.json
new file mode 100644
index 0000000..0425637
--- /dev/null
+++ b/DayByDay/Assets.xcassets/LaunchBackground.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "1.000",
+          "green" : "1.000",
+          "red" : "1.000"
+        }
+      },
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.000",
+          "green" : "0.000",
+          "red" : "0.000"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/DayByDay/ContentView.swift b/DayByDay/ContentView.swift
index c3ede7a..9924c5d 100644
--- a/DayByDay/ContentView.swift
+++ b/DayByDay/ContentView.swift
@@ -7,62 +7,180 @@
 
 import SwiftUI
 
+// MARK: - Category model
+
+enum Category: Int, CaseIterable, Identifiable {
+    case today, days, months, seasons, numbers, colors, shapes, animals, alphabet,
+         weather, bodyParts, food
+
+    var id: Int { rawValue }
+
+    var label: String {
+        switch self {
+        case .today:     "Today"
+        case .days:      "Days"
+        case .months:    "Months"
+        case .seasons:   "Seasons"
+        case .numbers:   "Numbers"
+        case .colors:    "Colors"
+        case .shapes:    "Shapes"
+        case .animals:   "Animals"
+        case .alphabet:  "Alphabet"
+        case .weather:   "Weather"
+        case .bodyParts: "Body"
+        case .food:      "Food"
+        }
+    }
+
+    var symbol: String {
+        switch self {
+        case .today:     "sun.horizon.fill"
+        case .days:      "calendar"
+        case .months:    "calendar.badge.clock"
+        case .seasons:   "leaf.fill"
+        case .numbers:   "123.rectangle.fill"
+        case .colors:    "paintpalette.fill"
+        case .shapes:    "pentagon.fill"
+        case .animals:   "pawprint.fill"
+        case .alphabet:  "abc"
+        case .weather:   "cloud.sun.rain.fill"
+        case .bodyParts: "figure.stand"
+        case .food:      "carrot.fill"
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .today:     .orange
+        case .days:      .blue
+        case .months:    .purple
+        case .seasons:   .green
+        case .numbers:   .red
+        case .colors:    .pink
+        case .shapes:    .teal
+        case .animals:   Color(red: 0.7, green: 0.45, blue: 0.2)
+        case .alphabet:  .indigo
+        case .weather:   Color(red: 0.4, green: 0.75, blue: 0.95)
+        case .bodyParts: Color(red: 0.9, green: 0.5, blue: 0.6)
+        case .food:      Color(red: 0.95, green: 0.6, blue: 0.2)
+        }
+    }
+
+    /// Whether this tile uses a special gradient background instead of the solid color.
+    var usesGradientBackground: Bool {
+        self == .colors
+    }
+}
+
+// MARK: - Home grid tile
+
+struct CategoryTile: View {
+    let category: Category
+
+    var body: some View {
+        ZStack {
+            if category.usesGradientBackground {
+                RoundedRectangle(cornerRadius: 32, style: .continuous)
+                    .fill(
+                        LinearGradient(
+                            colors: [.red, .orange, .yellow, .green, .blue, .purple],
+                            startPoint: .leading,
+                            endPoint: .trailing
+                        )
+                    )
+                    .shadow(color: category.color.opacity(0.4), radius: 8, y: 4)
+            } else {
+                RoundedRectangle(cornerRadius: 32, style: .continuous)
+                    .fill(category.color.gradient)
+                    .shadow(color: category.color.opacity(0.4), radius: 8, y: 4)
+            }
+
+            VStack(spacing: 12) {
+                Image(systemName: category.symbol)
+                    .font(.system(size: 48))
+                    .foregroundStyle(.white)
+                    .symbolRenderingMode(.hierarchical)
+
+                Text(category.label)
+                    .font(.system(size: 24, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white)
+            }
+        }
+        .accessibilityLabel(category.label)
+    }
+}
+
+// MARK: - Content view
+
 struct ContentView: View {
-    @State private var selectedTab = 0
     @AppStorage("hasSeenVoiceTip") private var hasSeenVoiceTip = false
 
-    private let tabs: [(label: String, symbol: String)] = [
-        ("Days", "calendar"),
-        ("Months", "calendar.badge.clock"),
-        ("Seasons", "leaf.fill"),
+    private let columns = [
+        GridItem(.flexible(), spacing: 24),
+        GridItem(.flexible(), spacing: 24)
     ]
 
     var body: some View {
-        VStack(spacing: 0) {
-            Group {
-                switch selectedTab {
-                case 0:  DaysOfWeekView()
-                case 1:  MonthsOfYearView()
-                default: SeasonsView()
-                }
-            }
-            .frame(maxWidth: .infinity, maxHeight: .infinity)
-
-            Divider()
-
-            // Bottom tab bar with large tap targets
-            HStack(spacing: 0) {
-                ForEach(Array(tabs.enumerated()), id: \.offset) { index, tab in
-                    Button {
-                        selectedTab = index
-                    } label: {
-                        VStack(spacing: 6) {
-                            Image(systemName: tab.symbol)
-                                .font(.system(size: 32))
-                            Text(tab.label)
-                                .font(.system(size: 18, weight: .semibold, design: .rounded))
+        NavigationStack {
+            ScrollView {
+                LazyVGrid(columns: columns, spacing: 24) {
+                    ForEach(Category.allCases) { category in
+                        NavigationLink(value: category) {
+                            CategoryTile(category: category)
+                                .aspectRatio(1, contentMode: .fit)
                         }
-                        .foregroundStyle(selectedTab == index ? Color.accentColor : Color.secondary)
+                        .buttonStyle(.plain)
+                    }
+                }
+                .padding(24)
+
+                Button {
+                    hasSeenVoiceTip = false
+                } label: {
+                    Label("Voice Quality Tips", systemImage: "speaker.wave.2.fill")
+                        .font(.system(size: 16, weight: .medium, design: .rounded))
+                        .foregroundStyle(.secondary)
                         .frame(maxWidth: .infinity)
                         .padding(.vertical, 14)
-                        .contentShape(Rectangle())
-                    }
-                    .buttonStyle(.plain)
-                    .accessibilityLabel(tab.label)
+                        .background(Color(.systemGray6))
+                        .cornerRadius(16)
                 }
+                .buttonStyle(.plain)
+                .padding(.horizontal, 24)
+                .padding(.bottom, 24)
             }
-            .padding(.horizontal, 24)
-            .padding(.bottom, 8)
-            .background(.bar)
-        }
-        .overlay {
-            if !hasSeenVoiceTip {
-                VoiceTipOverlay {
-                    withAnimation { hasSeenVoiceTip = true }
+            .navigationTitle("DayByDay")
+            .navigationDestination(for: Category.self) { category in
+                destinationView(for: category)
+                    .navigationTitle(category.label)
+            }
+            .overlay {
+                if !hasSeenVoiceTip {
+                    VoiceTipOverlay {
+                        withAnimation { hasSeenVoiceTip = true }
+                    }
                 }
             }
         }
     }
+
+    @ViewBuilder
+    private func destinationView(for category: Category) -> some View {
+        switch category {
+        case .today:     TodayView()
+        case .days:      DaysOfWeekView()
+        case .months:    MonthsOfYearView()
+        case .seasons:   SeasonsView()
+        case .numbers:   NumbersView()
+        case .colors:    ColorsView()
+        case .shapes:    ShapesView()
+        case .animals:   AnimalsView()
+        case .alphabet:  AlphabetView()
+        case .weather:   WeatherView()
+        case .bodyParts: BodyPartsView()
+        case .food:      FoodView()
+        }
+    }
 }
 
 /// A one-time informational overlay for parents explaining how to download
@@ -83,7 +201,7 @@ struct VoiceTipOverlay: View {
                 Text("Better Voices Available")
                     .font(.system(size: 28, weight: .bold, design: .rounded))
 
-                Text("For the best experience, download an enhanced voice on your iPad.\n\nSettings → Accessibility → Spoken Content → Voices → English → tap a voice marked Enhanced or Premium to download it.")
+                Text("For the best experience, download an enhanced voice on your device.\n\nSettings → Accessibility → Read & Speak → Voices → English → tap a voice marked Enhanced or Premium to download it.")
                     .font(.system(size: 18, design: .rounded))
                     .multilineTextAlignment(.center)
                     .foregroundStyle(.secondary)
diff --git a/DayByDay/DayByDayApp.swift b/DayByDay/DayByDayApp.swift
index 2cce4db..860d747 100644
--- a/DayByDay/DayByDayApp.swift
+++ b/DayByDay/DayByDayApp.swift
@@ -5,10 +5,27 @@
 //  Created by cmc on 2026-03-09.
 //
 
+import AVFoundation
 import SwiftUI
 
 @main
 struct DayByDayApp: App {
+    init() {
+        Task.detached(priority: .background) {
+            // Activate the audio session early so the first real tap doesn't block.
+            try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
+            try? AVAudioSession.sharedInstance().setActive(true)
+
+            // Force singleton init (voice selection) and prime the TTS engine
+            // with a silent utterance so subsequent speaks are instant.
+            await MainActor.run {
+                let warmup = AVSpeechUtterance(string: "")
+                warmup.volume = 0
+                SpeechSynthesizer.shared.speakUtterance(warmup)
+            }
+        }
+    }
+
     var body: some Scene {
         WindowGroup {
             ContentView()
diff --git a/DayByDay/Info.plist b/DayByDay/Info.plist
new file mode 100644
index 0000000..6aa377b
--- /dev/null
+++ b/DayByDay/Info.plist
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>UILaunchScreen</key>
+	<dict>
+		<key>UIColorName</key>
+		<string>LaunchBackground</string>
+		<key>UIImageName</key>
+		<string>AppIcon</string>
+		<key>UIImageRespectsSafeAreaInsets</key>
+		<false/>
+	</dict>
+</dict>
+</plist>
diff --git a/DayByDay/SpeechSynthesizer.swift b/DayByDay/SpeechSynthesizer.swift
index cac5caf..ab6421b 100644
--- a/DayByDay/SpeechSynthesizer.swift
+++ b/DayByDay/SpeechSynthesizer.swift
@@ -18,7 +18,16 @@ final class SpeechSynthesizer {
         self.voice = Self.bestAvailableVoice()
     }
 
+    /// Speaks a pre-configured utterance directly. Used for warmup at launch.
+    func speakUtterance(_ utterance: AVSpeechUtterance) {
+        synthesizer.speak(utterance)
+    }
+
     func speak(_ text: String) {
+        // Ensure speech plays through the speaker even when the silent switch is on.
+        try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
+        try? AVAudioSession.sharedInstance().setActive(true)
+
         if synthesizer.isSpeaking {
             synthesizer.stopSpeaking(at: .immediate)
         }
@@ -32,21 +41,19 @@ final class SpeechSynthesizer {
     }
 
     /// Picks the best en-US voice on the device.
-    /// Preference order: premium > enhanced > default quality.
-    /// Skips novelty voices so the child always hears a natural-sounding voice.
+    /// Filters for en-US, excludes novelty voices, then sorts by quality
+    /// descending so premium (3) > enhanced (2) > default (1).
     private static func bestAvailableVoice() -> AVSpeechSynthesisVoice? {
-        let candidates = AVSpeechSynthesisVoice.speechVoices().filter { voice in
-            voice.language.hasPrefix("en-US")
-            && !voice.voiceTraits.contains(.isNoveltyVoice)
-        }
-
-        if let premium = candidates.first(where: { $0.quality == .premium }) {
-            return premium
-        }
-        if let enhanced = candidates.first(where: { $0.quality == .enhanced }) {
-            return enhanced
-        }
-        // Fall back to the best default-quality voice.
-        return candidates.first ?? AVSpeechSynthesisVoice(language: "en-US")
+        let best = AVSpeechSynthesisVoice.speechVoices()
+            .filter { voice in
+                voice.language.hasPrefix("en-US")
+                && !voice.voiceTraits.contains(.isNoveltyVoice)
+            }
+            .sorted { $0.quality.rawValue > $1.quality.rawValue }
+            .first
+
+        let selected = best ?? AVSpeechSynthesisVoice(language: "en-US")
+        print("[DayByDay] Selected voice: \(selected?.name ?? "nil"), quality: \(selected?.quality.rawValue ?? -1)")
+        return selected
     }
 }
diff --git a/FoodCard.swift b/FoodCard.swift
new file mode 100644
index 0000000..957137e
--- /dev/null
+++ b/FoodCard.swift
@@ -0,0 +1,115 @@
+//
+//  FoodCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large, colorful card representing one fruit or vegetable.
+/// Tapping the card plays a bounce animation and speaks the food name aloud.
+/// Each food displays its emoji at large size for instant recognition.
+struct FoodCard: View {
+    let food: LearnFood
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(food.color.gradient)
+                .shadow(color: food.color.opacity(0.4), radius: 8, y: 4)
+
+            VStack(spacing: 16) {
+                Text(food.emoji)
+                    .font(.system(size: 72))
+
+                Text(food.name)
+                    .font(.system(size: 28, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white)
+            }
+        }
+        .scaleEffect(isTapped ? 1.12 : 1.0)
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(food.name)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(food.name)
+        .accessibilityAddTraits(.isButton)
+    }
+
+}
+
+// MARK: - Food model
+
+enum LearnFood: Int, CaseIterable, Identifiable {
+    case apple, banana, carrot, strawberry, grapes, lemon,
+         broccoli, watermelon, cherry, orange, corn, pear
+
+    var id: Int { rawValue }
+
+    var name: String {
+        switch self {
+        case .apple:      "Apple"
+        case .banana:     "Banana"
+        case .carrot:     "Carrot"
+        case .strawberry: "Strawberry"
+        case .grapes:     "Grapes"
+        case .lemon:      "Lemon"
+        case .broccoli:   "Broccoli"
+        case .watermelon: "Watermelon"
+        case .cherry:     "Cherry"
+        case .orange:     "Orange"
+        case .corn:       "Corn"
+        case .pear:       "Pear"
+        }
+    }
+
+    var emoji: String {
+        switch self {
+        case .apple:      "🍎"
+        case .banana:     "🍌"
+        case .carrot:     "🥕"
+        case .strawberry: "🍓"
+        case .grapes:     "🍇"
+        case .lemon:      "🍋"
+        case .broccoli:   "🥦"
+        case .watermelon: "🍉"
+        case .cherry:     "🍒"
+        case .orange:     "🍊"
+        case .corn:       "🌽"
+        case .pear:       "🍐"
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .apple:      Color(red: 0.85, green: 0.2, blue: 0.2)    // red
+        case .banana:     Color(red: 0.95, green: 0.8, blue: 0.2)    // yellow
+        case .carrot:     Color(red: 1.0, green: 0.55, blue: 0.15)   // orange
+        case .strawberry: Color(red: 0.9, green: 0.25, blue: 0.3)    // strawberry red
+        case .grapes:     Color(red: 0.55, green: 0.3, blue: 0.7)    // purple
+        case .lemon:      Color(red: 0.9, green: 0.8, blue: 0.2)     // lemon yellow
+        case .broccoli:   Color(red: 0.3, green: 0.65, blue: 0.3)    // green
+        case .watermelon: Color(red: 0.35, green: 0.7, blue: 0.4)    // watermelon green
+        case .cherry:     Color(red: 0.7, green: 0.15, blue: 0.2)    // dark red
+        case .orange:     Color(red: 1.0, green: 0.6, blue: 0.15)    // orange
+        case .corn:       Color(red: 0.9, green: 0.75, blue: 0.2)    // golden yellow
+        case .pear:       Color(red: 0.55, green: 0.75, blue: 0.3)   // pear green
+        }
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        FoodCard(food: .apple)
+            .frame(width: 200, height: 260)
+        FoodCard(food: .carrot)
+            .frame(width: 200, height: 260)
+    }
+    .padding()
+}
diff --git a/FoodView.swift b/FoodView.swift
new file mode 100644
index 0000000..4ee09d2
--- /dev/null
+++ b/FoodView.swift
@@ -0,0 +1,29 @@
+//
+//  FoodView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays all 12 fruits and vegetables as large, tappable cards in a scrollable grid.
+struct FoodView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 280), spacing: 24)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 24) {
+                ForEach(LearnFood.allCases) { food in
+                    FoodCard(food: food)
+                        .frame(height: 200)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    FoodView()
+}
diff --git a/LetterCard.swift b/LetterCard.swift
new file mode 100644
index 0000000..06183ae
--- /dev/null
+++ b/LetterCard.swift
@@ -0,0 +1,84 @@
+//
+//  LetterCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large, colorful card representing one letter of the alphabet.
+/// Tapping the card plays a bounce animation and speaks the letter aloud.
+struct LetterCard: View {
+    let letter: LearnLetter
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(letter.color.gradient)
+                .shadow(color: letter.color.opacity(0.4), radius: 8, y: 4)
+
+            Text(letter.character)
+                .font(.system(size: 64, weight: .heavy, design: .rounded))
+                .foregroundStyle(.white)
+        }
+        .scaleEffect(isTapped ? 1.12 : 1.0)
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(letter.spokenName)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(letter.spokenName)
+        .accessibilityAddTraits(.isButton)
+    }
+}
+
+// MARK: - Letter model
+
+enum LearnLetter: Int, CaseIterable, Identifiable {
+    case a, b, c, d, e, f, g, h, i, j, k, l, m,
+         n, o, p, q, r, s, t, u, v, w, x, y, z
+
+    var id: Int { rawValue }
+
+    var character: String {
+        String(Character(UnicodeScalar(65 + rawValue)!))
+    }
+
+    /// Speak the letter name clearly for a child.
+    /// Lowercase prevents TTS from saying "Capital A".
+    var spokenName: String {
+        character.lowercased()
+    }
+
+    /// Cycle through 8 bright colors to give variety without unique-per-letter mapping.
+    var color: Color {
+        let palette: [Color] = [
+            Color(red: 0.9, green: 0.3, blue: 0.3),   // red
+            Color(red: 1.0, green: 0.55, blue: 0.2),   // orange
+            Color(red: 1.0, green: 0.8, blue: 0.2),    // yellow
+            Color(red: 0.35, green: 0.75, blue: 0.4),   // green
+            Color(red: 0.2, green: 0.65, blue: 0.9),    // blue
+            Color(red: 0.55, green: 0.4, blue: 0.85),   // purple
+            Color(red: 0.85, green: 0.4, blue: 0.6),    // pink
+            Color(red: 0.2, green: 0.75, blue: 0.75),   // teal
+        ]
+        return palette[rawValue % palette.count]
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        LetterCard(letter: .a)
+            .frame(width: 140, height: 160)
+        LetterCard(letter: .b)
+            .frame(width: 140, height: 160)
+        LetterCard(letter: .c)
+            .frame(width: 140, height: 160)
+    }
+    .padding()
+}
diff --git a/NumberCard.swift b/NumberCard.swift
new file mode 100644
index 0000000..9ae1c39
--- /dev/null
+++ b/NumberCard.swift
@@ -0,0 +1,136 @@
+//
+//  NumberCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large, colorful card representing one number (1–10).
+/// Tapping the card plays a bounce animation and speaks the number name aloud.
+struct NumberCard: View {
+    let number: LearnNumber
+    var isHighlighted = false
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(number.color.gradient)
+                .shadow(color: number.color.opacity(0.4), radius: 8, y: 4)
+
+            VStack(spacing: 10) {
+                Text("\(number.numeral)")
+                    .font(.system(size: 60, weight: .heavy, design: .rounded))
+                    .foregroundStyle(.white)
+
+                Text(number.name)
+                    .font(.system(size: 28, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white)
+
+                // Dot pattern showing the quantity
+                DotPattern(count: number.numeral)
+                    .padding(.top, 4)
+            }
+        }
+        .overlay {
+            if isHighlighted {
+                RoundedRectangle(cornerRadius: 32, style: .continuous)
+                    .strokeBorder(.white, lineWidth: 5)
+            }
+        }
+        .shadow(color: isHighlighted ? .white.opacity(0.8) : .clear, radius: 12)
+        .scaleEffect(isTapped ? 1.12 : (isHighlighted ? 1.04 : 1.0))
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(number.name)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(number.name)
+        .accessibilityAddTraits(.isButton)
+    }
+}
+
+// MARK: - Number model
+
+enum LearnNumber: Int, CaseIterable, Identifiable {
+    case one = 1, two, three, four, five, six, seven, eight, nine, ten
+
+    var id: Int { rawValue }
+
+    /// Highlight the number matching today's day-of-month if it falls in 1–10.
+    static var current: LearnNumber? {
+        let day = Calendar.current.component(.day, from: Date())
+        return LearnNumber(rawValue: day)
+    }
+
+    var numeral: Int { rawValue }
+
+    var name: String {
+        switch self {
+        case .one:   "One"
+        case .two:   "Two"
+        case .three: "Three"
+        case .four:  "Four"
+        case .five:  "Five"
+        case .six:   "Six"
+        case .seven: "Seven"
+        case .eight: "Eight"
+        case .nine:  "Nine"
+        case .ten:   "Ten"
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .one:   Color(red: 0.9, green: 0.3, blue: 0.3)   // red
+        case .two:   Color(red: 1.0, green: 0.55, blue: 0.2)  // orange
+        case .three: Color(red: 1.0, green: 0.8, blue: 0.2)   // yellow
+        case .four:  Color(red: 0.35, green: 0.75, blue: 0.4)  // green
+        case .five:  Color(red: 0.2, green: 0.7, blue: 0.85)   // sky blue
+        case .six:   Color(red: 0.4, green: 0.45, blue: 0.9)   // blue
+        case .seven: Color(red: 0.6, green: 0.4, blue: 0.85)   // purple
+        case .eight: Color(red: 0.85, green: 0.35, blue: 0.6)  // pink
+        case .nine:  Color(red: 0.5, green: 0.75, blue: 0.45)  // lime
+        case .ten:   Color(red: 0.25, green: 0.8, blue: 0.75)  // teal
+        }
+    }
+}
+
+// MARK: - Dot pattern
+
+/// Displays a grid of filled circles representing a quantity.
+/// 1–5 dots use centered rows; 6–10 use a 5-column grid that wraps naturally.
+struct DotPattern: View {
+    let count: Int
+
+    private var columns: [GridItem] {
+        let maxCols = count <= 5 ? min(count, 5) : 5
+        return Array(repeating: GridItem(.fixed(14), spacing: 6), count: maxCols)
+    }
+
+    var body: some View {
+        LazyVGrid(columns: columns, spacing: 6) {
+            ForEach(0..<count, id: \.self) { _ in
+                Circle()
+                    .fill(.white.opacity(0.8))
+                    .frame(width: 14, height: 14)
+            }
+        }
+        .frame(maxWidth: .infinity)
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        NumberCard(number: .three, isHighlighted: true)
+            .frame(width: 280, height: 300)
+        NumberCard(number: .seven)
+            .frame(width: 280, height: 300)
+    }
+    .padding()
+}
diff --git a/NumbersView.swift b/NumbersView.swift
new file mode 100644
index 0000000..b6d5de6
--- /dev/null
+++ b/NumbersView.swift
@@ -0,0 +1,33 @@
+//
+//  NumbersView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays numbers 1–10 as large, tappable cards in a scrollable grid.
+/// Adapts between 1 and 2 columns depending on available width.
+struct NumbersView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 300, maximum: 500), spacing: 24)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 24) {
+                ForEach(LearnNumber.allCases) { number in
+                    NumberCard(
+                        number: number,
+                        isHighlighted: number == .current
+                    )
+                    .frame(height: 200)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    NumbersView()
+}
diff --git a/Screenshots/ipad.png b/Screenshots/ipad.png
index b840167..b68d40f 100644
Binary files a/Screenshots/ipad.png and b/Screenshots/ipad.png differ
diff --git a/Screenshots/iphone.png b/Screenshots/iphone.png
index b767322..0818b5c 100644
Binary files a/Screenshots/iphone.png and b/Screenshots/iphone.png differ
diff --git a/ShapeCard.swift b/ShapeCard.swift
new file mode 100644
index 0000000..ebc71e8
--- /dev/null
+++ b/ShapeCard.swift
@@ -0,0 +1,130 @@
+//
+//  ShapeCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large, colorful card representing one shape.
+/// Tapping the card plays a bounce animation and speaks the shape name aloud.
+struct ShapeCard: View {
+    let shape: LearnShape
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(shape.color.gradient)
+                .shadow(color: shape.color.opacity(0.4), radius: 8, y: 4)
+
+            VStack(spacing: 16) {
+                shape.shapeView
+                    .frame(width: 80, height: 80)
+                    .foregroundStyle(.white)
+
+                Text(shape.name)
+                    .font(.system(size: 32, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white)
+            }
+        }
+        .scaleEffect(isTapped ? 1.12 : 1.0)
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(shape.name)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(shape.name)
+        .accessibilityAddTraits(.isButton)
+    }
+}
+
+// MARK: - Shape model
+
+enum LearnShape: Int, CaseIterable, Identifiable {
+    case circle, square, triangle, star, heart, oval, diamond, rectangle
+
+    var id: Int { rawValue }
+
+    var name: String {
+        switch self {
+        case .circle:    "Circle"
+        case .square:    "Square"
+        case .triangle:  "Triangle"
+        case .star:      "Star"
+        case .heart:     "Heart"
+        case .oval:      "Oval"
+        case .diamond:   "Diamond"
+        case .rectangle: "Rectangle"
+        }
+    }
+
+    @ViewBuilder
+    var shapeView: some View {
+        switch self {
+        case .circle:
+            Circle().fill(.white)
+        case .square:
+            SwiftUI.Rectangle().fill(.white)
+        case .triangle:
+            TriangleShape().fill(.white)
+        case .star:
+            Image(systemName: "star.fill")
+                .font(.system(size: 72))
+                .foregroundStyle(.white)
+        case .heart:
+            Image(systemName: "heart.fill")
+                .font(.system(size: 72))
+                .foregroundStyle(.white)
+        case .oval:
+            Ellipse().fill(.white)
+                .frame(width: 90, height: 60)
+        case .diamond:
+            SwiftUI.Rectangle().fill(.white)
+                .frame(width: 60, height: 60)
+                .rotationEffect(.degrees(45))
+        case .rectangle:
+            SwiftUI.Rectangle().fill(.white)
+                .frame(width: 90, height: 55)
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .circle:    Color(red: 0.9, green: 0.3, blue: 0.3)   // red
+        case .square:    Color(red: 0.3, green: 0.5, blue: 0.9)   // blue
+        case .triangle:  Color(red: 0.35, green: 0.75, blue: 0.45) // green
+        case .star:      Color(red: 1.0, green: 0.75, blue: 0.15)  // gold
+        case .heart:     Color(red: 0.9, green: 0.35, blue: 0.5)   // pink
+        case .oval:      Color(red: 0.55, green: 0.4, blue: 0.85)  // purple
+        case .diamond:   Color(red: 0.2, green: 0.75, blue: 0.8)   // teal
+        case .rectangle: Color(red: 1.0, green: 0.55, blue: 0.2)   // orange
+        }
+    }
+}
+
+/// A simple triangle drawn with a Path.
+struct TriangleShape: Shape {
+    func path(in rect: CGRect) -> Path {
+        Path { path in
+            path.move(to: CGPoint(x: rect.midX, y: rect.minY))
+            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
+            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
+            path.closeSubpath()
+        }
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        ShapeCard(shape: .triangle)
+            .frame(width: 200, height: 260)
+        ShapeCard(shape: .star)
+            .frame(width: 200, height: 260)
+    }
+    .padding()
+}
diff --git a/ShapesView.swift b/ShapesView.swift
new file mode 100644
index 0000000..347dbad
--- /dev/null
+++ b/ShapesView.swift
@@ -0,0 +1,30 @@
+//
+//  ShapesView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays 8 shapes as large, tappable cards in a scrollable grid.
+/// Adapts between 1 and 2 columns depending on available width.
+struct ShapesView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 300, maximum: 500), spacing: 24)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 24) {
+                ForEach(LearnShape.allCases) { shape in
+                    ShapeCard(shape: shape)
+                        .frame(height: 200)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    ShapesView()
+}
diff --git a/TodayView.swift b/TodayView.swift
new file mode 100644
index 0000000..4fa0492
--- /dev/null
+++ b/TodayView.swift
@@ -0,0 +1,84 @@
+//
+//  TodayView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single-screen summary of today: day of the week, date, month, and season.
+/// The child taps the large card to hear "Today is [day], [month] [date]. It is [season]."
+struct TodayView: View {
+    @State private var isTapped = false
+
+    private var todayText: String {
+        let day = DayOfWeek.current.name
+        let month = MonthOfYear.current.name
+        let date = Calendar.current.component(.day, from: Date())
+        let season = Season.current.name
+        return "Today is \(day), \(month) \(date). It is \(season)."
+    }
+
+    var body: some View {
+        ScrollView {
+            VStack(spacing: 32) {
+                // Main today card
+                ZStack {
+                    RoundedRectangle(cornerRadius: 32, style: .continuous)
+                        .fill(Color.orange.gradient)
+                        .shadow(color: Color.orange.opacity(0.4), radius: 8, y: 4)
+
+                    VStack(spacing: 20) {
+                        Image(systemName: "sun.horizon.fill")
+                            .font(.system(size: 80))
+                            .foregroundStyle(.white)
+                            .symbolRenderingMode(.hierarchical)
+
+                        // Day of the week
+                        Text(DayOfWeek.current.name)
+                            .font(.system(size: 48, weight: .bold, design: .rounded))
+                            .foregroundStyle(.white)
+
+                        // Date number
+                        let dayNumber = Calendar.current.component(.day, from: Date())
+                        Text("\(dayNumber)")
+                            .font(.system(size: 72, weight: .heavy, design: .rounded))
+                            .foregroundStyle(.white.opacity(0.9))
+
+                        // Month and season
+                        HStack(spacing: 24) {
+                            Label(MonthOfYear.current.name, systemImage: MonthOfYear.current.symbol)
+                            Label(Season.current.name, systemImage: Season.current.symbol)
+                        }
+                        .font(.system(size: 24, weight: .semibold, design: .rounded))
+                        .foregroundStyle(.white.opacity(0.9))
+                    }
+                    .padding(.vertical, 32)
+                }
+                .frame(height: 420)
+                .scaleEffect(isTapped ? 1.08 : 1.0)
+                .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+                .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+                .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+                .onTapGesture {
+                    isTapped = true
+                    SpeechSynthesizer.shared.speak(todayText)
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                        isTapped = false
+                    }
+                }
+                .accessibilityLabel(todayText)
+                .accessibilityAddTraits(.isButton)
+
+                // Tap hint
+                Label("Tap to hear today", systemImage: "speaker.wave.2.fill")
+                    .font(.system(size: 22, weight: .medium, design: .rounded))
+                    .foregroundStyle(.secondary)
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    TodayView()
+}
diff --git a/WeatherCard.swift b/WeatherCard.swift
new file mode 100644
index 0000000..8b12786
--- /dev/null
+++ b/WeatherCard.swift
@@ -0,0 +1,102 @@
+//
+//  WeatherCard.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// A single large, colorful card representing one weather type.
+/// Tapping the card plays a bounce animation and speaks the weather name aloud.
+struct WeatherCard: View {
+    let weather: LearnWeather
+    @State private var isTapped = false
+
+    var body: some View {
+        ZStack {
+            RoundedRectangle(cornerRadius: 32, style: .continuous)
+                .fill(weather.color.gradient)
+                .shadow(color: weather.color.opacity(0.4), radius: 8, y: 4)
+
+            VStack(spacing: 16) {
+                Image(systemName: weather.symbol)
+                    .font(.system(size: 64))
+                    .foregroundStyle(.white)
+                    .symbolRenderingMode(.hierarchical)
+
+                Text(weather.name)
+                    .font(.system(size: 32, weight: .bold, design: .rounded))
+                    .foregroundStyle(.white)
+            }
+        }
+        .scaleEffect(isTapped ? 1.12 : 1.0)
+        .contentShape(RoundedRectangle(cornerRadius: 32, style: .continuous))
+        .rotation3DEffect(.degrees(isTapped ? 6 : 0), axis: (x: 1, y: 0, z: 0))
+        .animation(.spring(response: 0.35, dampingFraction: 0.5), value: isTapped)
+        .onTapGesture {
+            isTapped = true
+            SpeechSynthesizer.shared.speak(weather.name)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                isTapped = false
+            }
+        }
+        .accessibilityLabel(weather.name)
+        .accessibilityAddTraits(.isButton)
+    }
+}
+
+// MARK: - Weather model
+
+enum LearnWeather: Int, CaseIterable, Identifiable {
+    case sunny, cloudy, rainy, snowy, windy, stormy, foggy, rainbow
+
+    var id: Int { rawValue }
+
+    var name: String {
+        switch self {
+        case .sunny:   "Sunny"
+        case .cloudy:  "Cloudy"
+        case .rainy:   "Rainy"
+        case .snowy:   "Snowy"
+        case .windy:   "Windy"
+        case .stormy:  "Stormy"
+        case .foggy:   "Foggy"
+        case .rainbow: "Rainbow"
+        }
+    }
+
+    var symbol: String {
+        switch self {
+        case .sunny:   "sun.max.fill"
+        case .cloudy:  "cloud.fill"
+        case .rainy:   "cloud.rain.fill"
+        case .snowy:   "cloud.snow.fill"
+        case .windy:   "wind"
+        case .stormy:  "cloud.bolt.rain.fill"
+        case .foggy:   "cloud.fog.fill"
+        case .rainbow: "rainbow"
+        }
+    }
+
+    var color: Color {
+        switch self {
+        case .sunny:   Color(red: 1.0, green: 0.7, blue: 0.2)    // orange
+        case .cloudy:  Color(red: 0.6, green: 0.6, blue: 0.65)   // gray
+        case .rainy:   Color(red: 0.3, green: 0.6, blue: 0.9)    // blue
+        case .snowy:   Color(red: 0.55, green: 0.8, blue: 0.95)  // light blue
+        case .windy:   Color(red: 0.25, green: 0.7, blue: 0.7)   // teal
+        case .stormy:  Color(red: 0.2, green: 0.3, blue: 0.6)    // dark blue
+        case .foggy:   Color(red: 0.6, green: 0.5, blue: 0.7)    // muted purple
+        case .rainbow: Color(red: 0.9, green: 0.45, blue: 0.6)   // pink
+        }
+    }
+}
+
+#Preview {
+    HStack(spacing: 24) {
+        WeatherCard(weather: .sunny)
+            .frame(width: 200, height: 260)
+        WeatherCard(weather: .stormy)
+            .frame(width: 200, height: 260)
+    }
+    .padding()
+}
diff --git a/WeatherView.swift b/WeatherView.swift
new file mode 100644
index 0000000..e3b848b
--- /dev/null
+++ b/WeatherView.swift
@@ -0,0 +1,29 @@
+//
+//  WeatherView.swift
+//  DayByDay
+//
+
+import SwiftUI
+
+/// Displays all 8 weather types as large, tappable cards in a scrollable grid.
+struct WeatherView: View {
+    private let columns = [
+        GridItem(.adaptive(minimum: 280), spacing: 24)
+    ]
+
+    var body: some View {
+        ScrollView {
+            LazyVGrid(columns: columns, spacing: 24) {
+                ForEach(LearnWeather.allCases) { weather in
+                    WeatherCard(weather: weather)
+                        .frame(height: 200)
+                }
+            }
+            .padding(32)
+        }
+    }
+}
+
+#Preview {
+    WeatherView()
+}
diff --git a/claude.md b/claude.md
index ae1f595..4be8e25 100644
--- a/claude.md
+++ b/claude.md
@@ -1,8 +1,9 @@
 # DayByDay – Project Context
 
 ## What this is
-An iPad app for pre-K children (ages 4–5) that teaches days of the week, months
-of the year, and seasons through simple, tap-based interactions.
+An iOS app for pre-K children (ages 4–5) that teaches days of the week, months
+of the year, seasons, numbers, colors, shapes, animals, the alphabet, weather,
+body parts, and fruits & vegetables through simple, tap-based interactions.
 
 ## Target user
 A single child, approximately 4–5 years old, preparing to start school. Cannot
@@ -13,15 +14,28 @@ independently.
 - Large tap targets — nothing small or fiddly
 - Bright, clean colors — simple and friendly, not cluttered
 - No text-dependent interactions — the child cannot read
-- Audio is the primary feedback mechanism (day/month/season name read aloud on
-  tap)
+- Audio is the primary feedback mechanism (names read aloud on tap)
 - Simple animations on interaction — enough to delight, not enough to distract
 - No timers, no failure states, no scores — purely exploratory
 
-## Content
+## Content (12 categories, accessible from a NavigationStack home grid)
+- Today: summary card with current day, date, month, season
 - Days of the week: Sunday through Saturday
 - Months of the year: January through December
 - Seasons: Spring, Summer, Fall, Winter
+- Numbers: 1 through 10 (numeral + word + dot pattern)
+- Colors: 10 basic colors (Red, Orange, Yellow, Green, Blue, Purple, Pink,
+  Brown, Black, White)
+- Shapes: 8 shapes (Circle, Square, Triangle, Star, Heart, Oval, Diamond,
+  Rectangle)
+- Animals: 12 animals with SF Symbol icons
+- Alphabet: A through Z
+- Weather: 8 weather types (Sunny, Cloudy, Rainy, Snowy, Windy, Stormy,
+  Foggy, Rainbow)
+- Body Parts: 12 body parts (Head, Eyes, Ears, Nose, Mouth, Hands, Fingers,
+  Arms, Legs, Feet, Heart, Belly)
+- Fruits & Vegetables: 12 foods (Apple, Banana, Carrot, Strawberry, Grapes,
+  Lemon, Broccoli, Watermelon, Cherry, Orange, Corn, Pear)
 
 ## Technical constraints
 - SwiftUI, iOS only
@@ -30,6 +44,7 @@ independently.
 - Targets latest iOS
 
 ## What good looks like
-Each concept (day, month, season) should feel like its own tactile card or
-object the child can tap and explore. Transitions should be smooth. Audio should
-play immediately on tap with no perceptible delay.
+Each concept should feel like its own tactile card or object the child can tap
+and explore. Transitions should be smooth. Audio should play immediately on tap
+with no perceptible delay. The home screen is a colorful 2-column grid of square category
+tiles that navigate into each section.