// // EntryListView.swift // Feels (iOS) // // Created by Trey Tartt on 3/6/22. // import SwiftUI import CoreMotion struct EntryListView: View { @AppStorage(UserDefaultsStore.Keys.moodImages.rawValue, store: GroupUserDefaults.groupDefaults) private var imagePack: MoodImages = .FontAwesome @AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default @AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = DefaultTextColor.textColor @AppStorage(UserDefaultsStore.Keys.dayViewStyle.rawValue, store: GroupUserDefaults.groupDefaults) private var dayViewStyle: DayViewStyle = .classic @Environment(\.colorScheme) private var colorScheme public let entry: MoodEntryModel private var moodColor: Color { moodTint.color(forMood: entry.mood) } private var isMissing: Bool { entry.moodValue == Mood.missing.rawValue } var body: some View { switch dayViewStyle { case .classic: classicStyle case .minimal: minimalStyle case .compact: compactStyle case .bubble: bubbleStyle case .grid: gridStyle case .aura: auraStyle case .chronicle: chronicleStyle case .neon: neonStyle case .ink: inkStyle case .prism: prismStyle case .tape: tapeStyle case .morph: morphStyle case .stack: stackStyle case .wave: waveStyle case .pattern: patternStyle case .leather: leatherStyle case .glass: glassStyle case .threeD: threeDStyle case .motion: motionStyle case .micro: microStyle } } // MARK: - Classic Style (Original) private var classicStyle: some View { HStack(spacing: 16) { // Large mood icon with gradient background ZStack { Circle() .fill( LinearGradient( colors: isMissing ? [Color.gray.opacity(0.3), Color.gray.opacity(0.1)] : [moodColor.opacity(0.8), moodColor.opacity(0.4)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: 56, height: 56) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 32, height: 32) .foregroundColor(isMissing ? .gray : .white) } .shadow(color: isMissing ? .clear : moodColor.opacity(0.4), radius: 8, x: 0, y: 4) VStack(alignment: .leading, spacing: 6) { HStack(spacing: 6) { Text(Random.weekdayName(fromDate: entry.forDate)) .font(.system(size: 17, weight: .semibold)) .foregroundColor(textColor) Text("•") .foregroundColor(textColor.opacity(0.4)) Text(Random.dayFormat(fromDate: entry.forDate)) .font(.system(size: 17, weight: .medium)) .foregroundColor(textColor.opacity(0.8)) } if isMissing { Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 14, weight: .medium)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 14, weight: .semibold)) .foregroundColor(moodColor) .padding(.horizontal, 10) .padding(.vertical, 4) .background( Capsule() .fill(moodColor.opacity(0.15)) ) } } Spacer() Image(systemName: "chevron.right") .font(.system(size: 14, weight: .semibold)) .foregroundColor(textColor.opacity(0.3)) } .padding(.horizontal, 18) .padding(.vertical, 16) .background( RoundedRectangle(cornerRadius: 18) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) .shadow( color: isMissing ? .clear : moodColor.opacity(colorScheme == .dark ? 0.2 : 0.12), radius: 12, x: 0, y: 4 ) ) .overlay( RoundedRectangle(cornerRadius: 18) .stroke( isMissing ? Color.gray.opacity(0.2) : moodColor.opacity(0.2), lineWidth: 1 ) ) } // MARK: - Minimal Style private var minimalStyle: some View { HStack(spacing: 14) { // Simple flat circle with icon Circle() .fill(isMissing ? Color.gray.opacity(0.15) : moodColor.opacity(0.15)) .frame(width: 44, height: 44) .overlay( imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 22, height: 22) .foregroundColor(isMissing ? .gray : moodColor) ) VStack(alignment: .leading, spacing: 3) { Text(entry.forDate, format: .dateTime.weekday(.wide).day()) .font(.system(size: 16, weight: .medium)) .foregroundColor(textColor) if isMissing { Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 13)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 13, weight: .medium)) .foregroundColor(moodColor) } } Spacer() } .padding(.horizontal, 16) .padding(.vertical, 14) .background( RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) ) } // MARK: - Compact Style (Timeline) private var compactStyle: some View { HStack(spacing: 12) { // Timeline indicator VStack(spacing: 0) { Circle() .fill(isMissing ? Color.gray.opacity(0.4) : moodColor) .frame(width: 12, height: 12) } // Date column VStack(alignment: .leading, spacing: 0) { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 20, weight: .bold, design: .rounded)) .foregroundColor(textColor) Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) .font(.system(size: 11, weight: .medium)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) } .frame(width: 36) // Mood indicator bar if isMissing { RoundedRectangle(cornerRadius: 6) .fill(Color.gray.opacity(0.15)) .frame(height: 32) .overlay( Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 12, weight: .medium)) .foregroundColor(.gray) ) } else { RoundedRectangle(cornerRadius: 6) .fill(moodColor.opacity(0.2)) .frame(height: 32) .overlay( HStack(spacing: 6) { imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) .foregroundColor(moodColor) Text(entry.moodString) .font(.system(size: 13, weight: .semibold)) .foregroundColor(moodColor) } ) } Spacer(minLength: 0) } .padding(.horizontal, 12) .padding(.vertical, 10) } // MARK: - Bubble Style private var bubbleStyle: some View { HStack(spacing: 0) { // Full-width colored background HStack(spacing: 14) { // Icon imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 28, height: 28) .foregroundColor(isMissing ? .gray : .white) VStack(alignment: .leading, spacing: 2) { Text(entry.forDate, format: .dateTime.weekday(.wide).day()) .font(.system(size: 15, weight: .semibold)) .foregroundColor(isMissing ? textColor : .white) if isMissing { Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 13)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 13, weight: .medium)) .foregroundColor(.white.opacity(0.85)) } } Spacer() Image(systemName: "chevron.right") .font(.system(size: 12, weight: .semibold)) .foregroundColor(isMissing ? textColor.opacity(0.3) : .white.opacity(0.6)) } .padding(.horizontal, 18) .padding(.vertical, 14) .background( RoundedRectangle(cornerRadius: 16) .fill( isMissing ? (colorScheme == .dark ? Color(.systemGray5) : Color(.systemGray6)) : moodColor ) ) .shadow( color: isMissing ? .clear : moodColor.opacity(0.35), radius: 8, x: 0, y: 4 ) } } // MARK: - Grid Style (3 per row) private var gridStyle: some View { VStack(spacing: 6) { // Mood icon circle ZStack { Circle() .fill( isMissing ? Color.gray.opacity(0.2) : moodColor ) .aspectRatio(1, contentMode: .fit) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .padding(16) .foregroundColor(isMissing ? .gray : .white) } .shadow( color: isMissing ? .clear : moodColor.opacity(0.3), radius: 6, x: 0, y: 3 ) // Day number Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 16, weight: .bold, design: .rounded)) .foregroundColor(textColor) // Weekday abbreviation Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) .font(.system(size: 11, weight: .medium)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) } .frame(maxWidth: .infinity) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: 14) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) ) } // MARK: - Aura Style (Atmospheric glowing entries) private var auraStyle: some View { HStack(spacing: 0) { // Giant day number - the visual hero Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 64, weight: .black, design: .rounded)) .foregroundStyle( isMissing ? LinearGradient(colors: [Color.gray.opacity(0.3), Color.gray.opacity(0.15)], startPoint: .top, endPoint: .bottom) : LinearGradient(colors: [moodColor, moodColor.opacity(0.6)], startPoint: .top, endPoint: .bottom) ) .frame(width: 90) .shadow(color: isMissing ? .clear : moodColor.opacity(0.5), radius: 20, x: 0, y: 0) // Content area with glowing aura VStack(alignment: .leading, spacing: 8) { // Weekday with elegant typography Text(entry.forDate, format: .dateTime.weekday(.wide)) .font(.system(size: 13, weight: .semibold, design: .rounded)) .textCase(.uppercase) .tracking(2) .foregroundColor(textColor.opacity(0.5)) // Mood display with icon HStack(spacing: 10) { // Glowing mood orb ZStack { // Outer glow Circle() .fill( RadialGradient( colors: isMissing ? [Color.gray.opacity(0.2), Color.clear] : [moodColor.opacity(0.4), Color.clear], center: .center, startRadius: 0, endRadius: 30 ) ) .frame(width: 60, height: 60) // Inner solid circle Circle() .fill( isMissing ? Color.gray.opacity(0.3) : moodColor ) .frame(width: 38, height: 38) // Icon imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 20, height: 20) .foregroundColor(isMissing ? .gray : .white) } VStack(alignment: .leading, spacing: 2) { if isMissing { Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 15, weight: .medium)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 20, weight: .bold, design: .rounded)) .foregroundColor(textColor) // Month context Text(entry.forDate, format: .dateTime.month(.wide)) .font(.system(size: 12, weight: .medium)) .foregroundColor(textColor.opacity(0.4)) } } Spacer() // Subtle indicator if !isMissing { Circle() .fill(moodColor) .frame(width: 8, height: 8) .shadow(color: moodColor, radius: 4, x: 0, y: 0) } } } .padding(.leading, 4) .padding(.trailing, 16) } .padding(.vertical, 20) .padding(.horizontal, 16) .background( ZStack { // Base layer RoundedRectangle(cornerRadius: 24) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) // Mood glow overlay (subtle gradient at edges) if !isMissing { RoundedRectangle(cornerRadius: 24) .fill( LinearGradient( colors: [ moodColor.opacity(colorScheme == .dark ? 0.15 : 0.08), Color.clear, Color.clear ], startPoint: .leading, endPoint: .trailing ) ) } } ) .overlay( RoundedRectangle(cornerRadius: 24) .stroke( isMissing ? Color.gray.opacity(0.15) : moodColor.opacity(colorScheme == .dark ? 0.3 : 0.2), lineWidth: 1 ) ) .shadow( color: isMissing ? Color.black.opacity(0.05) : moodColor.opacity(colorScheme == .dark ? 0.25 : 0.15), radius: 16, x: 0, y: 8 ) } // MARK: - Chronicle Style (Editorial/Magazine) private var chronicleStyle: some View { VStack(alignment: .leading, spacing: 0) { // Top rule line Rectangle() .fill(isMissing ? Color.gray.opacity(0.3) : moodColor) .frame(height: 3) HStack(alignment: .top, spacing: 16) { // Left column: Giant day number in serif VStack(alignment: .trailing, spacing: 0) { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 72, weight: .regular, design: .serif)) .foregroundColor(textColor) .frame(width: 80) Text(entry.forDate, format: .dateTime.weekday(.abbreviated).month(.abbreviated)) .font(.system(size: 11, weight: .regular, design: .serif)) .italic() .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) } // Vertical divider Rectangle() .fill(textColor.opacity(0.15)) .frame(width: 1) // Right column: Mood content VStack(alignment: .leading, spacing: 12) { if isMissing { Text("Entry Missing") .font(.system(size: 24, weight: .regular, design: .serif)) .italic() .foregroundColor(.gray) Text("Tap to record your mood for this day") .font(.system(size: 13, weight: .regular, design: .serif)) .foregroundColor(.gray.opacity(0.7)) } else { // Pull-quote style mood name HStack(spacing: 8) { Rectangle() .fill(moodColor) .frame(width: 4) Text("\"\(entry.moodString)\"") .font(.system(size: 28, weight: .regular, design: .serif)) .italic() .foregroundColor(textColor) } // Icon and descriptor HStack(spacing: 10) { imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 20, height: 20) .foregroundColor(moodColor) Text("Recorded mood entry") .font(.system(size: 12, weight: .regular, design: .serif)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) .tracking(1.5) } } } .padding(.vertical, 8) Spacer() } .padding(.horizontal, 16) .padding(.vertical, 12) // Bottom rule line (thinner) Rectangle() .fill(textColor.opacity(0.1)) .frame(height: 1) } .background(colorScheme == .dark ? Color(.systemGray6) : .white) } // MARK: - Neon Style (Cyberpunk/Synthwave) private var neonStyle: some View { ZStack { // Dark base with scanline effect RoundedRectangle(cornerRadius: 4) .fill(Color.black) // Scanline overlay VStack(spacing: 2) { ForEach(0..<20, id: \.self) { _ in Rectangle() .fill(Color.white.opacity(0.03)) .frame(height: 1) Spacer().frame(height: 3) } } .clipShape(RoundedRectangle(cornerRadius: 4)) // Content HStack(spacing: 16) { // Neon-outlined mood indicator ZStack { // Glow effect RoundedRectangle(cornerRadius: 8) .stroke(isMissing ? Color.gray : moodColor, lineWidth: 2) .blur(radius: 4) .opacity(0.8) RoundedRectangle(cornerRadius: 8) .stroke(isMissing ? Color.gray : moodColor, lineWidth: 2) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 28, height: 28) .foregroundColor(isMissing ? .gray : moodColor) .shadow(color: isMissing ? .clear : moodColor, radius: 8, x: 0, y: 0) } .frame(width: 52, height: 52) VStack(alignment: .leading, spacing: 6) { // Date in monospace terminal style Text(entry.forDate, format: .dateTime.year().month(.twoDigits).day(.twoDigits)) .font(.system(size: 13, weight: .medium, design: .monospaced)) .foregroundColor(Color(red: 0.4, green: 1.0, blue: 0.4)) // Terminal green if isMissing { Text("NO_DATA") .font(.system(size: 18, weight: .bold, design: .monospaced)) .foregroundColor(.gray) } else { // Mood in glowing text Text(entry.moodString.uppercased()) .font(.system(size: 18, weight: .black, design: .default)) .foregroundColor(moodColor) .shadow(color: moodColor.opacity(0.8), radius: 6, x: 0, y: 0) .shadow(color: moodColor.opacity(0.4), radius: 12, x: 0, y: 0) } // Weekday Text(entry.forDate, format: .dateTime.weekday(.wide)) .font(.system(size: 11, weight: .medium, design: .monospaced)) .foregroundColor(.white.opacity(0.4)) .textCase(.uppercase) } Spacer() // Chevron with glow Image(systemName: "chevron.right") .font(.system(size: 14, weight: .bold)) .foregroundColor(isMissing ? .gray : moodColor) .shadow(color: isMissing ? .clear : moodColor, radius: 4, x: 0, y: 0) } .padding(.horizontal, 18) .padding(.vertical, 16) // Neon border RoundedRectangle(cornerRadius: 4) .stroke( LinearGradient( colors: isMissing ? [Color.gray.opacity(0.3), Color.gray.opacity(0.1)] : [moodColor.opacity(0.8), moodColor.opacity(0.3)], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 1 ) } .shadow( color: isMissing ? .clear : moodColor.opacity(0.3), radius: 12, x: 0, y: 4 ) } // MARK: - Ink Style (Japanese Zen/Calligraphy) private var inkStyle: some View { HStack(spacing: 20) { // Ensō (Zen circle) representing mood ZStack { // Brush stroke circle effect Circle() .stroke( isMissing ? Color.gray.opacity(0.2) : moodColor.opacity(0.7), style: StrokeStyle(lineWidth: 6, lineCap: .round) ) .frame(width: 56, height: 56) .rotationEffect(.degrees(-30)) // Gap in the circle (zen incomplete circle) Circle() .trim(from: 0, to: 0.85) .stroke( isMissing ? Color.gray.opacity(0.4) : moodColor, style: StrokeStyle(lineWidth: 4, lineCap: .round) ) .frame(width: 44, height: 44) .rotationEffect(.degrees(20)) // Small icon in center imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 18, height: 18) .foregroundColor(isMissing ? .gray.opacity(0.5) : moodColor.opacity(0.8)) } VStack(alignment: .leading, spacing: 8) { // Day number with brush-like weight variation HStack(alignment: .firstTextBaseline, spacing: 4) { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 36, weight: .thin)) .foregroundColor(textColor) VStack(alignment: .leading, spacing: 2) { Text(entry.forDate, format: .dateTime.month(.wide)) .font(.system(size: 11, weight: .light)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) .tracking(2) Text(entry.forDate, format: .dateTime.weekday(.wide)) .font(.system(size: 11, weight: .light)) .foregroundColor(textColor.opacity(0.35)) } } if isMissing { Text("—") .font(.system(size: 20, weight: .ultraLight)) .foregroundColor(.gray.opacity(0.4)) } else { // Mood in delicate typography Text(entry.moodString) .font(.system(size: 17, weight: .light)) .foregroundColor(moodColor) .tracking(1) } } Spacer() // Subtle ink dot indicator if !isMissing { Circle() .fill(moodColor.opacity(0.6)) .frame(width: 6, height: 6) } } .padding(.horizontal, 24) .padding(.vertical, 24) .background( ZStack { // Paper-like texture base RoundedRectangle(cornerRadius: 2) .fill(colorScheme == .dark ? Color(.systemGray6) : Color(white: 0.98)) // Subtle ink wash at edge if !isMissing { HStack { Rectangle() .fill( LinearGradient( colors: [moodColor.opacity(0.08), Color.clear], startPoint: .leading, endPoint: .trailing ) ) .frame(width: 80) Spacer() } } } ) .overlay( // Thin ink border RoundedRectangle(cornerRadius: 2) .stroke(textColor.opacity(0.08), lineWidth: 0.5) ) } // MARK: - Prism Style (Premium Glassmorphism) private var prismStyle: some View { ZStack { // Rainbow refraction edge effect RoundedRectangle(cornerRadius: 20) .fill( AngularGradient( colors: [.red, .orange, .yellow, .green, .blue, .purple, .red], center: .center ) ) .blur(radius: 8) .opacity(isMissing ? 0 : 0.4) // Frosted glass card RoundedRectangle(cornerRadius: 20) .fill(.ultraThinMaterial) .overlay( RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( colors: [ Color.white.opacity(colorScheme == .dark ? 0.1 : 0.5), Color.white.opacity(colorScheme == .dark ? 0.05 : 0.2) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) ) // Content HStack(spacing: 16) { // Glass orb with mood ZStack { Circle() .fill(.ultraThinMaterial) .frame(width: 56, height: 56) Circle() .fill( RadialGradient( colors: isMissing ? [Color.gray.opacity(0.3), Color.gray.opacity(0.1)] : [moodColor.opacity(0.6), moodColor.opacity(0.2)], center: .topLeading, startRadius: 0, endRadius: 40 ) ) .frame(width: 52, height: 52) // Light reflection Circle() .fill( LinearGradient( colors: [Color.white.opacity(0.6), Color.clear], startPoint: .topLeading, endPoint: .center ) ) .frame(width: 52, height: 52) .mask( Circle() .frame(width: 20, height: 20) .offset(x: -10, y: -10) ) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 26, height: 26) .foregroundColor(isMissing ? .gray : .white) } VStack(alignment: .leading, spacing: 6) { Text(entry.forDate, format: .dateTime.weekday(.wide)) .font(.system(size: 15, weight: .semibold)) .foregroundColor(textColor) HStack(spacing: 8) { Text(entry.forDate, format: .dateTime.month(.abbreviated).day()) .font(.system(size: 13, weight: .medium)) .foregroundColor(textColor.opacity(0.6)) if !isMissing { Capsule() .fill(moodColor.opacity(0.2)) .frame(width: 4, height: 4) Text(entry.moodString) .font(.system(size: 13, weight: .semibold)) .foregroundColor(moodColor) } else { Text("Tap to add") .font(.system(size: 13)) .foregroundColor(.gray) } } } Spacer() // Prismatic chevron Image(systemName: "chevron.right") .font(.system(size: 14, weight: .semibold)) .foregroundStyle( isMissing ? AnyShapeStyle(Color.gray.opacity(0.3)) : AnyShapeStyle( LinearGradient( colors: [moodColor, moodColor.opacity(0.5)], startPoint: .top, endPoint: .bottom ) ) ) } .padding(16) } .frame(height: 88) .shadow(color: isMissing ? .clear : moodColor.opacity(0.2), radius: 20, x: 0, y: 10) } // MARK: - Tape Style (Retro Cassette/Mixtape) private var tapeStyle: some View { HStack(spacing: 0) { // Track number column VStack { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 24, weight: .bold, design: .monospaced)) .foregroundColor(isMissing ? .gray : moodColor) } .frame(width: 50) .padding(.vertical, 16) .background( Rectangle() .fill(isMissing ? Color.gray.opacity(0.1) : moodColor.opacity(0.15)) ) // Tape reel visualization HStack(spacing: 12) { // Left reel ZStack { Circle() .stroke(isMissing ? Color.gray.opacity(0.3) : moodColor.opacity(0.4), lineWidth: 3) .frame(width: 32, height: 32) Circle() .fill(isMissing ? Color.gray.opacity(0.2) : moodColor.opacity(0.2)) .frame(width: 18, height: 18) Circle() .fill(colorScheme == .dark ? Color(.systemGray5) : .white) .frame(width: 8, height: 8) } // Track info VStack(alignment: .leading, spacing: 4) { Text(entry.forDate, format: .dateTime.weekday(.wide).month(.abbreviated)) .font(.system(size: 11, weight: .medium, design: .monospaced)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) if isMissing { Text("SIDE B - NO RECORDING") .font(.system(size: 14, weight: .bold, design: .rounded)) .foregroundColor(.gray) } else { Text(entry.moodString.uppercased()) .font(.system(size: 16, weight: .black, design: .rounded)) .foregroundColor(textColor) .tracking(1) } // Tape progress bar GeometryReader { geo in ZStack(alignment: .leading) { Capsule() .fill(textColor.opacity(0.1)) .frame(height: 4) Capsule() .fill(isMissing ? Color.gray : moodColor) .frame(width: geo.size.width * (isMissing ? 0.2 : 0.7), height: 4) } } .frame(height: 4) } Spacer() // Right reel ZStack { Circle() .stroke(isMissing ? Color.gray.opacity(0.3) : moodColor.opacity(0.4), lineWidth: 3) .frame(width: 32, height: 32) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) .foregroundColor(isMissing ? .gray : moodColor) } } .padding(.horizontal, 16) .padding(.vertical, 14) } .background( RoundedRectangle(cornerRadius: 8) .fill(colorScheme == .dark ? Color(.systemGray6) : Color(white: 0.96)) ) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(isMissing ? Color.gray.opacity(0.2) : moodColor.opacity(0.3), lineWidth: 1) ) } // MARK: - Morph Style (Liquid Organic Blobs) private var morphStyle: some View { ZStack { // Organic blob background GeometryReader { geo in ZStack { // Main blob Ellipse() .fill( RadialGradient( colors: isMissing ? [Color.gray.opacity(0.2), Color.gray.opacity(0.05)] : [moodColor.opacity(0.5), moodColor.opacity(0.1)], center: .center, startRadius: 0, endRadius: geo.size.width * 0.6 ) ) .frame(width: geo.size.width * 0.9, height: geo.size.height * 1.2) .offset(x: -geo.size.width * 0.1, y: 0) .blur(radius: 20) // Secondary blob if !isMissing { Ellipse() .fill(moodColor.opacity(0.3)) .frame(width: geo.size.width * 0.4, height: geo.size.height * 0.8) .offset(x: geo.size.width * 0.25, y: geo.size.height * 0.1) .blur(radius: 15) } } } // Content overlay HStack(spacing: 20) { // Morphing mood indicator ZStack { // Outer glow blob Circle() .fill(isMissing ? Color.gray.opacity(0.2) : moodColor.opacity(0.4)) .frame(width: 70, height: 70) .blur(radius: 10) // Inner solid Circle() .fill( LinearGradient( colors: isMissing ? [Color.gray.opacity(0.4), Color.gray.opacity(0.2)] : [moodColor, moodColor.opacity(0.7)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: 50, height: 50) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 24, height: 24) .foregroundColor(.white) } VStack(alignment: .leading, spacing: 8) { // Date with organic flow HStack(spacing: 0) { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 32, weight: .light)) .foregroundColor(textColor) VStack(alignment: .leading, spacing: 0) { Text(entry.forDate, format: .dateTime.month(.abbreviated)) .font(.system(size: 12, weight: .medium)) .foregroundColor(textColor.opacity(0.6)) Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) .font(.system(size: 11, weight: .regular)) .foregroundColor(textColor.opacity(0.4)) } .padding(.leading, 6) } if isMissing { Text("No mood recorded") .font(.system(size: 14, weight: .medium)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 18, weight: .semibold)) .foregroundColor(moodColor) } } Spacer() } .padding(20) } .frame(height: 110) .clipShape(RoundedRectangle(cornerRadius: 28)) .background( RoundedRectangle(cornerRadius: 28) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) ) } // MARK: - Stack Style (Layered Paper Notes) private var stackStyle: some View { ZStack { // Back paper layers (offset for depth) RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? Color(.systemGray5) : Color(white: 0.92)) .offset(x: 4, y: 4) RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? Color(.systemGray5).opacity(0.7) : Color(white: 0.95)) .offset(x: 2, y: 2) // Main note VStack(alignment: .leading, spacing: 0) { // Torn paper edge effect HStack(spacing: 0) { ForEach(0..<20, id: \.self) { i in Rectangle() .fill(isMissing ? Color.gray.opacity(0.3) : moodColor.opacity(0.6)) .frame(width: .infinity, height: CGFloat.random(in: 3...6)) } } .frame(height: 6) HStack(spacing: 16) { // Handwritten-style date VStack(alignment: .center, spacing: 2) { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 36, weight: .light, design: .serif)) .foregroundColor(textColor) Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) .font(.system(size: 12, weight: .medium, design: .serif)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) } .frame(width: 60) // Vertical line divider (like notebook margin) Rectangle() .fill(Color.red.opacity(0.3)) .frame(width: 1) // Content area VStack(alignment: .leading, spacing: 8) { // Lined paper effect Text(entry.forDate, format: .dateTime.month(.wide).year()) .font(.system(size: 12, weight: .regular, design: .serif)) .foregroundColor(textColor.opacity(0.4)) if isMissing { Text("nothing written...") .font(.system(size: 18, weight: .regular, design: .serif)) .italic() .foregroundColor(.gray) } else { HStack(spacing: 10) { imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 24, height: 24) .foregroundColor(moodColor) Text(entry.moodString) .font(.system(size: 20, weight: .medium, design: .serif)) .foregroundColor(textColor) } } // Notebook lines VStack(spacing: 8) { ForEach(0..<2, id: \.self) { _ in Rectangle() .fill(Color.blue.opacity(0.1)) .frame(height: 1) } } } .padding(.vertical, 8) Spacer() } .padding(16) } .background( RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? Color(.systemGray6) : Color(white: 0.98)) ) .shadow(color: Color.black.opacity(0.1), radius: 8, x: 2, y: 4) } } // MARK: - Wave Style (Horizontal Gradient River) private var waveStyle: some View { HStack(spacing: 0) { // Date column - minimal VStack(alignment: .trailing, spacing: 2) { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 28, weight: .thin)) .foregroundColor(textColor) Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) .font(.system(size: 10, weight: .medium)) .foregroundColor(textColor.opacity(0.4)) .textCase(.uppercase) } .frame(width: 50) .padding(.trailing, 12) // Wave gradient bar ZStack(alignment: .leading) { // Gradient wave RoundedRectangle(cornerRadius: 16) .fill( LinearGradient( colors: isMissing ? [Color.gray.opacity(0.2), Color.gray.opacity(0.1)] : [ moodColor, moodColor.opacity(0.7), moodColor.opacity(0.4) ], startPoint: .leading, endPoint: .trailing ) ) // Wave texture overlay if !isMissing { GeometryReader { geo in Path { path in path.move(to: CGPoint(x: 0, y: geo.size.height * 0.5)) for x in stride(from: 0, to: geo.size.width, by: 10) { let y = geo.size.height * 0.5 + sin(x / 20) * 8 path.addLine(to: CGPoint(x: x, y: y)) } path.addLine(to: CGPoint(x: geo.size.width, y: geo.size.height)) path.addLine(to: CGPoint(x: 0, y: geo.size.height)) path.closeSubpath() } .fill(Color.white.opacity(0.15)) } } // Content on wave HStack { // Icon imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 28, height: 28) .foregroundColor(isMissing ? .gray : .white) .padding(.leading, 16) if isMissing { Text("No entry") .font(.system(size: 15, weight: .medium)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 17, weight: .semibold)) .foregroundColor(.white) } Spacer() // Month indicator Text(entry.forDate, format: .dateTime.month(.abbreviated)) .font(.system(size: 11, weight: .bold)) .foregroundColor(isMissing ? .gray : .white.opacity(0.7)) .padding(.trailing, 16) } } .frame(height: 64) .shadow( color: isMissing ? .clear : moodColor.opacity(0.4), radius: 12, x: 0, y: 6 ) } .padding(.vertical, 4) } // MARK: - Pattern Style (Mood icons as repeating background) private var patternStyle: some View { HStack(spacing: 16) { // Large mood icon ZStack { Circle() .fill(isMissing ? Color.gray.opacity(0.2) : moodColor) .frame(width: 54, height: 54) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 28, height: 28) .foregroundColor(.white) } VStack(alignment: .leading, spacing: 6) { Text(entry.forDate, format: .dateTime.weekday(.wide).day()) .font(.system(size: 17, weight: .semibold)) .foregroundColor(textColor) if isMissing { Text("No mood recorded") .font(.system(size: 14)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 15, weight: .medium)) .foregroundColor(moodColor) } } .padding(.horizontal, 12) .padding(.vertical, 8) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 10)) Spacer() Text(entry.forDate, format: .dateTime.month(.abbreviated)) .font(.system(size: 12, weight: .medium)) .foregroundColor(textColor.opacity(0.6)) .padding(.horizontal, 10) .padding(.vertical, 6) .background(.ultraThinMaterial, in: Capsule()) } .padding(16) .frame(height: 86) .background { // Repeating mood icon pattern background GeometryReader { geo in let iconSize: CGFloat = 20 let spacing: CGFloat = 28 let cols = Int(geo.size.width / spacing) + 2 let rows = Int(geo.size.height / spacing) + 2 ZStack { // Base background color RoundedRectangle(cornerRadius: 16) .fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5)) // Pattern overlay ForEach(0..