@@ -16796,7 +16796,7 @@ struct OnboardingFlow: View {
1679616796
1679716797 private var panelHeight: CGFloat {
1679816798 if isHerdDiscoveryStep { return 668 }
16799- if model.onboardingStep == learnStep { return 574 }
16799+ if model.onboardingStep == learnStep { return 604 }
1680016800 if isDropdownProfileStep { return 592 }
1680116801 return usesExpandedPanel ? 568 : 486
1680216802 }
@@ -21408,8 +21408,7 @@ struct OnboardingLearningStep: View {
2140821408 job: model.onboardingLearningJob,
2140921409 statusText: model.onboardingFinalizationStatus.isEmpty ? model.text(.learningPreparing) : model.onboardingFinalizationStatus
2141021410 )
21411- .frame(maxWidth: 640)
21412- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
21411+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
2141321412 .task {
2141421413 await model.startOnboardingFinalization()
2141521414 }
@@ -21425,12 +21424,12 @@ struct OnboardingLearningLivePanel: View {
2142521424 var body: some View {
2142621425 VStack(spacing: 12) {
2142721426 OnboardingLearningAnimation()
21428- .frame(width: 62 , height: 62 )
21427+ .frame(width: 260 , height: 156 )
2142921428 .accessibilityHidden(true)
2143021429
21431- VStack(spacing: 8 ) {
21430+ VStack(spacing: 6 ) {
2143221431 Text(model.text(.learningTitle))
21433- .font(.system(size: 23 , weight: .semibold))
21432+ .font(.system(size: 22 , weight: .semibold))
2143421433 .foregroundStyle(ElephantTheme.ink)
2143521434 .multilineTextAlignment(.center)
2143621435 Text(panelSubtitle)
@@ -21439,7 +21438,7 @@ struct OnboardingLearningLivePanel: View {
2143921438 .multilineTextAlignment(.center)
2144021439 .lineLimit(2)
2144121440 .fixedSize(horizontal: false, vertical: true)
21442- .frame(maxWidth: 500 )
21441+ .frame(maxWidth: 520 )
2144321442 }
2144421443
2144521444 HStack(alignment: .center, spacing: 14) {
@@ -21451,15 +21450,15 @@ struct OnboardingLearningLivePanel: View {
2145121450 .controlSize(.small)
2145221451 } else {
2145321452 Image(systemName: statusSymbol)
21454- .font(.callout .weight(.bold))
21453+ .font(.caption .weight(.bold))
2145521454 .foregroundStyle(statusTint)
2145621455 }
2145721456 }
21458- .frame(width: 42 , height: 42 )
21457+ .frame(width: 36 , height: 36 )
2145921458
2146021459 VStack(alignment: .leading, spacing: 4) {
2146121460 Text(stageTitle)
21462- .font(.callout .weight(.semibold))
21461+ .font(.caption .weight(.semibold))
2146321462 .foregroundStyle(ElephantTheme.ink)
2146421463 .lineLimit(1)
2146521464 Text(stageDetail)
@@ -21471,40 +21470,21 @@ struct OnboardingLearningLivePanel: View {
2147121470 Spacer(minLength: 0)
2147221471 Pill(text: statusLabel, tint: statusTint)
2147321472 }
21474- .padding(12)
21475- .frame(maxWidth: 540, minHeight: 76, alignment: .leading)
21476- .background(Color(nsColor: .textBackgroundColor).opacity(0.64), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
21477- .overlay(RoundedRectangle(cornerRadius: 10, style: .continuous).stroke(statusTint.opacity(0.20), lineWidth: 1))
21473+ .frame(maxWidth: 590, minHeight: 52, alignment: .leading)
21474+
21475+ Divider()
21476+ .frame(maxWidth: 590)
21477+ .opacity(0.55)
2147821478
2147921479 OnboardingLearningToolTimeline(job: job, statusText: statusText)
21480- .frame(maxWidth: 540)
21481- }
21482- .padding(.horizontal, 26)
21483- .padding(.vertical, 20)
21484- .frame(maxWidth: .infinity, minHeight: 442, alignment: .center)
21485- .background(learningPanelBackground)
21486- .overlay(RoundedRectangle(cornerRadius: 18, style: .continuous).stroke(Color.white.opacity(0.50), lineWidth: 1))
21487- .shadow(color: ElephantTheme.accent.opacity(0.10), radius: 30, x: 0, y: 18)
21480+ .frame(maxWidth: 590)
21481+ }
21482+ .padding(.horizontal, 18)
21483+ .padding(.vertical, 2)
21484+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
2148821485 .animation(reduceMotion ? nil : .easeOut(duration: 0.18), value: activityKey)
2148921486 }
2149021487
21491- private var learningPanelBackground: some View {
21492- RoundedRectangle(cornerRadius: 18, style: .continuous)
21493- .fill(.regularMaterial)
21494- .overlay(
21495- LinearGradient(
21496- colors: [
21497- ElephantTheme.accent.opacity(0.080),
21498- ElephantTheme.green.opacity(0.045),
21499- ElephantTheme.ember.opacity(0.035)
21500- ],
21501- startPoint: .topLeading,
21502- endPoint: .bottomTrailing
21503- )
21504- .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
21505- )
21506- }
21507-
2150821488 private var activityKey: String {
2150921489 "\(job?.status ?? "")|\(job?.progressStage ?? "")|\(job?.progressDetail ?? "")|\(job?.modelProgress.text ?? "")|\(latestToolItem?.id ?? "")"
2151021490 }
@@ -21706,7 +21686,7 @@ struct OnboardingLearningToolTimeline: View {
2170621686 var statusText: String
2170721687
2170821688 var body: some View {
21709- VStack(alignment: .leading, spacing: 8 ) {
21689+ VStack(alignment: .leading, spacing: 9 ) {
2171021690 HStack(alignment: .center, spacing: 9) {
2171121691 Image(systemName: "waveform.path.ecg")
2171221692 .font(.caption.weight(.bold))
@@ -21719,14 +21699,12 @@ struct OnboardingLearningToolTimeline: View {
2171921699 .truncationMode(.middle)
2172021700 Spacer(minLength: 0)
2172121701 }
21722- .padding(.horizontal, 10)
21723- .padding(.vertical, 7)
21724- .background(statusTint.opacity(0.08), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
21725- .overlay(RoundedRectangle(cornerRadius: 10, style: .continuous).stroke(statusTint.opacity(0.18), lineWidth: 1))
21702+ .padding(.horizontal, 2)
21703+ .padding(.vertical, 2)
2172621704
2172721705 ScrollViewReader { proxy in
2172821706 ScrollView(.vertical) {
21729- VStack(spacing: 8 ) {
21707+ VStack(spacing: 9 ) {
2173021708 if !modelProgress.isEmpty {
2173121709 OnboardingLearningModelLiveSlot(progress: modelProgress)
2173221710 .id("model")
@@ -21761,10 +21739,7 @@ struct OnboardingLearningToolTimeline: View {
2176121739 .frame(maxWidth: .infinity, alignment: .top)
2176221740 .clipped()
2176321741 }
21764- .padding(11)
2176521742 .frame(maxWidth: .infinity, minHeight: timelineHeight, maxHeight: timelineHeight, alignment: .top)
21766- .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
21767- .overlay(RoundedRectangle(cornerRadius: 12, style: .continuous).stroke(ElephantTheme.line.opacity(0.68), lineWidth: 1))
2176821743 .clipped()
2176921744 .animation(toolSlotAnimation, value: "\(latestToolItem?.id ?? "waiting")-\(modelProgress.text)")
2177021745 }
@@ -21780,9 +21755,9 @@ struct OnboardingLearningToolTimeline: View {
2178021755 return "\(title) · \(detail)"
2178121756 }
2178221757
21783- private var liveContentHeight: CGFloat { 118 }
21758+ private var liveContentHeight: CGFloat { 120 }
2178421759
21785- private var timelineHeight: CGFloat { 174 }
21760+ private var timelineHeight: CGFloat { 156 }
2178621761
2178721762 private var activityScrollKey: String {
2178821763 "\(modelProgress.text)|\(latestToolItem?.id ?? "")|\(job?.progressStage ?? "")|\(job?.progressDetail ?? "")"
@@ -22056,10 +22031,8 @@ struct OnboardingLearningToolTimelineEmpty: View {
2205622031 Spacer(minLength: 0)
2205722032 }
2205822033 .padding(.horizontal, 10)
22059- .padding(.vertical, 9)
22060- .frame(maxWidth: .infinity, minHeight: 76, maxHeight: 76, alignment: .leading)
22061- .background(ElephantTheme.canvas.opacity(0.48), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
22062- .overlay(RoundedRectangle(cornerRadius: 10, style: .continuous).stroke(ElephantTheme.line.opacity(0.42), lineWidth: 1))
22034+ .padding(.vertical, 8)
22035+ .frame(maxWidth: .infinity, minHeight: 58, alignment: .leading)
2206322036 }
2206422037}
2206522038
@@ -22101,10 +22074,8 @@ struct OnboardingLearningModelLiveSlot: View {
2210122074 .animation(reduceMotion ? nil : .easeOut(duration: 0.16), value: progress.text)
2210222075 }
2210322076 .padding(.horizontal, 12)
22104- .padding(.vertical, 10)
22105- .frame(maxWidth: .infinity, minHeight: 78, alignment: .topLeading)
22106- .background(ElephantTheme.canvas.opacity(0.52), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
22107- .overlay(RoundedRectangle(cornerRadius: 10, style: .continuous).stroke(ElephantTheme.accent.opacity(0.18), lineWidth: 1))
22077+ .padding(.vertical, 6)
22078+ .frame(maxWidth: .infinity, minHeight: 70, alignment: .topLeading)
2210822079 .onAppear {
2210922080 guard !reduceMotion else { return }
2211022081 pulse = true
@@ -22158,9 +22129,7 @@ struct OnboardingLearningToolLiveSlot: View {
2215822129 .background(item.tint.opacity(0.10), in: Capsule())
2215922130 }
2216022131 .padding(.horizontal, 12)
22161- .frame(maxWidth: .infinity, minHeight: 76, maxHeight: 76, alignment: .center)
22162- .background(item.tint.opacity(0.07), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
22163- .overlay(RoundedRectangle(cornerRadius: 10, style: .continuous).stroke(item.tint.opacity(0.16), lineWidth: 1))
22132+ .frame(maxWidth: .infinity, minHeight: 62, alignment: .center)
2216422133 }
2216522134
2216622135 private var slotEyebrow: String {
@@ -22196,32 +22165,73 @@ struct OnboardingLearningAnimation: View {
2219622165 Canvas { context, size in
2219722166 let seconds = reduceMotion ? 0 : timeline.date.timeIntervalSinceReferenceDate
2219822167 let center = CGPoint(x: size.width / 2, y: size.height / 2)
22199- let radius = min(size.width, size.height) * 0.34
22168+ let horizontalRadius = size.width * 0.38
22169+ let verticalRadius = size.height * 0.36
2220022170 let palette = [ElephantTheme.accent, ElephantTheme.green, ElephantTheme.ember]
2220122171
22202- for index in 0..<3 {
22203- let inset = CGFloat(index) * 18
22204- let rect = CGRect(x: center.x - radius + inset / 2, y: center.y - radius + inset / 2, width: (radius * 2) - inset, height: (radius * 2) - inset)
22172+ let glowRect = CGRect(
22173+ x: center.x - horizontalRadius * 1.18,
22174+ y: center.y - verticalRadius * 1.18,
22175+ width: horizontalRadius * 2.36,
22176+ height: verticalRadius * 2.36
22177+ )
22178+ context.fill(
22179+ Path(ellipseIn: glowRect),
22180+ with: .color(ElephantTheme.accent.opacity(0.055))
22181+ )
22182+
22183+ var nodes: [CGPoint] = []
22184+ for index in 0..<9 {
22185+ let lane = CGFloat(index % 3)
22186+ let nodeRadius = 0.62 + lane * 0.18
22187+ let angle = seconds * (0.26 + Double(index % 4) * 0.035) + Double(index) * .pi * 2 / 9
22188+ let point = CGPoint(
22189+ x: center.x + CGFloat(cos(angle)) * horizontalRadius * nodeRadius,
22190+ y: center.y + CGFloat(sin(angle)) * verticalRadius * nodeRadius
22191+ )
22192+ nodes.append(point)
22193+ }
22194+
22195+ for index in nodes.indices {
22196+ let next = nodes[(index + 3) % nodes.count]
22197+ var path = Path()
22198+ path.move(to: nodes[index])
22199+ path.addLine(to: next)
2220522200 context.stroke(
22206- Path(ellipseIn: rect) ,
22207- with: .color(palette[index]. opacity(0.26 )),
22208- style: StrokeStyle(lineWidth: 2, lineCap: .round, dash: [22, 16], dashPhase: CGFloat(seconds * 18 + Double(index) * 12) )
22201+ path ,
22202+ with: .color(ElephantTheme.accent. opacity(0.08 )),
22203+ style: StrokeStyle(lineWidth: 1. 2, lineCap: .round)
2220922204 )
2221022205 }
2221122206
22212- for index in 0..<6 {
22213- let angle = seconds * 0.42 + Double(index) * .pi / 3
22214- let point = CGPoint(
22215- x: center.x + CGFloat(cos(angle)) * (radius + 8),
22216- y: center.y + CGFloat(sin(angle)) * (radius + 8)
22207+ for index in 0..<4 {
22208+ let inset = CGFloat(index) * 20
22209+ let rect = CGRect(
22210+ x: center.x - horizontalRadius + inset / 2,
22211+ y: center.y - verticalRadius + inset / 3,
22212+ width: (horizontalRadius * 2) - inset,
22213+ height: (verticalRadius * 2) - inset * 0.66
22214+ )
22215+ context.stroke(
22216+ Path(ellipseIn: rect),
22217+ with: .color(palette[index % palette.count].opacity(0.18)),
22218+ style: StrokeStyle(lineWidth: 1.8, lineCap: .round, dash: [24, 18], dashPhase: CGFloat(seconds * 16 + Double(index) * 12))
2221722219 )
22218- let rect = CGRect(x: point.x - 4, y: point.y - 4, width: 8, height: 8)
22220+ }
22221+
22222+ for index in nodes.indices {
22223+ let pulse = 0.74 + 0.18 * sin(seconds * 1.25 + Double(index))
22224+ let size = CGFloat(6.5 + Double(index % 3) * 1.2) * CGFloat(pulse)
22225+ let rect = CGRect(x: nodes[index].x - size / 2, y: nodes[index].y - size / 2, width: size, height: size)
2221922226 context.fill(Path(ellipseIn: rect), with: .color(palette[index % palette.count].opacity(0.78)))
2222022227 }
2222122228
22222- let core = CGRect(x: center.x - 8, y: center.y - 8, width: 16, height: 16)
22229+ let corePulse = CGFloat(1.0 + 0.06 * sin(seconds * 1.5))
22230+ let coreSize = CGFloat(24) * corePulse
22231+ let core = CGRect(x: center.x - coreSize / 2, y: center.y - coreSize / 2, width: coreSize, height: coreSize)
22232+ context.fill(Path(ellipseIn: core.insetBy(dx: -12, dy: -12)), with: .color(ElephantTheme.accent.opacity(0.08)))
2222322233 context.fill(Path(ellipseIn: core), with: .color(ElephantTheme.accent.opacity(0.22)))
22224- context.fill(Path(ellipseIn: core.insetBy(dx: 4 , dy: 4 )), with: .color(ElephantTheme.accent.opacity(0.62)))
22234+ context.fill(Path(ellipseIn: core.insetBy(dx: 7 , dy: 7 )), with: .color(ElephantTheme.accent.opacity(0.62)))
2222522235 }
2222622236 }
2222722237 }
0 commit comments