diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 83d8af4..754a04c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,8 @@ "WebSearch", "WebFetch(domain:swiftwithmajid.com)", "WebFetch(domain:azamsharp.com)", - "WebFetch(domain:www.createwithswift.com)" + "WebFetch(domain:www.createwithswift.com)", + "Skill(frontend-design:frontend-design)" ] } } diff --git a/Shared/Models/UserDefaultsStore.swift b/Shared/Models/UserDefaultsStore.swift index 242bcb1..fae2725 100644 --- a/Shared/Models/UserDefaultsStore.swift +++ b/Shared/Models/UserDefaultsStore.swift @@ -12,6 +12,7 @@ enum VotingLayoutStyle: Int, CaseIterable { case cards = 1 // Larger tappable cards with labels case radial = 2 // Semi-circle/wheel arrangement case stacked = 3 // Full-width vertical list + case aura = 4 // Atmospheric glowing orbs with flowing layout var displayName: String { switch self { @@ -19,6 +20,7 @@ enum VotingLayoutStyle: Int, CaseIterable { case .cards: return "Cards" case .radial: return "Radial" case .stacked: return "Stacked" + case .aura: return "Aura" } } } @@ -29,6 +31,18 @@ enum DayViewStyle: Int, CaseIterable { case compact = 2 // Dense timeline view case bubble = 3 // Colorful full-width bubbles case grid = 4 // 3 entries per row grid + case aura = 5 // Atmospheric glowing entries with giant typography + case chronicle = 6 // Editorial magazine with dramatic serif typography + case neon = 7 // Cyberpunk synthwave with glowing edges + case ink = 8 // Japanese zen calligraphy with brush strokes + case prism = 9 // Premium glassmorphism with light refraction + case tape = 10 // Retro cassette mixtape aesthetic + case morph = 11 // Liquid organic blob shapes + case stack = 12 // Layered paper notes with depth + case wave = 13 // Horizontal gradient river bands + case pattern = 14 // Mood icons as repeating background pattern + case leather = 15 // Skeuomorphic leather with stitching + case glass = 16 // iOS 26 liquid glass with variable blur var displayName: String { switch self { @@ -37,6 +51,18 @@ enum DayViewStyle: Int, CaseIterable { case .compact: return "Compact" case .bubble: return "Bubble" case .grid: return "Grid" + case .aura: return "Aura" + case .chronicle: return "Chronicle" + case .neon: return "Neon" + case .ink: return "Ink" + case .prism: return "Prism" + case .tape: return "Tape" + case .morph: return "Morph" + case .stack: return "Stack" + case .wave: return "Wave" + case .pattern: return "Pattern" + case .leather: return "Leather" + case .glass: return "Glass" } } diff --git a/Shared/Views/AddMoodHeaderView.swift b/Shared/Views/AddMoodHeaderView.swift index ca3885f..8dfa34f 100644 --- a/Shared/Views/AddMoodHeaderView.swift +++ b/Shared/Views/AddMoodHeaderView.swift @@ -57,6 +57,8 @@ struct AddMoodHeaderView: View { RadialVotingView(moodTint: moodTint, onMoodSelected: addItem) case .stacked: StackedVotingView(moodTint: moodTint, onMoodSelected: addItem) + case .aura: + AuraVotingView(moodTint: moodTint, onMoodSelected: addItem) } } @@ -231,6 +233,106 @@ struct StackedVotingView: View { } } +// MARK: - Layout 5: Aura (Atmospheric glowing orbs) +struct AuraVotingView: View { + let moodTint: MoodTints + let onMoodSelected: (Mood) -> Void + @Environment(\.colorScheme) private var colorScheme + + var body: some View { + VStack(spacing: 24) { + // Top row: 3 moods (Horrible, Bad, Average) + HStack(spacing: 16) { + ForEach(Array(Mood.allValues.prefix(3))) { mood in + auraButton(for: mood) + } + } + + // Bottom row: 2 moods (Good, Great) - centered + HStack(spacing: 24) { + ForEach(Array(Mood.allValues.suffix(2))) { mood in + auraButton(for: mood) + } + } + } + .padding(.vertical, 8) + } + + private func auraButton(for mood: Mood) -> some View { + let color = moodTint.color(forMood: mood) + + return Button(action: { onMoodSelected(mood) }) { + VStack(spacing: 10) { + // Glowing orb + ZStack { + // Outer atmospheric glow + Circle() + .fill( + RadialGradient( + colors: [ + color.opacity(0.5), + color.opacity(0.2), + Color.clear + ], + center: .center, + startRadius: 0, + endRadius: 45 + ) + ) + .frame(width: 90, height: 90) + + // Middle glow ring + Circle() + .fill( + RadialGradient( + colors: [ + color.opacity(0.8), + color.opacity(0.4) + ], + center: .center, + startRadius: 10, + endRadius: 30 + ) + ) + .frame(width: 60, height: 60) + + // Inner solid core + Circle() + .fill(color) + .frame(width: 48, height: 48) + .shadow(color: color.opacity(0.8), radius: 12, x: 0, y: 0) + + // Icon + mood.icon + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 26, height: 26) + .foregroundColor(.white) + } + + // Label with elegant typography + Text(mood.strValue) + .font(.system(size: 12, weight: .semibold, design: .rounded)) + .foregroundColor(color) + .tracking(0.5) + } + } + .buttonStyle(AuraButtonStyle(color: color)) + } +} + +// Custom button style for aura with glow effect on press +struct AuraButtonStyle: ButtonStyle { + let color: Color + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .scaleEffect(configuration.isPressed ? 0.92 : 1.0) + .brightness(configuration.isPressed ? 0.1 : 0) + .animation(.easeInOut(duration: 0.15), value: configuration.isPressed) + } +} + // MARK: - Button Styles struct MoodButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { diff --git a/Shared/Views/CustomizeView/CustomizeView.swift b/Shared/Views/CustomizeView/CustomizeView.swift index 1062974..45e3115 100644 --- a/Shared/Views/CustomizeView/CustomizeView.swift +++ b/Shared/Views/CustomizeView/CustomizeView.swift @@ -476,34 +476,37 @@ struct VotingLayoutPickerCompact: View { } var body: some View { - HStack(spacing: 10) { - ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in - Button(action: { - withAnimation(.easeInOut(duration: 0.2)) { - votingLayoutStyle = layout.rawValue - } - EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName]) - }) { - VStack(spacing: 6) { - layoutIcon(for: layout) - .frame(width: 44, height: 44) - .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.4)) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in + Button(action: { + withAnimation(.easeInOut(duration: 0.2)) { + votingLayoutStyle = layout.rawValue + } + EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName]) + }) { + VStack(spacing: 6) { + layoutIcon(for: layout) + .frame(width: 44, height: 44) + .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.4)) - Text(layout.displayName) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.5)) + Text(layout.displayName) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.5)) + } + .frame(width: 70) + .padding(.vertical, 12) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(currentLayout == layout + ? Color.accentColor.opacity(0.1) + : (colorScheme == .dark ? Color(.systemGray5) : Color(.systemGray6))) + ) } - .frame(maxWidth: .infinity) - .padding(.vertical, 12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(currentLayout == layout - ? Color.accentColor.opacity(0.1) - : (colorScheme == .dark ? Color(.systemGray5) : Color(.systemGray6))) - ) + .buttonStyle(.plain) } - .buttonStyle(.plain) } + .padding(.horizontal, 4) } } @@ -530,6 +533,34 @@ struct VotingLayoutPickerCompact: View { VStack(spacing: 4) { ForEach(0..<4, id: \.self) { _ in RoundedRectangle(cornerRadius: 2).frame(width: 32, height: 7) } } + case .aura: + // Glowing orbs in 2 rows + VStack(spacing: 4) { + HStack(spacing: 6) { + ForEach(0..<3, id: \.self) { _ in + ZStack { + Circle() + .fill(RadialGradient(colors: [.green.opacity(0.5), .clear], center: .center, startRadius: 0, endRadius: 8)) + .frame(width: 14, height: 14) + Circle() + .fill(.green) + .frame(width: 8, height: 8) + } + } + } + HStack(spacing: 10) { + ForEach(0..<2, id: \.self) { _ in + ZStack { + Circle() + .fill(RadialGradient(colors: [.green.opacity(0.5), .clear], center: .center, startRadius: 0, endRadius: 8)) + .frame(width: 14, height: 14) + Circle() + .fill(.green) + .frame(width: 8, height: 8) + } + } + } + } } } @@ -830,36 +861,39 @@ struct DayViewStylePickerCompact: View { @Environment(\.colorScheme) private var colorScheme var body: some View { - HStack(spacing: 10) { - ForEach(DayViewStyle.allCases, id: \.rawValue) { style in - Button(action: { - withAnimation(.easeInOut(duration: 0.2)) { - dayViewStyle = style - } - let impactMed = UIImpactFeedbackGenerator(style: .medium) - impactMed.impactOccurred() - EventLogger.log(event: "change_day_view_style", withData: ["style": style.displayName]) - }) { - VStack(spacing: 6) { - styleIcon(for: style) - .frame(width: 44, height: 44) - .foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.4)) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + ForEach(DayViewStyle.allCases, id: \.rawValue) { style in + Button(action: { + withAnimation(.easeInOut(duration: 0.2)) { + dayViewStyle = style + } + let impactMed = UIImpactFeedbackGenerator(style: .medium) + impactMed.impactOccurred() + EventLogger.log(event: "change_day_view_style", withData: ["style": style.displayName]) + }) { + VStack(spacing: 6) { + styleIcon(for: style) + .frame(width: 44, height: 44) + .foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.4)) - Text(style.displayName) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.5)) + Text(style.displayName) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(dayViewStyle == style ? .accentColor : textColor.opacity(0.5)) + } + .frame(width: 70) + .padding(.vertical, 12) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(dayViewStyle == style + ? Color.accentColor.opacity(0.1) + : (colorScheme == .dark ? Color(.systemGray5) : Color(.systemGray6))) + ) } - .frame(maxWidth: .infinity) - .padding(.vertical, 12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(dayViewStyle == style - ? Color.accentColor.opacity(0.1) - : (colorScheme == .dark ? Color(.systemGray5) : Color(.systemGray6))) - ) + .buttonStyle(.plain) } - .buttonStyle(.plain) } + .padding(.horizontal, 4) } } @@ -919,6 +953,201 @@ struct DayViewStylePickerCompact: View { Circle().fill(.green).frame(width: 10, height: 10) Circle().fill(.yellow).frame(width: 10, height: 10) } + case .aura: + // Giant number with glowing orb + HStack(spacing: 4) { + Text("17") + .font(.system(size: 20, weight: .black, design: .rounded)) + .foregroundStyle( + LinearGradient(colors: [.green, .green.opacity(0.5)], startPoint: .top, endPoint: .bottom) + ) + ZStack { + Circle() + .fill( + RadialGradient(colors: [.green.opacity(0.6), .clear], center: .center, startRadius: 0, endRadius: 12) + ) + .frame(width: 24, height: 24) + Circle() + .fill(.green) + .frame(width: 12, height: 12) + } + } + case .chronicle: + // Editorial magazine style + VStack(alignment: .leading, spacing: 2) { + Rectangle().frame(width: 34, height: 2) + HStack(spacing: 4) { + Text("12") + .font(.system(size: 18, weight: .regular, design: .serif)) + Rectangle().frame(width: 1, height: 20) + VStack(alignment: .leading, spacing: 1) { + RoundedRectangle(cornerRadius: 1).frame(width: 12, height: 3) + RoundedRectangle(cornerRadius: 1).frame(width: 8, height: 2).opacity(0.5) + } + } + } + case .neon: + // Cyberpunk neon style + ZStack { + RoundedRectangle(cornerRadius: 2) + .fill(Color.black) + .frame(width: 38, height: 28) + RoundedRectangle(cornerRadius: 4) + .stroke(Color.green, lineWidth: 1) + .frame(width: 16, height: 16) + .shadow(color: .green, radius: 4, x: 0, y: 0) + RoundedRectangle(cornerRadius: 2) + .stroke(Color.green.opacity(0.5), lineWidth: 0.5) + .frame(width: 38, height: 28) + } + case .ink: + // Japanese zen style + HStack(spacing: 6) { + ZStack { + Circle() + .trim(from: 0, to: 0.85) + .stroke(style: StrokeStyle(lineWidth: 2, lineCap: .round)) + .frame(width: 18, height: 18) + .rotationEffect(.degrees(20)) + } + VStack(alignment: .leading, spacing: 2) { + RoundedRectangle(cornerRadius: 1).frame(width: 14, height: 2).opacity(0.3) + RoundedRectangle(cornerRadius: 1).frame(width: 10, height: 2).opacity(0.6) + } + } + case .prism: + // Glassmorphism with rainbow edge + ZStack { + RoundedRectangle(cornerRadius: 6) + .fill( + AngularGradient(colors: [.red, .orange, .yellow, .green, .blue, .purple, .red], center: .center) + ) + .frame(width: 36, height: 26) + .blur(radius: 3) + .opacity(0.6) + RoundedRectangle(cornerRadius: 5) + .fill(.ultraThinMaterial) + .frame(width: 32, height: 22) + Circle() + .fill(.green.opacity(0.5)) + .frame(width: 10, height: 10) + .offset(x: -6) + } + case .tape: + // Cassette tape reels + HStack(spacing: 8) { + ZStack { + Circle().stroke(lineWidth: 2).frame(width: 14, height: 14) + Circle().frame(width: 6, height: 6) + } + VStack(spacing: 2) { + RoundedRectangle(cornerRadius: 1).frame(width: 16, height: 3) + RoundedRectangle(cornerRadius: 1).frame(width: 16, height: 2).opacity(0.5) + } + ZStack { + Circle().stroke(lineWidth: 2).frame(width: 14, height: 14) + Circle().frame(width: 6, height: 6) + } + } + case .morph: + // Organic blob shapes + ZStack { + Ellipse() + .fill(.green.opacity(0.4)) + .frame(width: 28, height: 22) + .blur(radius: 4) + Ellipse() + .fill(.green.opacity(0.6)) + .frame(width: 18, height: 14) + .offset(x: 4, y: 2) + .blur(radius: 2) + Circle() + .fill(.green) + .frame(width: 12, height: 12) + } + case .stack: + // Layered paper notes + ZStack { + RoundedRectangle(cornerRadius: 3) + .frame(width: 28, height: 22) + .opacity(0.3) + .offset(x: 3, y: 3) + RoundedRectangle(cornerRadius: 3) + .frame(width: 28, height: 22) + .opacity(0.5) + .offset(x: 1.5, y: 1.5) + RoundedRectangle(cornerRadius: 3) + .frame(width: 28, height: 22) + VStack(spacing: 3) { + Rectangle().frame(width: 18, height: 2) + Rectangle().frame(width: 14, height: 2).opacity(0.5) + } + } + case .wave: + // Horizontal gradient wave + VStack(spacing: 3) { + Capsule().fill(.green).frame(width: 34, height: 8) + Capsule().fill(.green.opacity(0.6)).frame(width: 34, height: 8) + Capsule().fill(.green.opacity(0.3)).frame(width: 34, height: 8) + } + case .pattern: + // Repeating pattern of icons + ZStack { + VStack(spacing: 6) { + HStack(spacing: 8) { + Circle().frame(width: 6, height: 6).opacity(0.2) + Circle().frame(width: 6, height: 6).opacity(0.2) + Circle().frame(width: 6, height: 6).opacity(0.2) + } + HStack(spacing: 8) { + Circle().frame(width: 6, height: 6).opacity(0.2) + Circle().frame(width: 6, height: 6).opacity(0.2) + Circle().frame(width: 6, height: 6).opacity(0.2) + } + } + RoundedRectangle(cornerRadius: 4) + .fill(.green.opacity(0.3)) + .frame(width: 28, height: 18) + Circle() + .fill(.green) + .frame(width: 12, height: 12) + .offset(x: -6) + } + case .leather: + // Skeuomorphic leather + ZStack { + RoundedRectangle(cornerRadius: 4) + .fill(Color(red: 0.4, green: 0.28, blue: 0.18)) + .frame(width: 36, height: 26) + RoundedRectangle(cornerRadius: 3) + .strokeBorder(style: StrokeStyle(lineWidth: 1, dash: [2, 2])) + .foregroundColor(Color(red: 0.6, green: 0.5, blue: 0.35)) + .frame(width: 30, height: 20) + Circle() + .fill(Color(red: 0.8, green: 0.7, blue: 0.5)) + .frame(width: 10, height: 10) + } + case .glass: + // Liquid glass effect + ZStack { + RoundedRectangle(cornerRadius: 8) + .fill(.ultraThinMaterial) + .frame(width: 36, height: 26) + RoundedRectangle(cornerRadius: 8) + .fill( + LinearGradient( + colors: [.white.opacity(0.5), .white.opacity(0.1)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .frame(width: 36, height: 26) + Circle() + .fill(.green.opacity(0.5)) + .frame(width: 12, height: 12) + .offset(x: -6) + .blur(radius: 2) + } } } } diff --git a/Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift b/Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift index a69820c..e930f46 100644 --- a/Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift +++ b/Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift @@ -27,38 +27,40 @@ struct VotingLayoutPickerView: View { .padding(.horizontal) .padding(.top) - HStack(spacing: 8) { - ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in - Button(action: { - withAnimation(.easeInOut(duration: 0.2)) { - votingLayoutStyle = layout.rawValue - } - EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName]) - }) { - VStack(spacing: 6) { - layoutIcon(for: layout) - .frame(width: 44, height: 44) - .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.6)) + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in + Button(action: { + withAnimation(.easeInOut(duration: 0.2)) { + votingLayoutStyle = layout.rawValue + } + EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName]) + }) { + VStack(spacing: 6) { + layoutIcon(for: layout) + .frame(width: 44, height: 44) + .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.6)) - Text(layout.displayName) - .font(.caption) - .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.8)) + Text(layout.displayName) + .font(.caption) + .foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.8)) + } + .frame(width: 70) + .padding(.vertical, 12) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(currentLayout == layout ? Color.accentColor.opacity(0.15) : Color.clear) + ) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(currentLayout == layout ? Color.accentColor : Color.clear, lineWidth: 2) + ) } - .frame(maxWidth: .infinity) - .padding(.vertical, 12) - .background( - RoundedRectangle(cornerRadius: 10) - .fill(currentLayout == layout ? Color.accentColor.opacity(0.15) : Color.clear) - ) - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke(currentLayout == layout ? Color.accentColor : Color.clear, lineWidth: 2) - ) + .buttonStyle(.plain) } - .buttonStyle(.plain) } + .padding(.horizontal) } - .padding(.horizontal) .padding(.bottom) } } @@ -98,6 +100,34 @@ struct VotingLayoutPickerView: View { .frame(width: 32, height: 6) } } + case .aura: + // Glowing orbs in 2 rows + VStack(spacing: 4) { + HStack(spacing: 6) { + ForEach(0..<3, id: \.self) { _ in + ZStack { + Circle() + .fill(RadialGradient(colors: [.green.opacity(0.5), .clear], center: .center, startRadius: 0, endRadius: 8)) + .frame(width: 14, height: 14) + Circle() + .fill(.green) + .frame(width: 8, height: 8) + } + } + } + HStack(spacing: 10) { + ForEach(0..<2, id: \.self) { _ in + ZStack { + Circle() + .fill(RadialGradient(colors: [.green.opacity(0.5), .clear], center: .center, startRadius: 0, endRadius: 8)) + .frame(width: 14, height: 14) + Circle() + .fill(.green) + .frame(width: 8, height: 8) + } + } + } + } } } diff --git a/Shared/Views/DayView/DayView.swift b/Shared/Views/DayView/DayView.swift index 6861ba2..805da21 100644 --- a/Shared/Views/DayView/DayView.swift +++ b/Shared/Views/DayView/DayView.swift @@ -122,7 +122,7 @@ struct DayView: View { ForEach(months.sorted(by: { $0.key > $1.key }), id: \.key) { month, entries in - Section(header: SectionHeaderView(month: month, year: year)) { + Section(header: SectionHeaderView(month: month, year: year, entries: entries)) { monthListView(month: month, year: year, entries: entries) } } @@ -139,7 +139,40 @@ struct DayView: View { // view that make up the list body extension DayView { - private func SectionHeaderView(month: Int, year: Int) -> some View { + private func SectionHeaderView(month: Int, year: Int, entries: [MoodEntryModel]) -> some View { + Group { + switch dayViewStyle { + case .aura: + auraSectionHeader(month: month, year: year) + case .chronicle: + chronicleSectionHeader(month: month, year: year) + case .neon: + neonSectionHeader(month: month, year: year) + case .ink: + inkSectionHeader(month: month, year: year) + case .prism: + prismSectionHeader(month: month, year: year) + case .tape: + tapeSectionHeader(month: month, year: year) + case .morph: + morphSectionHeader(month: month, year: year) + case .stack: + stackSectionHeader(month: month, year: year) + case .wave: + waveSectionHeader(month: month, year: year, entries: entries) + case .pattern: + patternSectionHeader(month: month, year: year) + case .leather: + leatherSectionHeader(month: month, year: year) + case .glass: + glassSectionHeader(month: month, year: year) + default: + defaultSectionHeader(month: month, year: year) + } + } + } + + private func defaultSectionHeader(month: Int, year: Int) -> some View { HStack(spacing: 10) { // Calendar icon Image(systemName: "calendar") @@ -157,6 +190,600 @@ extension DayView { .background(.ultraThinMaterial) } + private func auraSectionHeader(month: Int, year: Int) -> some View { + HStack(spacing: 0) { + // Large month number as hero element + Text(String(format: "%02d", month)) + .font(.system(size: 48, weight: .black, design: .rounded)) + .foregroundStyle( + LinearGradient( + colors: [textColor, textColor.opacity(0.4)], + startPoint: .top, + endPoint: .bottom + ) + ) + .frame(width: 80) + + VStack(alignment: .leading, spacing: 2) { + Text(Random.monthName(fromMonthInt: month).uppercased()) + .font(.system(size: 14, weight: .bold, design: .rounded)) + .tracking(3) + .foregroundColor(textColor) + + Text(String(year)) + .font(.system(size: 12, weight: .medium, design: .rounded)) + .foregroundColor(textColor.opacity(0.5)) + } + + Spacer() + + // Decorative element + Circle() + .fill(textColor.opacity(0.1)) + .frame(width: 8, height: 8) + } + .padding(.horizontal, 20) + .padding(.vertical, 20) + .background( + ZStack { + // Base material + Rectangle() + .fill(.ultraThinMaterial) + + // Subtle gradient accent + LinearGradient( + colors: [ + textColor.opacity(0.03), + Color.clear + ], + startPoint: .leading, + endPoint: .trailing + ) + } + ) + } + + private func chronicleSectionHeader(month: Int, year: Int) -> some View { + VStack(alignment: .leading, spacing: 0) { + // Thick editorial rule + Rectangle() + .fill(textColor) + .frame(height: 4) + + HStack(alignment: .firstTextBaseline, spacing: 12) { + // Large serif month name + Text(Random.monthName(fromMonthInt: month).uppercased()) + .font(.system(size: 28, weight: .regular, design: .serif)) + .foregroundColor(textColor) + + // Year in lighter weight + Text(String(year)) + .font(.system(size: 16, weight: .light, design: .serif)) + .italic() + .foregroundColor(textColor.opacity(0.5)) + + Spacer() + + // Decorative flourish + Text("§") + .font(.system(size: 20, weight: .regular, design: .serif)) + .foregroundColor(textColor.opacity(0.3)) + } + .padding(.horizontal, 16) + .padding(.vertical, 16) + + // Thin bottom rule + Rectangle() + .fill(textColor.opacity(0.2)) + .frame(height: 1) + } + .background(.ultraThinMaterial) + } + + private func neonSectionHeader(month: Int, year: Int) -> some View { + HStack(spacing: 12) { + // Glowing terminal prompt + Text(">") + .font(.system(size: 18, weight: .bold, design: .monospaced)) + .foregroundColor(Color(red: 0.4, green: 1.0, blue: 0.4)) + .shadow(color: Color(red: 0.4, green: 1.0, blue: 0.4).opacity(0.8), radius: 4, x: 0, y: 0) + + Text("\(Random.monthName(fromMonthInt: month).uppercased())_\(String(year))") + .font(.system(size: 16, weight: .bold, design: .monospaced)) + .foregroundColor(.white) + .shadow(color: .white.opacity(0.3), radius: 2, x: 0, y: 0) + + Spacer() + + // Blinking cursor effect + Rectangle() + .fill(Color(red: 0.4, green: 1.0, blue: 0.4)) + .frame(width: 10, height: 18) + .shadow(color: Color(red: 0.4, green: 1.0, blue: 0.4), radius: 4, x: 0, y: 0) + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .background( + ZStack { + Color.black + + // Scanlines + VStack(spacing: 3) { + ForEach(0..<8, id: \.self) { _ in + Rectangle() + .fill(Color.white.opacity(0.02)) + .frame(height: 1) + Spacer().frame(height: 3) + } + } + } + ) + } + + private func inkSectionHeader(month: Int, year: Int) -> some View { + HStack(alignment: .center, spacing: 16) { + // Brush stroke accent + Capsule() + .fill(textColor.opacity(0.15)) + .frame(width: 40, height: 3) + + VStack(alignment: .leading, spacing: 2) { + Text(Random.monthName(fromMonthInt: month)) + .font(.system(size: 18, weight: .thin)) + .tracking(4) + .foregroundColor(textColor) + + Text(String(year)) + .font(.system(size: 11, weight: .ultraLight)) + .foregroundColor(textColor.opacity(0.4)) + } + + Spacer() + + // Zen circle ornament + Circle() + .trim(from: 0, to: 0.7) + .stroke(textColor.opacity(0.2), style: StrokeStyle(lineWidth: 2, lineCap: .round)) + .frame(width: 20, height: 20) + .rotationEffect(.degrees(-60)) + } + .padding(.horizontal, 24) + .padding(.vertical, 20) + .background( + Rectangle() + .fill(.ultraThinMaterial) + ) + } + + private func prismSectionHeader(month: Int, year: Int) -> some View { + ZStack { + // Rainbow edge glow + RoundedRectangle(cornerRadius: 16) + .fill( + AngularGradient( + colors: [.red, .orange, .yellow, .green, .blue, .purple, .red], + center: .leading + ) + ) + .blur(radius: 8) + .opacity(0.3) + + // Glass content + HStack(spacing: 12) { + Text(Random.monthName(fromMonthInt: month)) + .font(.system(size: 20, weight: .semibold)) + .foregroundColor(textColor) + + Capsule() + .fill(textColor.opacity(0.2)) + .frame(width: 4, height: 4) + + Text(String(year)) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(textColor.opacity(0.6)) + + Spacer() + } + .padding(.horizontal, 20) + .padding(.vertical, 16) + .background(.ultraThinMaterial) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + } + + private func tapeSectionHeader(month: Int, year: Int) -> some View { + HStack(spacing: 12) { + // Tape reel icon + ZStack { + Circle() + .stroke(textColor.opacity(0.3), lineWidth: 2) + .frame(width: 24, height: 24) + Circle() + .fill(textColor.opacity(0.2)) + .frame(width: 10, height: 10) + } + + VStack(alignment: .leading, spacing: 2) { + Text("SIDE A") + .font(.system(size: 10, weight: .bold, design: .monospaced)) + .foregroundColor(textColor.opacity(0.4)) + + Text("\(Random.monthName(fromMonthInt: month).uppercased()) '\(String(year).suffix(2))") + .font(.system(size: 16, weight: .black, design: .rounded)) + .foregroundColor(textColor) + .tracking(1) + } + + Spacer() + + // Track counter + Text(String(format: "%02d", month)) + .font(.system(size: 20, weight: .bold, design: .monospaced)) + .foregroundColor(textColor.opacity(0.3)) + } + .padding(.horizontal, 16) + .padding(.vertical, 14) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(.ultraThinMaterial) + ) + } + + private func morphSectionHeader(month: Int, year: Int) -> some View { + ZStack { + // Organic blob background + HStack { + Ellipse() + .fill(textColor.opacity(0.08)) + .frame(width: 120, height: 60) + .blur(radius: 15) + .offset(x: -20) + Spacer() + } + + HStack(spacing: 16) { + Text(Random.monthName(fromMonthInt: month)) + .font(.system(size: 22, weight: .light)) + .foregroundColor(textColor) + + Text(String(year)) + .font(.system(size: 14, weight: .regular)) + .foregroundColor(textColor.opacity(0.4)) + + Spacer() + + // Blob indicator + Circle() + .fill(textColor.opacity(0.15)) + .frame(width: 12, height: 12) + .blur(radius: 2) + } + .padding(.horizontal, 24) + .padding(.vertical, 18) + } + .background(.ultraThinMaterial) + } + + private func stackSectionHeader(month: Int, year: Int) -> some View { + VStack(alignment: .leading, spacing: 0) { + // Torn edge + HStack(spacing: 0) { + ForEach(0..<30, id: \.self) { _ in + Rectangle() + .fill(textColor.opacity(0.2)) + .frame(height: CGFloat.random(in: 2...4)) + } + } + .frame(height: 4) + + HStack(spacing: 12) { + // Red margin line + Rectangle() + .fill(Color.red.opacity(0.3)) + .frame(width: 2) + + Text(Random.monthName(fromMonthInt: month)) + .font(.system(size: 18, weight: .regular, design: .serif)) + .foregroundColor(textColor) + + Text(String(year)) + .font(.system(size: 14, weight: .light, design: .serif)) + .italic() + .foregroundColor(textColor.opacity(0.5)) + + Spacer() + } + .padding(.horizontal, 16) + .padding(.vertical, 14) + } + .background(.ultraThinMaterial) + } + + private func waveSectionHeader(month: Int, year: Int, entries: [MoodEntryModel]) -> some View { + // Calculate average mood (excluding missing entries) + let validEntries = entries.filter { $0.moodValue != Mood.missing.rawValue && $0.moodValue <= 4 } + let averageMood: Double = validEntries.isEmpty ? 0 : Double(validEntries.map { $0.moodValue }.reduce(0, +)) / Double(validEntries.count) + let hasData = !validEntries.isEmpty + + // Map average to a mood for coloring (0-4 scale) + let moodForColor: Mood = { + if !hasData { return .missing } + switch Int(round(averageMood)) { + case 0: return .horrible + case 1: return .bad + case 2: return .average + case 3: return .good + default: return .great + } + }() + + let barColor = hasData ? moodTint.color(forMood: moodForColor) : textColor.opacity(0.2) + // Width percentage based on average (0=20%, 4=100%) + let widthPercent = hasData ? 0.2 + (averageMood / 4.0) * 0.8 : 0.2 + + return HStack(spacing: 0) { + // Month number + Text(String(format: "%02d", month)) + .font(.system(size: 32, weight: .thin)) + .foregroundColor(hasData ? barColor.opacity(0.6) : textColor.opacity(0.3)) + .frame(width: 50) + + // Gradient bar sized by average mood + GeometryReader { geo in + ZStack(alignment: .leading) { + // Background track + Capsule() + .fill(textColor.opacity(0.1)) + .frame(height: 8) + + // Colored bar based on average + Capsule() + .fill( + LinearGradient( + colors: [barColor, barColor.opacity(0.6)], + startPoint: .leading, + endPoint: .trailing + ) + ) + .frame(width: geo.size.width * widthPercent, height: 8) + .shadow(color: barColor.opacity(0.4), radius: 4, x: 0, y: 2) + } + } + .frame(height: 8) + .padding(.horizontal, 12) + + VStack(alignment: .trailing, spacing: 2) { + Text(Random.monthName(fromMonthInt: month)) + .font(.system(size: 14, weight: .medium)) + .foregroundColor(textColor) + + if hasData { + Text(String(format: "%.1f avg", averageMood + 1)) // Display as 1-5 + .font(.system(size: 10, weight: .semibold)) + .foregroundColor(barColor) + } else { + Text(String(year)) + .font(.system(size: 11, weight: .regular)) + .foregroundColor(textColor.opacity(0.4)) + } + } + } + .padding(.horizontal, 16) + .padding(.vertical, 16) + .background(.ultraThinMaterial) + } + + private func patternSectionHeader(month: Int, year: Int) -> some View { + ZStack { + // Subtle mood icon pattern background + GeometryReader { geo in + let iconSize: CGFloat = 12 + let spacing: CGFloat = 20 + let cols = Int(geo.size.width / spacing) + 1 + let rows = 3 + + Canvas { context, size in + for row in 0.. some View { + ZStack { + // Leather texture background + RoundedRectangle(cornerRadius: 4) + .fill( + LinearGradient( + colors: [ + Color(red: 0.45, green: 0.28, blue: 0.18), + Color(red: 0.38, green: 0.22, blue: 0.12), + Color(red: 0.42, green: 0.25, blue: 0.15) + ], + startPoint: .top, + endPoint: .bottom + ) + ) + + // Leather grain texture + Rectangle() + .fill( + EllipticalGradient( + colors: [.white.opacity(0.08), .clear], + center: .topLeading, + startRadiusFraction: 0, + endRadiusFraction: 0.5 + ) + ) + + HStack { + // Left stitching + VStack(spacing: 6) { + ForEach(0..<4, id: \.self) { _ in + Capsule() + .fill(Color(red: 0.7, green: 0.55, blue: 0.35)) + .frame(width: 6, height: 2) + } + } + .padding(.leading, 8) + + VStack(alignment: .leading, spacing: 2) { + Text(Random.monthName(fromMonthInt: month).uppercased()) + .font(.system(size: 16, weight: .bold, design: .serif)) + .foregroundColor(Color(red: 0.9, green: 0.85, blue: 0.75)) + .shadow(color: .black.opacity(0.3), radius: 1, x: 0, y: 1) + + Text(String(year)) + .font(.system(size: 12, weight: .medium, design: .serif)) + .foregroundColor(Color(red: 0.8, green: 0.7, blue: 0.55)) + } + .padding(.leading, 12) + + Spacer() + + // Metal rivet + ZStack { + Circle() + .fill( + RadialGradient( + colors: [ + Color(red: 0.85, green: 0.75, blue: 0.5), + Color(red: 0.55, green: 0.45, blue: 0.25) + ], + center: .topLeading, + startRadius: 0, + endRadius: 10 + ) + ) + .frame(width: 16, height: 16) + Circle() + .fill(Color(red: 0.4, green: 0.3, blue: 0.2)) + .frame(width: 6, height: 6) + } + .padding(.trailing, 16) + } + .padding(.vertical, 14) + } + .frame(height: 56) + .clipShape(RoundedRectangle(cornerRadius: 4)) + } + + private func glassSectionHeader(month: Int, year: Int) -> some View { + ZStack { + // Variable blur glass layers + RoundedRectangle(cornerRadius: 20) + .fill(.ultraThinMaterial) + + // Light refraction effect + GeometryReader { geo in + Ellipse() + .fill( + LinearGradient( + colors: [.white.opacity(0.4), .white.opacity(0)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .frame(width: geo.size.width * 0.6, height: 30) + .blur(radius: 10) + .offset(x: 20, y: 5) + } + + // Rainbow edge shimmer + RoundedRectangle(cornerRadius: 20) + .stroke( + AngularGradient( + colors: [ + .white.opacity(0.5), + .blue.opacity(0.2), + .purple.opacity(0.2), + .white.opacity(0.3), + .white.opacity(0.5) + ], + center: .center + ), + lineWidth: 1 + ) + .blur(radius: 0.5) + + HStack(spacing: 16) { + // Floating glass orb + ZStack { + Circle() + .fill(.ultraThinMaterial) + .frame(width: 36, height: 36) + + Circle() + .fill( + LinearGradient( + colors: [.white.opacity(0.6), .clear], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .frame(width: 34, height: 34) + .blur(radius: 4) + + Text(String(format: "%02d", month)) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(textColor) + } + + VStack(alignment: .leading, spacing: 2) { + Text(Random.monthName(fromMonthInt: month)) + .font(.system(size: 18, weight: .medium)) + .foregroundColor(textColor) + + Text(String(year)) + .font(.system(size: 12, weight: .regular)) + .foregroundColor(textColor.opacity(0.5)) + } + + Spacer() + + // Specular highlight dot + Circle() + .fill(.white.opacity(0.6)) + .frame(width: 6, height: 6) + .blur(radius: 1) + } + .padding(.horizontal, 20) + } + .frame(height: 64) + } + private var gridColumns: [GridItem] { [ GridItem(.flexible(), spacing: 10), diff --git a/Shared/Views/EntryListView.swift b/Shared/Views/EntryListView.swift index 85a76e2..9a18bd2 100644 --- a/Shared/Views/EntryListView.swift +++ b/Shared/Views/EntryListView.swift @@ -36,6 +36,30 @@ struct EntryListView: View { 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 } } @@ -319,6 +343,1311 @@ struct EntryListView: View { .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 { + ZStack { + // Repeating mood icon pattern background + GeometryReader { geo in + let iconSize: CGFloat = 24 + let spacing: CGFloat = 32 + let cols = Int(geo.size.width / spacing) + 2 + let rows = Int(geo.size.height / spacing) + 2 + + ForEach(0..