diff --git a/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconFull.imageset/Contents.json b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconFull.imageset/Contents.json new file mode 100644 index 0000000..79e959b --- /dev/null +++ b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconFull.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "full.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconFull.imageset/full.svg b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconFull.imageset/full.svg new file mode 100644 index 0000000..1b46017 --- /dev/null +++ b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconFull.imageset/full.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconLower.imageset/Contents.json b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconLower.imageset/Contents.json new file mode 100644 index 0000000..05952b2 --- /dev/null +++ b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconLower.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "lower.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconLower.imageset/lower.svg b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconLower.imageset/lower.svg new file mode 100644 index 0000000..8a9c816 --- /dev/null +++ b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconLower.imageset/lower.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconUpper.imageset/Contents.json b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconUpper.imageset/Contents.json new file mode 100644 index 0000000..97c600c --- /dev/null +++ b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconUpper.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "upper.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconUpper.imageset/upper.svg b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconUpper.imageset/upper.svg new file mode 100644 index 0000000..4d37eee --- /dev/null +++ b/tabatago-swift/TabataGo/Resources/Assets.xcassets/ZoneIconUpper.imageset/upper.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tabatago-swift/TabataGo/Views/Components/ZoneHighlightIcon.swift b/tabatago-swift/TabataGo/Views/Components/ZoneHighlightIcon.swift index cec9253..0ef701f 100644 --- a/tabatago-swift/TabataGo/Views/Components/ZoneHighlightIcon.swift +++ b/tabatago-swift/TabataGo/Views/Components/ZoneHighlightIcon.swift @@ -1,131 +1,35 @@ import SwiftUI -struct BodySilhouetteShape: Shape { - func path(in rect: CGRect) -> Path { - let w = rect.width - let h = rect.height - let mx = w * 0.5 - var p = Path() - - let headCY = h * 0.09 - let headR = w * 0.12 - p.addEllipse(in: CGRect( - x: mx - headR, y: headCY - headR, - width: headR * 2, height: headR * 2 - )) - - let neckW = w * 0.06 - let neckTop = headCY + headR - let shoulderY = h * 0.20 - p.addRect(CGRect( - x: mx - neckW, y: neckTop, - width: neckW * 2, height: shoulderY - neckTop - )) - - let shoulderHW = w * 0.42 - let armOuterBotY = h * 0.44 - let armInnerBotY = h * 0.44 - let armOuterX = w * 0.06 - let armInnerX = w * 0.12 - - p.move(to: CGPoint(x: mx - neckW, y: shoulderY)) - p.addLine(to: CGPoint(x: mx - shoulderHW, y: shoulderY)) - p.addLine(to: CGPoint(x: mx - armOuterX, y: armOuterBotY)) - p.addLine(to: CGPoint(x: mx - armInnerX, y: armInnerBotY)) - p.closeSubpath() - - p.move(to: CGPoint(x: mx + neckW, y: shoulderY)) - p.addLine(to: CGPoint(x: mx + shoulderHW, y: shoulderY)) - p.addLine(to: CGPoint(x: mx + armOuterX, y: armOuterBotY)) - p.addLine(to: CGPoint(x: mx + armInnerX, y: armInnerBotY)) - p.closeSubpath() - - let torsoHW = w * 0.18 - let waistY = h * 0.46 - let waistHW = w * 0.15 - p.move(to: CGPoint(x: mx - torsoHW, y: shoulderY)) - p.addLine(to: CGPoint(x: mx + torsoHW, y: shoulderY)) - p.addLine(to: CGPoint(x: mx + waistHW, y: waistY)) - p.addLine(to: CGPoint(x: mx - waistHW, y: waistY)) - p.closeSubpath() - - let hipHW = w * 0.22 - let hipY = h * 0.52 - p.move(to: CGPoint(x: mx - waistHW, y: waistY)) - p.addLine(to: CGPoint(x: mx + waistHW, y: waistY)) - p.addLine(to: CGPoint(x: mx + hipHW, y: hipY)) - p.addLine(to: CGPoint(x: mx - hipHW, y: hipY)) - p.closeSubpath() - - let gap = w * 0.02 - let legBotY = h * 0.92 - let footH = h * 0.05 - let footExtra = w * 0.04 - - p.move(to: CGPoint(x: mx - hipHW, y: hipY)) - p.addLine(to: CGPoint(x: mx - gap, y: hipY)) - p.addLine(to: CGPoint(x: mx - gap, y: legBotY)) - p.addLine(to: CGPoint(x: mx - hipHW - footExtra, y: legBotY + footH)) - p.addLine(to: CGPoint(x: mx - hipHW, y: legBotY)) - p.closeSubpath() - - p.move(to: CGPoint(x: mx + gap, y: hipY)) - p.addLine(to: CGPoint(x: mx + hipHW, y: hipY)) - p.addLine(to: CGPoint(x: mx + hipHW, y: legBotY)) - p.addLine(to: CGPoint(x: mx + hipHW + footExtra, y: legBotY + footH)) - p.addLine(to: CGPoint(x: mx + gap, y: legBotY)) - p.closeSubpath() - - return p - } -} - +/// Body-zone highlight icon backed by vector SVG assets in `Assets.xcassets`. +/// +/// Each zone renders its own artwork: +/// - `upper-body` → `ZoneIconUpper` +/// - `lower-body` → `ZoneIconLower` +/// - `full-body` → `ZoneIconFull` +/// +/// The SVGs ship with their own baked-in colors, so the icon is **not** +/// tinted by `Theme.zoneGradient` — it shows the artwork as authored. +/// To make icons auto-recolor per zone instead, switch the imagesets to +/// single-color SVGs and set `"template-rendering-intent": "template"` +/// in each `Contents.json`, then apply `.foregroundStyle(...)` here. +/// +/// Drawn on a 56×80 frame with `scaledToFit()` so the SVG keeps its +/// native aspect ratio inside the card. struct ZoneHighlightIcon: View { let zone: String - private var waistFraction: CGFloat { 0.50 } - var body: some View { - let shape = BodySilhouetteShape() - ZStack { - shape - .fill(.white.opacity(0.12)) - - shape - .fill(zoneGradient) - .mask(zoneMask) - } - .frame(width: 56, height: 80) + Image(decorative: imageName, bundle: .main) + .resizable() + .scaledToFit() + .frame(width: 56, height: 80) } - private var zoneGradient: LinearGradient { - switch zone { - case "upper-body": - LinearGradient(colors: [.orange, .red.opacity(0.8)], startPoint: .top, endPoint: .bottom) - case "lower-body": - LinearGradient(colors: [.blue, .purple.opacity(0.8)], startPoint: .top, endPoint: .bottom) - case "full-body": - LinearGradient(colors: [Theme.brand, .purple], startPoint: .top, endPoint: .bottom) - default: - LinearGradient(colors: [.gray, .secondary], startPoint: .top, endPoint: .bottom) - } - } - - @ViewBuilder - private var zoneMask: some View { - switch zone { - case "upper-body": - Rectangle() - .frame(height: 80 * waistFraction) - .frame(maxHeight: .infinity, alignment: .top) - case "lower-body": - Rectangle() - .frame(height: 80 * waistFraction) - .frame(maxHeight: .infinity, alignment: .bottom) - case "full-body": - Rectangle() - default: - Rectangle() + private var imageName: String { + switch zone.lowercased() { + case "upper-body", "upper": return "ZoneIconUpper" + case "lower-body", "lower": return "ZoneIconLower" + default: return "ZoneIconFull" } } }