|
@@ -7,6 +7,7 @@ struct AppTileView: View {
|
|
|
|
|
|
|
|
@State private var isHovering = false
|
|
@State private var isHovering = false
|
|
|
@State private var isPressing = false
|
|
@State private var isPressing = false
|
|
|
|
|
+ private let tileCornerRadius: CGFloat = 14
|
|
|
|
|
|
|
|
var body: some View {
|
|
var body: some View {
|
|
|
Button(action: onTap) {
|
|
Button(action: onTap) {
|
|
@@ -32,33 +33,57 @@ struct AppTileView: View {
|
|
|
.padding(.horizontal, 7)
|
|
.padding(.horizontal, 7)
|
|
|
.padding(.vertical, 10)
|
|
.padding(.vertical, 10)
|
|
|
.background(
|
|
.background(
|
|
|
- RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
|
|
|
|
|
+ RoundedRectangle(cornerRadius: tileCornerRadius, style: .continuous)
|
|
|
.fill(
|
|
.fill(
|
|
|
LinearGradient(
|
|
LinearGradient(
|
|
|
colors: isHovering
|
|
colors: isHovering
|
|
|
- ? [Color.white.opacity(0.14), Color.white.opacity(0.06)]
|
|
|
|
|
- : [Color.clear, Color.clear],
|
|
|
|
|
|
|
+ ? [Color.white.opacity(0.16), Color.white.opacity(0.06)]
|
|
|
|
|
+ : [Color.white.opacity(0.03), Color.white.opacity(0.015)],
|
|
|
startPoint: .topLeading,
|
|
startPoint: .topLeading,
|
|
|
endPoint: .bottomTrailing
|
|
endPoint: .bottomTrailing
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
.overlay(
|
|
.overlay(
|
|
|
- RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
|
|
|
|
|
+ // Soft spotlight makes hover feel richer without harsh contrast.
|
|
|
|
|
+ RoundedRectangle(cornerRadius: tileCornerRadius, style: .continuous)
|
|
|
|
|
+ .fill(
|
|
|
|
|
+ RadialGradient(
|
|
|
|
|
+ colors: isHovering
|
|
|
|
|
+ ? [Color.white.opacity(0.16), Color.clear]
|
|
|
|
|
+ : [Color.clear, Color.clear],
|
|
|
|
|
+ center: .top,
|
|
|
|
|
+ startRadius: 0,
|
|
|
|
|
+ endRadius: 80
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .allowsHitTesting(false)
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: tileCornerRadius, style: .continuous)
|
|
|
|
|
+ .stroke(
|
|
|
|
|
+ isHovering ? Color.white.opacity(0.3) : Color.white.opacity(0.06),
|
|
|
|
|
+ lineWidth: isHovering ? 1.3 : 1
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .overlay(
|
|
|
|
|
+ RoundedRectangle(cornerRadius: tileCornerRadius, style: .continuous)
|
|
|
|
|
+ .inset(by: 1)
|
|
|
.stroke(
|
|
.stroke(
|
|
|
- isHovering ? Color.white.opacity(0.24) : Color.clear,
|
|
|
|
|
- lineWidth: isHovering ? 1.2 : 1
|
|
|
|
|
|
|
+ isHovering ? Color.white.opacity(0.12) : Color.clear,
|
|
|
|
|
+ lineWidth: 1
|
|
|
)
|
|
)
|
|
|
|
|
+ .allowsHitTesting(false)
|
|
|
)
|
|
)
|
|
|
- .scaleEffect(isPressing ? 0.98 : (isHovering ? 1.025 : 1.0))
|
|
|
|
|
|
|
+ .scaleEffect(isPressing ? 0.985 : (isHovering ? 1.02 : 1.0))
|
|
|
.offset(y: isPressing ? 0 : (isHovering ? -2 : 0))
|
|
.offset(y: isPressing ? 0 : (isHovering ? -2 : 0))
|
|
|
.shadow(
|
|
.shadow(
|
|
|
color: .black.opacity(isHovering ? 0.22 : 0),
|
|
color: .black.opacity(isHovering ? 0.22 : 0),
|
|
|
- radius: isHovering ? 16 : 0,
|
|
|
|
|
|
|
+ radius: isHovering ? 18 : 0,
|
|
|
x: 0,
|
|
x: 0,
|
|
|
- y: isHovering ? 10 : 0
|
|
|
|
|
|
|
+ y: isHovering ? 12 : 0
|
|
|
)
|
|
)
|
|
|
- .contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|
|
|
|
|
|
+ .contentShape(RoundedRectangle(cornerRadius: tileCornerRadius, style: .continuous))
|
|
|
.animation(.spring(response: 0.24, dampingFraction: 0.88), value: isHovering)
|
|
.animation(.spring(response: 0.24, dampingFraction: 0.88), value: isHovering)
|
|
|
.animation(.easeOut(duration: 0.12), value: isPressing)
|
|
.animation(.easeOut(duration: 0.12), value: isPressing)
|
|
|
}
|
|
}
|