[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71C4RHRVZJCY8QTNHFCEP7D":3,"skill-same:01M71C4RHRVZJCY8QTNHFCEP7D":55},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":28,"linkOnly":29,"markdownContent":30,"markdownBody":31,"frontMatter":32,"refName":33,"commitSha":34,"skillDirectory":35,"forksCount":36,"repositoryHtmlUrl":37,"repositoryHomepageUrl":6,"ownerHtmlUrl":38,"ownerBlogUrl":6,"ownerDescription":39,"updatedAt":40,"install":41,"resources":54},"01M71C4RHRVZJCY8QTNHFCEP7D","genjutsu-swiftui-motion",null,"SwiftUI animation foundations - withAnimation, transitions, matchedGeometryEffect, PhaseAnimator, KeyframeAnimator, springs, gestures.","nexuslinkproductions\u002Fyuri-os","nexuslinkproductions","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F273246263?v=4","01M718YX9NZRCW50B59B8ZVDKN","Marcel Spatz","01M718YXAQ7530F4B1VKN6SFQV",2,"MIT","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fgenjutsu-swiftui-motion\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27],"swiftui","motion","genjutsu swiftui motion","gestures","Animation","transitions","swiftui animation","foundations","springs","2026-11-10 10:12:30",false,"---\nname: genjutsu-swiftui-motion\ndescription: \"SwiftUI animation foundations - withAnimation, transitions, matchedGeometryEffect, PhaseAnimator, KeyframeAnimator, springs, gestures.\"\n---\n\n# SwiftUI Motion\n\n> SwiftUI animation core. Loaded for any SwiftUI project (iOS, macOS, multi-target Apple).\n> Concise rules here. Deep-dive in `references\u002F`.\n> Pair with `..\u002Fmotion-principles\u002FSKILL.md` (foundation) and `..\u002Fmobile-principles\u002FSKILL.md` (touch UX).\n\n---\n\n## Animation API decision tree\n\n| Need | API |\n|---|---|\n| Single value over time | `withAnimation { } + @State` or `.animation(_, value:)` |\n| Multiple coordinated states | `PhaseAnimator(phases)` (iOS 17+) |\n| Time-based keyframes | `KeyframeAnimator(initialValue:repeating:content:)` (iOS 17+) |\n| Custom property animations | `@Animatable` macro (iOS 17+) or `Animatable` protocol |\n| Shared element transitions | `matchedGeometryEffect(id:in:)` |\n| Gesture-driven | `DragGesture` \u002F `MagnifyGesture` + `.offset` \u002F `.scaleEffect` |\n| Loop forever | `.animation(.linear.repeatForever(autoreverses: true), value: ...)` or `.phaseAnimator` |\n\n**Rule:** start with `withAnimation`. Reach for `PhaseAnimator` only when you have 3+ ordered states. Reach for `KeyframeAnimator` only when you need parallel time-based tracks.\n\n---\n\n## Springs (the only easing you should care about)\n\nSwiftUI ships 4 named springs (iOS 17+). Use them. Tune `response` \u002F `dampingFraction` only when a preset is wrong.\n\n| Preset (iOS 17+) | Equivalent | Mood |\n|---|---|---|\n| `.snappy` | `.spring(response: 0.3, dampingFraction: 0.85)` | UI snappy |\n| `.bouncy` | `.spring(response: 0.5, dampingFraction: 0.7)` | playful |\n| `.smooth` | `.spring(response: 0.5, dampingFraction: 1.0)` | calm, no bounce |\n| `.interactiveSpring()` | `.spring(response: 0.15, dampingFraction: 0.86)` | gesture follow |\n\n`response` is the time the spring takes to settle (lower = snappier, higher = softer). `dampingFraction` is the overshoot intensity in `0...1` (1 = no overshoot, 0 = perpetual oscillation - never use 0). For UI work, stay in `response: 0.2...0.5` and `dampingFraction: 0.7...1.0`. Deep-dive: `references\u002Fsprings-cheatsheet.md`.\n\niOS 17+ also exposes `.spring(duration:bounce:)` where `bounce` is `0...1` (0 = critically damped, 1 = full bounce). It's the same spring, exposed in a more designer-friendly way:\n\n```swift\n.animation(.spring(duration: 0.4, bounce: 0.3), value: state)\n```\n\n---\n\n## Implicit vs explicit animations\n\n```swift\n\u002F\u002F Implicit - via .animation modifier (binds to a value)\nCircle()\n    .scaleEffect(scale)\n    .animation(.spring(.snappy), value: scale)\n```\n\n```swift\n\u002F\u002F Explicit - via withAnimation block\nButton(\"Grow\") {\n    withAnimation(.smooth) { scale = 1.5 }\n}\n```\n\n**Rule:** prefer explicit (`withAnimation`) for state changes triggered by user actions; use implicit when *any* change to a value should always animate (e.g., a progress bar that updates from anywhere). Never both on the same property - the outer `withAnimation` wins, but the implicit `.animation` modifier still runs and stacks confusingly.\n\n---\n\n## Transitions\n\nTransitions drive insertion \u002F removal of views inside an `if`, `switch`, or `ForEach`. They run when the parent's animation context fires (so wrap state mutations in `withAnimation`).\n\n```swift\nif visible {\n    Card().transition(.asymmetric(\n        insertion: .move(edge: .bottom).combined(with: .opacity),\n        removal: .opacity.animation(.easeIn(duration: 0.15))\n    ))\n}\n```\n\n**BAD - vanish into a black hole:**\n```swift\nCard().transition(.scale)  \u002F\u002F scales to 0, the universal \"broken\" feel\n```\n\n**GOOD - never scale to 0:**\n```swift\nCard().transition(\n    .scale(scale: 0.95).combined(with: .opacity)\n)\n```\n\niOS 17+ also has the `.transition(_:)` modifier with custom transitions via the `Transition` protocol - useful for shared timing across many views. For 90% of work, the built-in combinators (`.move`, `.opacity`, `.scale`, `.slide`, `.push`, `.asymmetric`, `.combined(with:)`) are enough.\n\n---\n\n## matchedGeometryEffect (hero animations)\n\nTag two views with the same `id` in the same `Namespace`. SwiftUI interpolates frame and position when the source view is replaced.\n\n```swift\nstruct Gallery: View {\n    @Namespace private var ns\n    @State private var expanded = false\n\n    var body: some View {\n        ZStack {\n            if expanded {\n                LargeCard()\n                    .matchedGeometryEffect(id: \"card\", in: ns)\n                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = false } }\n            } else {\n                SmallCard()\n                    .matchedGeometryEffect(id: \"card\", in: ns)\n                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = true } }\n            }\n        }\n    }\n}\n```\n\n`isSource: true` (default on the source-of-truth view) tells SwiftUI which frame to interpolate from. Common gotchas: id collisions across unrelated namespaces, view identity instability (use stable ids, not array indices), and animating out of an `if` branch where the destination view doesn't exist yet (wrap both branches inside the same parent, use opacity to hide instead of removing).\n\n---\n\n## PhaseAnimator (iOS 17+)\n\nFor ordered state choreography. Define a `CaseIterable + Hashable` enum, SwiftUI walks through phases sequentially, settling on the last one.\n\n```swift\nenum SuccessPhase: CaseIterable { case start, scaleUp, rotate, settle }\n\nstruct SuccessCheck: View {\n    @State private var trigger = false\n\n    var body: some View {\n        Image(systemName: \"checkmark.circle.fill\")\n            .font(.system(size: 64))\n            .foregroundStyle(.green)\n            .phaseAnimator(SuccessPhase.allCases, trigger: trigger) { view, phase in\n                view\n                    .scaleEffect(phase == .start ? 0 : phase == .settle ? 1 : 1.2)\n                    .rotationEffect(.degrees(phase == .rotate ? 360 : 0))\n                    .opacity(phase == .start ? 0 : 1)\n            } animation: { phase in\n                switch phase {\n                case .start: .smooth(duration: 0.05)\n                case .scaleUp: .spring(.bouncy, blendDuration: 0.25)\n                case .rotate: .spring(response: 0.4, dampingFraction: 0.8)\n                case .settle: .smooth(duration: 0.2)\n                }\n            }\n            .onTapGesture { trigger.toggle() }\n    }\n}\n```\n\n`trigger:` is optional - omit it to advance through phases automatically once on appear. Use it when you need an external signal (button tap, model update). Phases run sequentially, never in parallel - if you need parallelism, use `KeyframeAnimator`.\n\n---\n\n## KeyframeAnimator (iOS 17+)\n\nFor continuous, time-based animations with parallel tracks. Each `KeyframeTrack` animates one keypath independently; SwiftUI runs them all together.\n\n```swift\nstruct AnimationValues {\n    var scale: Double = 1\n    var rotation: Angle = .zero\n    var opacity: Double = 1\n}\n\nstruct HeartTap: View {\n    @State private var counter = 0\n\n    var body: some View {\n        Image(systemName: \"heart.fill\")\n            .font(.system(size: 64))\n            .foregroundStyle(.pink)\n            .keyframeAnimator(initialValue: AnimationValues(), trigger: counter) { content, values in\n                content\n                    .scaleEffect(values.scale)\n                    .rotationEffect(values.rotation)\n                    .opacity(values.opacity)\n            } keyframes: { _ in\n                KeyframeTrack(\\.scale) {\n                    SpringKeyframe(1.3, duration: 0.15)\n                    SpringKeyframe(1.0, duration: 0.3, spring: .bouncy)\n                }\n                KeyframeTrack(\\.rotation) {\n                    CubicKeyframe(.degrees(15), duration: 0.1)\n                    CubicKeyframe(.degrees(-15), duration: 0.2)\n                    CubicKeyframe(.degrees(0), duration: 0.15)\n                }\n            }\n            .onTapGesture { counter += 1 }\n    }\n}\n```\n\nFour keyframe types: `LinearKeyframe` (constant velocity between points), `SpringKeyframe` (settles with spring), `CubicKeyframe` (cubic bezier ease), `MoveKeyframe` (jump cut, no interpolation). Trigger on a value change to re-run the animation. Deep-dive: `references\u002Fphase-keyframe-deep.md`.\n\n---\n\n## Animatable \u002F @Animatable\n\nFor custom drawing that needs interpolation. The `@Animatable` macro (iOS 17+) auto-synthesizes `animatableData` for any `Equatable` properties; the older `Animatable` protocol still works.\n\n```swift\nstruct ProgressRing: Shape {\n    var progress: Double  \u002F\u002F 0...1\n\n    var animatableData: Double {\n        get { progress }\n        set { progress = newValue }\n    }\n\n    func path(in rect: CGRect) -> Path {\n        var p = Path()\n        p.addArc(\n            center: CGPoint(x: rect.midX, y: rect.midY),\n            radius: rect.width \u002F 2,\n            startAngle: .degrees(-90),\n            endAngle: .degrees(-90 + 360 * progress),\n            clockwise: false\n        )\n        return p\n    }\n}\n\nProgressRing(progress: progress)\n    .stroke(.tint, lineWidth: 6)\n    .animation(.spring(.smooth), value: progress)\n```\n\nFor multi-property shapes use `AnimatablePair\u003CA, B>` (or nested pairs) as `animatableData`. The `@Animatable` macro removes that boilerplate when properties are `Equatable + Animatable`.\n\n---\n\n## Gestures\n\n| Gesture | Type | Use |\n|---|---|---|\n| `TapGesture` | discrete | tap, double-tap (`count: 2`) |\n| `LongPressGesture` | discrete + continuous (`onEnded`\u002F`onChanged`) | context menus, hold-to-record |\n| `DragGesture` | continuous | drag, swipe-to-dismiss |\n| `MagnifyGesture` | continuous | pinch-zoom (iOS 17+, replaces `MagnificationGesture`) |\n| `RotateGesture` | continuous | rotate (iOS 17+, replaces `RotationGesture`) |\n| `SpatialTapGesture` | discrete | tap with location info |\n\n```swift\nlet tap = TapGesture().onEnded { print(\"tap\") }\nlet drag = DragGesture().onChanged { value in offset = value.translation }\nZStack { ... }\n    .gesture(tap.simultaneously(with: drag))\n```\n\nThree composition operators: `.simultaneously(with:)` (parallel recognition), `.sequenced(before:)` (one must complete first), `.exclusively(before:)` (one or the other, not both). For fine-grained control over when child views can claim the gesture, use `.simultaneousGesture(_, including: GestureMask)` with `.gesture`, `.subviews`, `.all`, or `.none`. Deep-dive: `references\u002Fgestures-swiftui.md`.\n\n---\n\n## Anti-Patterns (BAD \u002F GOOD)\n\n### 1. Deprecated `.animation` form (no value binding)\n\n```swift\n\u002F\u002F BAD - implicit-anim-everywhere, deprecated in iOS 15+\nCircle().scaleEffect(scale).animation(.easeInOut)\n```\n\n```swift\n\u002F\u002F GOOD - bind to a specific value\nCircle().scaleEffect(scale).animation(.easeInOut, value: scale)\n\u002F\u002F OR\nwithAnimation(.easeInOut) { scale = 1.5 }\n```\n\n### 2. Animating frame size directly\n\n```swift\n\u002F\u002F BAD - .frame() drives layout pass every frame, drops fps under load\nCard().frame(height: expanded ? 400 : 100)\n    .animation(.spring(), value: expanded)\n```\n\n```swift\n\u002F\u002F GOOD - animate transform-equivalents (scale, offset) that the compositor handles\nCard()\n    .frame(height: 400)\n    .scaleEffect(expanded ? 1 : 0.4, anchor: .top)\n    .animation(.spring(), value: expanded)\n\u002F\u002F OR for actual layout transitions, use matchedGeometryEffect\n```\n\n### 3. Scale to 0 (the vanish-into-nothing trap)\n\n```swift\n\u002F\u002F BAD - element vanishes into a black hole, feels broken\nCard().transition(.scale)\n```\n\n```swift\n\u002F\u002F GOOD - minimum scale 0.9-0.95 + opacity\nCard().transition(.scale(scale: 0.95).combined(with: .opacity))\n```\n\n### 4. `withAnimation` inside `body`\n\n```swift\n\u002F\u002F BAD - body runs on every render, animation re-fires arbitrarily\nvar body: some View {\n    let _ = withAnimation(.spring()) { scale = 1.2 }  \u002F\u002F never do this\n    Circle().scaleEffect(scale)\n}\n```\n\n```swift\n\u002F\u002F GOOD - trigger from user actions or .onChange\nvar body: some View {\n    Circle()\n        .scaleEffect(scale)\n        .onTapGesture {\n            withAnimation(.spring()) { scale = scale == 1 ? 1.2 : 1 }\n        }\n}\n```\n\n---\n\n## Reduced motion respect\n\nMandatory. SwiftUI exposes the iOS \u002F macOS \"Reduce Motion\" accessibility setting via the environment. See `..\u002Fmotion-principles\u002FSKILL.md` for the cross-platform rationale.\n\n```swift\nstruct Hero: View {\n    @Environment(\\.accessibilityReduceMotion) var reduceMotion\n    @State private var shown = false\n\n    var body: some View {\n        Text(\"Welcome\")\n            .opacity(shown ? 1 : 0)\n            .offset(y: shown ? 0 : (reduceMotion ? 0 : 20))\n            .animation(reduceMotion ? .none : .spring(.smooth), value: shown)\n            .onAppear { shown = true }\n    }\n}\n```\n\nRule: cross-fades and opacity are still allowed under reduced motion; large translations, scale-from-zero, parallax, and looping motion must be neutralized.\n\n---\n\n## Quick Reference: Loading sub-skills\n\n| Need | Load |\n|---|---|\n| Springs deep-dive (response\u002FdampingFraction tuning, mood) | `references\u002Fsprings-cheatsheet.md` |\n| PhaseAnimator + KeyframeAnimator complex sequences | `references\u002Fphase-keyframe-deep.md` |\n| Gesture composition + conflict resolution | `references\u002Fgestures-swiftui.md` |\n| Advanced visuals (Metal, Liquid Glass) | `..\u002Fswiftui-graphics\u002FSKILL.md` |\n| Cross-platform UX (mobile, desktop) | `..\u002Fmobile-principles\u002FSKILL.md`, `..\u002Fdesktop-principles\u002FSKILL.md` |\n| Foundation (timing, easing, a11y) | `..\u002Fmotion-principles\u002FSKILL.md` |\n\n---\n\n## Sources\n\n- [twostraws\u002FSwiftUI-Agent-Skill](https:\u002F\u002Fgithub.com\u002Ftwostraws\u002FSwiftUI-Agent-Skill)\n- [GetStream\u002Fswiftui-spring-animations](https:\u002F\u002Fgithub.com\u002FGetStream\u002Fswiftui-spring-animations)\n- [amosgyamfi\u002Fopen-swiftui-animations](https:\u002F\u002Fgithub.com\u002Famosgyamfi\u002Fopen-swiftui-animations)\n- [AvdLee\u002FSwiftUI-Agent-Skill](https:\u002F\u002Fgithub.com\u002FAvdLee\u002FSwiftUI-Agent-Skill)\n- [Apple SwiftUI Animations](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fswiftui\u002Fanimations)\n- [Apple PhaseAnimator](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fswiftui\u002Fphaseanimator)\n- [Apple KeyframeAnimator](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fswiftui\u002Fkeyframeanimator)\n- [WWDC23 Animate with springs](https:\u002F\u002Fdeveloper.apple.com\u002Fvideos\u002Fplay\u002Fwwdc2023\u002F10158\u002F)\n- [WWDC23 Wind your way through advanced animations](https:\u002F\u002Fdeveloper.apple.com\u002Fvideos\u002Fplay\u002Fwwdc2023\u002F10157\u002F)\n","# SwiftUI Motion\n\n> SwiftUI animation core. Loaded for any SwiftUI project (iOS, macOS, multi-target Apple).\n> Concise rules here. Deep-dive in `references\u002F`.\n> Pair with `..\u002Fmotion-principles\u002FSKILL.md` (foundation) and `..\u002Fmobile-principles\u002FSKILL.md` (touch UX).\n\n---\n\n## Animation API decision tree\n\n| Need | API |\n|---|---|\n| Single value over time | `withAnimation { } + @State` or `.animation(_, value:)` |\n| Multiple coordinated states | `PhaseAnimator(phases)` (iOS 17+) |\n| Time-based keyframes | `KeyframeAnimator(initialValue:repeating:content:)` (iOS 17+) |\n| Custom property animations | `@Animatable` macro (iOS 17+) or `Animatable` protocol |\n| Shared element transitions | `matchedGeometryEffect(id:in:)` |\n| Gesture-driven | `DragGesture` \u002F `MagnifyGesture` + `.offset` \u002F `.scaleEffect` |\n| Loop forever | `.animation(.linear.repeatForever(autoreverses: true), value: ...)` or `.phaseAnimator` |\n\n**Rule:** start with `withAnimation`. Reach for `PhaseAnimator` only when you have 3+ ordered states. Reach for `KeyframeAnimator` only when you need parallel time-based tracks.\n\n---\n\n## Springs (the only easing you should care about)\n\nSwiftUI ships 4 named springs (iOS 17+). Use them. Tune `response` \u002F `dampingFraction` only when a preset is wrong.\n\n| Preset (iOS 17+) | Equivalent | Mood |\n|---|---|---|\n| `.snappy` | `.spring(response: 0.3, dampingFraction: 0.85)` | UI snappy |\n| `.bouncy` | `.spring(response: 0.5, dampingFraction: 0.7)` | playful |\n| `.smooth` | `.spring(response: 0.5, dampingFraction: 1.0)` | calm, no bounce |\n| `.interactiveSpring()` | `.spring(response: 0.15, dampingFraction: 0.86)` | gesture follow |\n\n`response` is the time the spring takes to settle (lower = snappier, higher = softer). `dampingFraction` is the overshoot intensity in `0...1` (1 = no overshoot, 0 = perpetual oscillation - never use 0). For UI work, stay in `response: 0.2...0.5` and `dampingFraction: 0.7...1.0`. Deep-dive: `references\u002Fsprings-cheatsheet.md`.\n\niOS 17+ also exposes `.spring(duration:bounce:)` where `bounce` is `0...1` (0 = critically damped, 1 = full bounce). It's the same spring, exposed in a more designer-friendly way:\n\n```swift\n.animation(.spring(duration: 0.4, bounce: 0.3), value: state)\n```\n\n---\n\n## Implicit vs explicit animations\n\n```swift\n\u002F\u002F Implicit - via .animation modifier (binds to a value)\nCircle()\n    .scaleEffect(scale)\n    .animation(.spring(.snappy), value: scale)\n```\n\n```swift\n\u002F\u002F Explicit - via withAnimation block\nButton(\"Grow\") {\n    withAnimation(.smooth) { scale = 1.5 }\n}\n```\n\n**Rule:** prefer explicit (`withAnimation`) for state changes triggered by user actions; use implicit when *any* change to a value should always animate (e.g., a progress bar that updates from anywhere). Never both on the same property - the outer `withAnimation` wins, but the implicit `.animation` modifier still runs and stacks confusingly.\n\n---\n\n## Transitions\n\nTransitions drive insertion \u002F removal of views inside an `if`, `switch`, or `ForEach`. They run when the parent's animation context fires (so wrap state mutations in `withAnimation`).\n\n```swift\nif visible {\n    Card().transition(.asymmetric(\n        insertion: .move(edge: .bottom).combined(with: .opacity),\n        removal: .opacity.animation(.easeIn(duration: 0.15))\n    ))\n}\n```\n\n**BAD - vanish into a black hole:**\n```swift\nCard().transition(.scale)  \u002F\u002F scales to 0, the universal \"broken\" feel\n```\n\n**GOOD - never scale to 0:**\n```swift\nCard().transition(\n    .scale(scale: 0.95).combined(with: .opacity)\n)\n```\n\niOS 17+ also has the `.transition(_:)` modifier with custom transitions via the `Transition` protocol - useful for shared timing across many views. For 90% of work, the built-in combinators (`.move`, `.opacity`, `.scale`, `.slide`, `.push`, `.asymmetric`, `.combined(with:)`) are enough.\n\n---\n\n## matchedGeometryEffect (hero animations)\n\nTag two views with the same `id` in the same `Namespace`. SwiftUI interpolates frame and position when the source view is replaced.\n\n```swift\nstruct Gallery: View {\n    @Namespace private var ns\n    @State private var expanded = false\n\n    var body: some View {\n        ZStack {\n            if expanded {\n                LargeCard()\n                    .matchedGeometryEffect(id: \"card\", in: ns)\n                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = false } }\n            } else {\n                SmallCard()\n                    .matchedGeometryEffect(id: \"card\", in: ns)\n                    .onTapGesture { withAnimation(.spring(.smooth)) { expanded = true } }\n            }\n        }\n    }\n}\n```\n\n`isSource: true` (default on the source-of-truth view) tells SwiftUI which frame to interpolate from. Common gotchas: id collisions across unrelated namespaces, view identity instability (use stable ids, not array indices), and animating out of an `if` branch where the destination view doesn't exist yet (wrap both branches inside the same parent, use opacity to hide instead of removing).\n\n---\n\n## PhaseAnimator (iOS 17+)\n\nFor ordered state choreography. Define a `CaseIterable + Hashable` enum, SwiftUI walks through phases sequentially, settling on the last one.\n\n```swift\nenum SuccessPhase: CaseIterable { case start, scaleUp, rotate, settle }\n\nstruct SuccessCheck: View {\n    @State private var trigger = false\n\n    var body: some View {\n        Image(systemName: \"checkmark.circle.fill\")\n            .font(.system(size: 64))\n            .foregroundStyle(.green)\n            .phaseAnimator(SuccessPhase.allCases, trigger: trigger) { view, phase in\n                view\n                    .scaleEffect(phase == .start ? 0 : phase == .settle ? 1 : 1.2)\n                    .rotationEffect(.degrees(phase == .rotate ? 360 : 0))\n                    .opacity(phase == .start ? 0 : 1)\n            } animation: { phase in\n                switch phase {\n                case .start: .smooth(duration: 0.05)\n                case .scaleUp: .spring(.bouncy, blendDuration: 0.25)\n                case .rotate: .spring(response: 0.4, dampingFraction: 0.8)\n                case .settle: .smooth(duration: 0.2)\n                }\n            }\n            .onTapGesture { trigger.toggle() }\n    }\n}\n```\n\n`trigger:` is optional - omit it to advance through phases automatically once on appear. Use it when you need an external signal (button tap, model update). Phases run sequentially, never in parallel - if you need parallelism, use `KeyframeAnimator`.\n\n---\n\n## KeyframeAnimator (iOS 17+)\n\nFor continuous, time-based animations with parallel tracks. Each `KeyframeTrack` animates one keypath independently; SwiftUI runs them all together.\n\n```swift\nstruct AnimationValues {\n    var scale: Double = 1\n    var rotation: Angle = .zero\n    var opacity: Double = 1\n}\n\nstruct HeartTap: View {\n    @State private var counter = 0\n\n    var body: some View {\n        Image(systemName: \"heart.fill\")\n            .font(.system(size: 64))\n            .foregroundStyle(.pink)\n            .keyframeAnimator(initialValue: AnimationValues(), trigger: counter) { content, values in\n                content\n                    .scaleEffect(values.scale)\n                    .rotationEffect(values.rotation)\n                    .opacity(values.opacity)\n            } keyframes: { _ in\n                KeyframeTrack(\\.scale) {\n                    SpringKeyframe(1.3, duration: 0.15)\n                    SpringKeyframe(1.0, duration: 0.3, spring: .bouncy)\n                }\n                KeyframeTrack(\\.rotation) {\n                    CubicKeyframe(.degrees(15), duration: 0.1)\n                    CubicKeyframe(.degrees(-15), duration: 0.2)\n                    CubicKeyframe(.degrees(0), duration: 0.15)\n                }\n            }\n            .onTapGesture { counter += 1 }\n    }\n}\n```\n\nFour keyframe types: `LinearKeyframe` (constant velocity between points), `SpringKeyframe` (settles with spring), `CubicKeyframe` (cubic bezier ease), `MoveKeyframe` (jump cut, no interpolation). Trigger on a value change to re-run the animation. Deep-dive: `references\u002Fphase-keyframe-deep.md`.\n\n---\n\n## Animatable \u002F @Animatable\n\nFor custom drawing that needs interpolation. The `@Animatable` macro (iOS 17+) auto-synthesizes `animatableData` for any `Equatable` properties; the older `Animatable` protocol still works.\n\n```swift\nstruct ProgressRing: Shape {\n    var progress: Double  \u002F\u002F 0...1\n\n    var animatableData: Double {\n        get { progress }\n        set { progress = newValue }\n    }\n\n    func path(in rect: CGRect) -> Path {\n        var p = Path()\n        p.addArc(\n            center: CGPoint(x: rect.midX, y: rect.midY),\n            radius: rect.width \u002F 2,\n            startAngle: .degrees(-90),\n            endAngle: .degrees(-90 + 360 * progress),\n            clockwise: false\n        )\n        return p\n    }\n}\n\nProgressRing(progress: progress)\n    .stroke(.tint, lineWidth: 6)\n    .animation(.spring(.smooth), value: progress)\n```\n\nFor multi-property shapes use `AnimatablePair\u003CA, B>` (or nested pairs) as `animatableData`. The `@Animatable` macro removes that boilerplate when properties are `Equatable + Animatable`.\n\n---\n\n## Gestures\n\n| Gesture | Type | Use |\n|---|---|---|\n| `TapGesture` | discrete | tap, double-tap (`count: 2`) |\n| `LongPressGesture` | discrete + continuous (`onEnded`\u002F`onChanged`) | context menus, hold-to-record |\n| `DragGesture` | continuous | drag, swipe-to-dismiss |\n| `MagnifyGesture` | continuous | pinch-zoom (iOS 17+, replaces `MagnificationGesture`) |\n| `RotateGesture` | continuous | rotate (iOS 17+, replaces `RotationGesture`) |\n| `SpatialTapGesture` | discrete | tap with location info |\n\n```swift\nlet tap = TapGesture().onEnded { print(\"tap\") }\nlet drag = DragGesture().onChanged { value in offset = value.translation }\nZStack { ... }\n    .gesture(tap.simultaneously(with: drag))\n```\n\nThree composition operators: `.simultaneously(with:)` (parallel recognition), `.sequenced(before:)` (one must complete first), `.exclusively(before:)` (one or the other, not both). For fine-grained control over when child views can claim the gesture, use `.simultaneousGesture(_, including: GestureMask)` with `.gesture`, `.subviews`, `.all`, or `.none`. Deep-dive: `references\u002Fgestures-swiftui.md`.\n\n---\n\n## Anti-Patterns (BAD \u002F GOOD)\n\n### 1. Deprecated `.animation` form (no value binding)\n\n```swift\n\u002F\u002F BAD - implicit-anim-everywhere, deprecated in iOS 15+\nCircle().scaleEffect(scale).animation(.easeInOut)\n```\n\n```swift\n\u002F\u002F GOOD - bind to a specific value\nCircle().scaleEffect(scale).animation(.easeInOut, value: scale)\n\u002F\u002F OR\nwithAnimation(.easeInOut) { scale = 1.5 }\n```\n\n### 2. Animating frame size directly\n\n```swift\n\u002F\u002F BAD - .frame() drives layout pass every frame, drops fps under load\nCard().frame(height: expanded ? 400 : 100)\n    .animation(.spring(), value: expanded)\n```\n\n```swift\n\u002F\u002F GOOD - animate transform-equivalents (scale, offset) that the compositor handles\nCard()\n    .frame(height: 400)\n    .scaleEffect(expanded ? 1 : 0.4, anchor: .top)\n    .animation(.spring(), value: expanded)\n\u002F\u002F OR for actual layout transitions, use matchedGeometryEffect\n```\n\n### 3. Scale to 0 (the vanish-into-nothing trap)\n\n```swift\n\u002F\u002F BAD - element vanishes into a black hole, feels broken\nCard().transition(.scale)\n```\n\n```swift\n\u002F\u002F GOOD - minimum scale 0.9-0.95 + opacity\nCard().transition(.scale(scale: 0.95).combined(with: .opacity))\n```\n\n### 4. `withAnimation` inside `body`\n\n```swift\n\u002F\u002F BAD - body runs on every render, animation re-fires arbitrarily\nvar body: some View {\n    let _ = withAnimation(.spring()) { scale = 1.2 }  \u002F\u002F never do this\n    Circle().scaleEffect(scale)\n}\n```\n\n```swift\n\u002F\u002F GOOD - trigger from user actions or .onChange\nvar body: some View {\n    Circle()\n        .scaleEffect(scale)\n        .onTapGesture {\n            withAnimation(.spring()) { scale = scale == 1 ? 1.2 : 1 }\n        }\n}\n```\n\n---\n\n## Reduced motion respect\n\nMandatory. SwiftUI exposes the iOS \u002F macOS \"Reduce Motion\" accessibility setting via the environment. See `..\u002Fmotion-principles\u002FSKILL.md` for the cross-platform rationale.\n\n```swift\nstruct Hero: View {\n    @Environment(\\.accessibilityReduceMotion) var reduceMotion\n    @State private var shown = false\n\n    var body: some View {\n        Text(\"Welcome\")\n            .opacity(shown ? 1 : 0)\n            .offset(y: shown ? 0 : (reduceMotion ? 0 : 20))\n            .animation(reduceMotion ? .none : .spring(.smooth), value: shown)\n            .onAppear { shown = true }\n    }\n}\n```\n\nRule: cross-fades and opacity are still allowed under reduced motion; large translations, scale-from-zero, parallax, and looping motion must be neutralized.\n\n---\n\n## Quick Reference: Loading sub-skills\n\n| Need | Load |\n|---|---|\n| Springs deep-dive (response\u002FdampingFraction tuning, mood) | `references\u002Fsprings-cheatsheet.md` |\n| PhaseAnimator + KeyframeAnimator complex sequences | `references\u002Fphase-keyframe-deep.md` |\n| Gesture composition + conflict resolution | `references\u002Fgestures-swiftui.md` |\n| Advanced visuals (Metal, Liquid Glass) | `..\u002Fswiftui-graphics\u002FSKILL.md` |\n| Cross-platform UX (mobile, desktop) | `..\u002Fmobile-principles\u002FSKILL.md`, `..\u002Fdesktop-principles\u002FSKILL.md` |\n| Foundation (timing, easing, a11y) | `..\u002Fmotion-principles\u002FSKILL.md` |\n\n---\n\n## Sources\n\n- [twostraws\u002FSwiftUI-Agent-Skill](https:\u002F\u002Fgithub.com\u002Ftwostraws\u002FSwiftUI-Agent-Skill)\n- [GetStream\u002Fswiftui-spring-animations](https:\u002F\u002Fgithub.com\u002FGetStream\u002Fswiftui-spring-animations)\n- [amosgyamfi\u002Fopen-swiftui-animations](https:\u002F\u002Fgithub.com\u002Famosgyamfi\u002Fopen-swiftui-animations)\n- [AvdLee\u002FSwiftUI-Agent-Skill](https:\u002F\u002Fgithub.com\u002FAvdLee\u002FSwiftUI-Agent-Skill)\n- [Apple SwiftUI Animations](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fswiftui\u002Fanimations)\n- [Apple PhaseAnimator](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fswiftui\u002Fphaseanimator)\n- [Apple KeyframeAnimator](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fswiftui\u002Fkeyframeanimator)\n- [WWDC23 Animate with springs](https:\u002F\u002Fdeveloper.apple.com\u002Fvideos\u002Fplay\u002Fwwdc2023\u002F10158\u002F)\n- [WWDC23 Wind your way through advanced animations](https:\u002F\u002Fdeveloper.apple.com\u002Fvideos\u002Fplay\u002Fwwdc2023\u002F10157\u002F)\n",{"name":5,"description":7},"main","4c6d8d65f3d6096e2fc25b0845e481dda73c2794","skills\u002Fgenjutsu-swiftui-motion",0,"https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions","Building deterministic AI governance and continuity systems. Workflow architect + visual storyteller","2026-11-11 16:42:59",{"source":8,"skillName":5,"commands":42,"prompt":52,"zipAvailable":53},[43,46,49],{"tool":44,"command":45},"npx","npx skills add nexuslinkproductions\u002Fyuri-os --skill genjutsu-swiftui-motion",{"tool":47,"command":48},"bunx","bunx skills add nexuslinkproductions\u002Fyuri-os --skill genjutsu-swiftui-motion",{"tool":50,"command":51},"pnpm","pnpm dlx skills add nexuslinkproductions\u002Fyuri-os --skill genjutsu-swiftui-motion","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fgenjutsu-swiftui-motion\u002FSKILL.md",true,[],[56,79,103,133,162,181],{"skillNo":57,"name":58,"nameZh":6,"description":59,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":60,"categories":61,"keywords":62,"publishedAt":28},"01M71CG1AME5WC0A79AE65WT0X","zoom-out","Tell the agent to zoom out and give broader context or a higher-level perspective. Use when you're unfamiliar with a section of code or need to understand how it fits into the bigger picture.","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fzoom-out\u002FSKILL.md",[],[63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78],"zoom out","zoom","with","harness","how","section","re","true","understand","use","perspective","when","you","code","it","context",{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":28},"01M71CFY9ATD0BAXV6B14HFG7C","yuri-shura","6-perspective adversarial review for high-stakes turns (architecture decisions, refactor planning, deployment review) — fans out architect, adversary (7-vector attack), maintainer, ops, product, and security perspectives in parallel, then consolidates. Use when user says 'review this decision', '\u002Fshura', 'adversarial review', 'what could go wrong', 'architecture review', or 'deployment review'.","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fyuri-shura\u002FSKILL.md",[],[86,87,66,88,89,90,91,92,93,94,95,72,96,74,97,98,99,100,101,102],"yuri shura","parallel","architecture review","planning","for","deployment","vector","strategic review","decision","strategic","ops","workflow","decisions","architecture","review","perspectives","adversarial",{"skillNo":104,"name":105,"nameZh":6,"description":106,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":114,"publishedAt":28},"01M71CFTTYW1HA2MNX38EV1GJ5","yuri-sales-intelligence","Unified sales and outreach skill generating cold outreach openers, social selling sequences, objection rebuttals, and ad creative for any paid platform — multi-persona aware, adapting tone and offer frame to buyer type. Use when user says 'write a cold email', 'outreach sequence', 'objection handling', 'ad copy', or 'sales pitch'.","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fyuri-sales-intelligence\u002FSKILL.md",[109],{"categoryNo":110,"categoryName":111,"taxonomyType":112,"taxonomyTypeDesc":113},"01JBSEEDCAT000000000000005","邮件与沟通","SCENARIO","使用场景",[115,116,117,118,119,120,121,122,123,74,124,125,126,127,128,129,130,90,131,132],"intelligence","sales intelligence","yuri sales intelligence","sales","cold","paid","social","persona","pitch","cold email","ad creative","write","sequences","sequence","platform","email","cold outreach","frame",{"skillNo":134,"name":135,"nameZh":6,"description":136,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":137,"categories":138,"keywords":147,"publishedAt":28},"01M71CFQMBFTFADJNGP0Z2WCYK","yuri-code-intelligence","Unified code quality intelligence layer that diagnoses code smells (Long Method, Feature Envy, design pattern issues), prescribes targeted Fowler refactoring techniques, and reviews code for security, performance, and clarity. Use when user says 'review this code', 'refactor this', 'what's wrong with this method', 'code smells', or 'clean this up'.","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fyuri-code-intelligence\u002FSKILL.md",[139,142],{"categoryNo":140,"categoryName":141,"taxonomyType":112,"taxonomyTypeDesc":113},"01JBSEEDCAT000000000000007","代码审查",{"categoryNo":143,"categoryName":144,"taxonomyType":145,"taxonomyTypeDesc":146},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[148,76,149,115,66,150,151,152,90,153,154,155,156,157,158,159,160,161,72,74],"yuri code intelligence","code intelligence","reviews","code quality","feature","performance","code review","long","up","issues","clean","techniques","improve","design",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":168,"publishedAt":28},"01M71CFP32RT6GD3GKNHCJP8VP","yuri-adw","Invoke when user asks to run the ADW loop \u002F agentic engineering loop \u002F software factory workflow.","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fyuri-adw\u002FSKILL.md",[],[169,170,171,172,173,174,175,176,177,74,178,97,179,180],"yuri adw","run","engineering loop","software factory","software","agentic engineering","factory","loop","invoke","engineering","agentic","user",{"skillNo":182,"name":183,"nameZh":6,"description":184,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":185,"categories":186,"keywords":199,"publishedAt":28},"01M71CFK4VPEKY6A73237P7RSN","writing-skills","Use when creating new skills, editing existing skills, or verifying skills work before deployment","https:\u002F\u002Fgithub.com\u002Fnexuslinkproductions\u002Fyuri-os\u002Fblob\u002Fmain\u002Fskills\u002Fwriting-skills\u002FSKILL.md",[187,190,193,196],{"categoryNo":188,"categoryName":189,"taxonomyType":112,"taxonomyTypeDesc":113},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":191,"categoryName":192,"taxonomyType":112,"taxonomyTypeDesc":113},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":194,"categoryName":195,"taxonomyType":112,"taxonomyTypeDesc":113},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":197,"categoryName":198,"taxonomyType":145,"taxonomyTypeDesc":146},"01JBSEEDCAT000000000000036","内容创作者",[200,201,202,74,97,203,66,204,91,72,205],"writing","writing skills","new","work","editing","Existing"]