Add Neon/Synthwave style and 4 paywall themes
- Add 4 distinct paywall themes (Celestial, Garden, Neon, Minimal) with preview/switcher in debug settings - Add Neon voting layout with synthwave equalizer bar design - Upgrade Neon entry style with grid background, cyan/magenta gradients, scanline effects, and mini equalizer visualization - Add PaywallPreviewSettingsView for testing different paywall styles - Use consistent synthwave color palette (cyan #00FFD0, magenta #FF00CC) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -590,103 +590,205 @@ struct EntryListView: View {
|
||||
.background(colorScheme == .dark ? Color(.systemGray6) : .white)
|
||||
}
|
||||
|
||||
// MARK: - Neon Style (Cyberpunk/Synthwave)
|
||||
// MARK: - Neon Style (Synthwave Arcade)
|
||||
private var neonStyle: some View {
|
||||
ZStack {
|
||||
// Dark base with scanline effect
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(Color.black)
|
||||
let neonCyan = Color(red: 0.0, green: 1.0, blue: 0.82)
|
||||
let neonMagenta = Color(red: 1.0, green: 0.0, blue: 0.8)
|
||||
let deepBlack = Color(red: 0.02, green: 0.02, blue: 0.04)
|
||||
|
||||
// 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)
|
||||
// Map mood to synthwave color spectrum
|
||||
let synthwaveColor: Color = {
|
||||
switch entry.mood {
|
||||
case .great: return neonCyan
|
||||
case .good: return Color(red: 0.0, green: 0.9, blue: 0.6) // Cyan-green
|
||||
case .average: return Color(red: 0.9, green: 0.9, blue: 0.2) // Neon yellow
|
||||
case .bad: return Color(red: 1.0, green: 0.5, blue: 0.3) // Neon orange
|
||||
case .horrible: return neonMagenta
|
||||
default: return Color(red: 0.9, green: 0.9, blue: 0.2) // Fallback yellow
|
||||
}
|
||||
}()
|
||||
|
||||
return ZStack {
|
||||
// Deep black base
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(deepBlack)
|
||||
|
||||
// Grid background pattern
|
||||
Canvas { context, size in
|
||||
let gridSpacing: CGFloat = 12
|
||||
let gridColor = neonCyan.opacity(0.08)
|
||||
|
||||
// Horizontal grid lines
|
||||
for y in stride(from: 0, through: size.height, by: gridSpacing) {
|
||||
var path = Path()
|
||||
path.move(to: CGPoint(x: 0, y: y))
|
||||
path.addLine(to: CGPoint(x: size.width, y: y))
|
||||
context.stroke(path, with: .color(gridColor), lineWidth: 0.5)
|
||||
}
|
||||
|
||||
// Vertical grid lines
|
||||
for x in stride(from: 0, through: size.width, by: gridSpacing) {
|
||||
var path = Path()
|
||||
path.move(to: CGPoint(x: x, y: 0))
|
||||
path.addLine(to: CGPoint(x: x, y: size.height))
|
||||
context.stroke(path, with: .color(gridColor), lineWidth: 0.5)
|
||||
}
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
|
||||
// Scanline overlay for CRT effect
|
||||
VStack(spacing: 0) {
|
||||
ForEach(0..<30, id: \.self) { _ in
|
||||
Rectangle()
|
||||
.fill(Color.white.opacity(0.015))
|
||||
.frame(height: 1)
|
||||
Spacer().frame(height: 2)
|
||||
}
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
|
||||
// Content
|
||||
HStack(spacing: 16) {
|
||||
// Neon-outlined mood indicator
|
||||
// Neon equalizer-style mood indicator
|
||||
ZStack {
|
||||
// Glow effect
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(isMissing ? Color.gray : moodColor, lineWidth: 2)
|
||||
// Outer glow ring
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
colors: isMissing
|
||||
? [Color.gray.opacity(0.3)]
|
||||
: [neonCyan.opacity(0.6), neonMagenta.opacity(0.6)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 2
|
||||
)
|
||||
.blur(radius: 4)
|
||||
.opacity(0.8)
|
||||
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(isMissing ? Color.gray : moodColor, lineWidth: 2)
|
||||
// Inner border
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
colors: isMissing
|
||||
? [Color.gray.opacity(0.4)]
|
||||
: [neonCyan, neonMagenta],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1.5
|
||||
)
|
||||
|
||||
// Mood icon with synthwave glow
|
||||
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)
|
||||
.foregroundColor(isMissing ? .gray : synthwaveColor)
|
||||
.shadow(color: isMissing ? .clear : synthwaveColor.opacity(0.9), radius: 8, x: 0, y: 0)
|
||||
.shadow(color: isMissing ? .clear : synthwaveColor.opacity(0.5), radius: 16, x: 0, y: 0)
|
||||
.accessibilityLabel(entry.mood.strValue)
|
||||
}
|
||||
.frame(width: 52, height: 52)
|
||||
.frame(width: 54, height: 54)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
// Date in monospace terminal style
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
// Date in cyan monospace
|
||||
Text(entry.forDate, format: .dateTime.year().month(.twoDigits).day(.twoDigits))
|
||||
.font(.caption.weight(.medium).monospaced())
|
||||
.foregroundColor(Color(red: 0.4, green: 1.0, blue: 0.4)) // Terminal green
|
||||
.font(.system(.caption, design: .monospaced).weight(.semibold))
|
||||
.foregroundColor(neonCyan)
|
||||
.shadow(color: neonCyan.opacity(0.5), radius: 4, x: 0, y: 0)
|
||||
|
||||
if isMissing {
|
||||
Text("NO_DATA")
|
||||
.font(.headline.weight(.bold).monospaced())
|
||||
.foregroundColor(.gray)
|
||||
.font(.system(.headline, design: .monospaced).weight(.black))
|
||||
.foregroundColor(.gray.opacity(0.6))
|
||||
} else {
|
||||
// Mood in glowing text
|
||||
// Mood text with synthwave gradient glow
|
||||
Text(entry.moodString.uppercased())
|
||||
.font(.headline.weight(.black))
|
||||
.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)
|
||||
.font(.system(.headline, design: .monospaced).weight(.black))
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: [neonCyan, synthwaveColor, neonMagenta],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
.shadow(color: synthwaveColor.opacity(0.8), radius: 6, x: 0, y: 0)
|
||||
.shadow(color: neonMagenta.opacity(0.3), radius: 12, x: 0, y: 0)
|
||||
}
|
||||
|
||||
// Weekday
|
||||
// Weekday in magenta
|
||||
Text(entry.forDate, format: .dateTime.weekday(.wide))
|
||||
.font(.caption2.weight(.medium).monospaced())
|
||||
.foregroundColor(.white.opacity(0.4))
|
||||
.font(.system(.caption2, design: .monospaced).weight(.medium))
|
||||
.foregroundColor(neonMagenta.opacity(0.7))
|
||||
.textCase(.uppercase)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Chevron with glow
|
||||
// Equalizer bars indicator (mini visualization)
|
||||
if !isMissing {
|
||||
HStack(spacing: 2) {
|
||||
ForEach(0..<5, id: \.self) { index in
|
||||
let barHeight: CGFloat = {
|
||||
let moodIndex = Mood.allValues.firstIndex(of: entry.mood) ?? 2
|
||||
let heights: [[CGFloat]] = [
|
||||
[28, 22, 16, 10, 6], // Great
|
||||
[24, 28, 18, 12, 8], // Good
|
||||
[16, 20, 28, 20, 16], // Okay
|
||||
[8, 12, 18, 28, 24], // Bad
|
||||
[6, 10, 16, 22, 28] // Awful
|
||||
]
|
||||
return heights[moodIndex][index]
|
||||
}()
|
||||
|
||||
RoundedRectangle(cornerRadius: 1)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [neonCyan, neonMagenta],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
.frame(width: 3, height: barHeight)
|
||||
.shadow(color: neonCyan.opacity(0.5), radius: 2, x: 0, y: 0)
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
|
||||
// Chevron with gradient glow
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.subheadline.weight(.bold))
|
||||
.foregroundColor(isMissing ? .gray : moodColor)
|
||||
.shadow(color: isMissing ? .clear : moodColor, radius: 4, x: 0, y: 0)
|
||||
.foregroundStyle(
|
||||
isMissing
|
||||
? AnyShapeStyle(Color.gray.opacity(0.4))
|
||||
: AnyShapeStyle(LinearGradient(
|
||||
colors: [neonCyan, neonMagenta],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
))
|
||||
)
|
||||
.shadow(color: isMissing ? .clear : neonCyan.opacity(0.5), radius: 4, x: 0, y: 0)
|
||||
}
|
||||
.padding(.horizontal, 18)
|
||||
.padding(.vertical, 16)
|
||||
.padding(.vertical, 14)
|
||||
|
||||
// Neon border
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
// Cyan-to-magenta gradient border
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
colors: isMissing
|
||||
? [Color.gray.opacity(0.3), Color.gray.opacity(0.1)]
|
||||
: [moodColor.opacity(0.8), moodColor.opacity(0.3)],
|
||||
? [Color.gray.opacity(0.2), Color.gray.opacity(0.1)]
|
||||
: [neonCyan.opacity(0.7), neonMagenta.opacity(0.7)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
)
|
||||
}
|
||||
.shadow(
|
||||
color: isMissing ? .clear : moodColor.opacity(0.3),
|
||||
radius: 12,
|
||||
x: 0,
|
||||
y: 4
|
||||
)
|
||||
// Outer glow effect
|
||||
.shadow(color: isMissing ? .clear : neonCyan.opacity(0.2), radius: 12, x: 0, y: 2)
|
||||
.shadow(color: isMissing ? .clear : neonMagenta.opacity(0.15), radius: 20, x: 0, y: 4)
|
||||
}
|
||||
|
||||
// MARK: - Ink Style (Japanese Zen/Calligraphy)
|
||||
|
||||
Reference in New Issue
Block a user