Files
obsidian-vault/personal/projects/psychologist-app/character-design.md
T

43 KiB
Raw Blame History

Дизайн Анимированного Персонажа для AI-Компаньона (Психологическое Приложение)

Исследование выполнено: май 2026
Контекст: персонаж — визуальное лицо AI-компаньона в приложении психологической рефлексии. Абстрактный (не человек, не робот). Реагирует на эмоции и голос пользователя.


1. Существующие Примеры Абстрактных AI Персонажей

1.1 Большие платформы

🔵 Siri (Apple) — Waveform / Орб

  • Эволюция: Siri прошёл путь от простых волн (iOS 7–12) до живого светящегося шара (iOS 14+) и «гало» вокруг экрана (iOS 18/26)
  • Что работает: плавные цветовые переходы синий→фиолетовый→розовый ассоциируются с «думающим» AI без антропоморфизации
  • Реализация: Core Animation + Metal шейдеры; собственный рендер Apple
  • Эмоциональный эффект: пользователи воспринимают его как «живой», но не угрожающий (Reddit: r/ios26, обсуждение «Type to Siri Wavy Effect»)
  • Референс: Apple Human Interface Guidelines — Siri

🟡 Alexa (Amazon) — Кольцо/Ring

  • Визуал: круговое световое кольцо: синий = слушает, оранжевый = нет сети, фиолетовый = не беспокоить, белый = регулировка громкости
  • Что работает: семиотика цвета как язык состояний — пользователи быстро запоминают коды без слов
  • Ограничение для нашего case: слишком «железный», не подходит для эмпатийного контекста

🔮 Google Assistant — Точки / G-шар

  • Визуал: 4 цветные точки (синяя, красная, жёлтая, зелёная), анимирующиеся в вол-ну при активации; позже — 3D светящийся «G»
  • Что работает: цветовая идентичность бренда, минимализм
  • Ограничение: слишком корпоративный, не тёплый

🟢 ChatGPT (OpenAI) — Пульсирующий Шар

  • Визуал: анимированный чёрно-белый шар в голосовом режиме Canvas/voice mode; в web — простая анимация печатающегося текста
  • Решение: нейтральный ч/б орб не навязывает эмоцию — пользователь сам проецирует
  • Реакция пользователей: в Reddit threads (r/ChatGPT) отмечают, что голосовой орб ощущается «медитативно», снижает тревогу перед диалогом

🔵 Microsoft Copilot — Градиентная Орбита

  • Визуал: анимированная орбита из синих и фиолетовых полос, морфирующая при ответе
  • Что работает: ощущение «умного движения», не угрожающего; подходит для рабочей среды

1.2 Mental Health / Медитация

🧘 Headspace — «Andy» (апельсиновый шар)

  • Визуал: простой оранжевый круг с минимальными чертами лица (точки-глаза, дуга-рот)
  • Степень абстракции: умеренная — есть лицо, но без носа/ушей
  • Что работает: тёплый цвет = дружелюбие, округлость = безопасность
  • Референс: Headspace Design System
  • Ограничение: лицо делает его «детским» — не подходит для взрослой аудитории

🌊 Calm — Анимированные Фоны

  • Подход: Calm отказался от персонажа-компаньона в пользу окружающей среды (вода, листья, луна)
  • Вывод для нас: персонаж должен быть частью «среды», а не отдельным элементом

💜 Replika (AI Companion) — 3D Аватар

  • Подход: антропоморфный 3D аватар, настраиваемый пользователем
  • Минус: тревожная долина (Uncanny Valley), юридические и этические вопросы привязанности
  • Вывод: НЕ делать антропоморфный. Наш выбор абстракции — правильный

🌿 Woebot — Минималистичный Робот

  • Визуал: простой квадратный «робот» с экраном-лицом
  • Что работает: простота снижает тревогу, неантропоморфность делает его «инструментом», а не «существом»
  • Ограничение: слишком «роботизированный» для эмпатийного контекста

1.3 Прочие Примеры

🦜 Duolingo — Duo (сова) + Lily

  • Duo: антропоморфная сова — зелёная, с глазами; огромный успех с точки зрения вовлечённости
  • Lily (AI Video Call, 2025): реализован в Rive — анимированный персонаж с state machine, живущий в видеозвонке
  • Кейс-стади Rive: Duolingo использует Rive для интерактивных персонажей, потому что state machine позволяет плавно переходить между сотнями эмоциональных состояний без написания кода вручную
  • Вывод: Rive — production-proven для персонажей в consumer apps

🎵 Spotify Wrapped 2025

  • Реализован в Rive; дизайн отправляет в production интерактивную анимацию, код управляет данными
  • Паттерн: «design ships interaction in Rive, code drives it with data» — именно то, что нам нужно

🤖 Notion AI — Спиннер-искры

  • Визуал: анимированные искры/частицы при генерации текста
  • Что работает: ощущение «творческой энергии», не агрессивное
  • Минус: статичный, нет эмоциональных состояний

2. Rive.app — Детальный Разбор

2.1 Что такое Rive и зачем он нам

Rive — это инструмент для создания интерактивных, production-ready анимаций с встроенной логикой через State Machine. В отличие от Lottie (только воспроизведение заранее заданной анимации), Rive позволяет:

  • Управлять переходами между состояниями в реальном времени
  • Передавать данные из кода (амплитуда звука, состояние AI) прямо в дизайн
  • Дизайнер работает в редакторе → разработчик подключает .riv файл → интерактивность работает «из коробки»

Ключевое преимущество для нашего случая: голосовая реактивность реализуется через Number input в State Machine — разработчик передаёт значение (0.0–1.0 от амплитуды микрофона), дизайн реагирует без написания анимационного кода.


2.2 Rive для iOS — Техническая Интеграция

Репозиторий: github.com/rive-app/rive-ios

  • 780 | MIT License | v6.20.3 (май 2026) | 239 релизов

Установка (Swift Package Manager — рекомендуется):

// Package.swift
dependencies: [
    .package(url: "https://github.com/rive-app/rive-ios", from: "6.13.0")
]

Требования: iOS 14.0+, visionOS 1.0+, tvOS 16.0+, macOS 13.1+

Минимальная интеграция в SwiftUI (Legacy API — проще для начала):

import RiveRuntime

struct CharacterView: View {
    var body: some View {
        RiveViewModel(
            fileName: "ai_companion",
            stateMachineName: "EmotionMachine"
        ).view()
    }
}

Продвинутая интеграция (новый runtime, Swift Concurrency):

import RiveRuntime

struct CharacterView: View {
    @State private var riveView: RiveUIView?
    
    var body: some View {
        AsyncRiveUIViewRepresentable {
            let worker = try await Worker()
            let file = try await File(source: .local("ai_companion", Bundle.main), worker: worker)
            return try await Rive(file: file)
        }
    }
}

Управление через State Machine (Data Binding):

// Передача амплитуды микрофона → анимация
let numberProp = viewModelInstance.numberProperty(fromPath: "audioAmplitude")!
numberProp.value = Float(microphoneAmplitude) // 0.0...1.0

// Изменение эмоционального состояния
let enumProp = viewModelInstance.enumProperty(fromPath: "emotionState")!
enumProp.value = "thinking"  // или "speaking", "listening", "happy", "concerned"

2.3 State Machine в Rive — Как Работает

State Machine — это граф состояний анимации с переходами.

Для нашего персонажа:

[Entry] → [Idle/Breathing] ↔ [Listening] ↔ [Thinking] ↔ [Speaking] ↔ [Happy] ↔ [Concerned]

Типы inputs:

Тип Использование
Boolean isListening, isSpeaking, isThinking
Number audioAmplitude (0.01.0), emotionIntensity
Trigger onHappy, onConcerned, onGreet
Enum currentState (idle/listen/think/speak/happy/concerned)

Settled State: автоматически останавливает рендеринг когда нет активных переходов → экономит CPU/батарею. Для idle-анимации = зацикленная бесконечная анимация, которая продолжает работать.

Playback управление:

simpleVM.pause()   // заморозить на текущем кадре
simpleVM.play()    // возобновить с текущей позиции  
simpleVM.stop()    // сброс к начальному состоянию
simpleVM.reset()   // полный сброс state machine

2.4 Голосовая Реактивность: Как Именно

Схема реализации:

  1. AVAudioEngine (iOS) → получаем амплитуду микрофона в реальном времени
  2. Нормализуем: amplitude = (rawValue + 160) / 160 (или другой масштаб)
  3. Передаём в Rive через Number property: audioAmplitude = smoothedAmplitude
  4. В Rive дизайнер привязывает это число к scale/radius/деформации blob

Код для получения амплитуды:

import AVFoundation

class MicrophoneMonitor: ObservableObject {
    private var audioEngine = AVAudioEngine()
    @Published var amplitude: Float = 0.0
    
    func startMonitoring() {
        let inputNode = audioEngine.inputNode
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { buffer, _ in
            guard let channelData = buffer.floatChannelData else { return }
            let frameLength = Int(buffer.frameLength)
            let rms = sqrt(channelData[0][0..<frameLength].map { $0 * $0 }.reduce(0, +) / Float(frameLength))
            DispatchQueue.main.async {
                // Сглаживание (lerp) для избежания дёргания
                self.amplitude = self.amplitude * 0.7 + min(rms * 10, 1.0) * 0.3
            }
        }
        try? audioEngine.start()
    }
}

В SwiftUI — связка:

struct CharacterContainer: View {
    @StateObject private var mic = MicrophoneMonitor()
    @State private var riveVM = RiveViewModel(fileName: "companion", stateMachineName: "Main")
    
    var body: some View {
        riveVM.view()
            .onChange(of: mic.amplitude) { newValue in
                riveVM.setInput("audioAmplitude", value: newValue)
            }
    }
}

2.5 Rive Pricing для Indie Разработчика

Тариф Цена Что важно
Free $0/мес Редактор бесплатно, нельзя экспортировать .riv файлы для production
Cadet $9/мес (до 3 сидений) Экспорт .riv файлов, неограниченные файлы, все State Machine фичи
Voyager $32/мес Libraries, Embed URL hosting, до 25 сидений
Enterprise $120/мес Для компаний $10M+ выручки

Вывод для indie: $9/мес (Cadet) — это всё что нужно для MVP и production. Free можно использовать для прототипирования. Дизайнер тоже работает на Cadet или тебе передаёт .riv файл.


2.6 Lottie vs Rive — Сравнение для Нашего Use Case

Критерий Lottie Rive
Интерактивность Только воспроизведение State Machine, inputs, data binding
Голосовая реактивность Невозможно нативно Number input → real-time
Размер файла ~50200KB ~20100KB (векторный .riv)
Производительность iOS Хорошая Отличная (собственный рендерер на Metal)
Workflow дизайнер After Effects → экспорт JSON Rive Editor (браузер/десктоп)
Стоимость для дизайнера AE нужен ($55/мес) Rive Cadet $9/мес
Обучение разработчика 12 часа 48 часов
Эмоциональные состояния Несколько файлов + код 1 файл + state machine
Settled state Экономия CPU
Итог для нашего проекта Не подходит Единственный правильный выбор

Вердикт: Lottie — для декоративных анимаций (иконки, сплэш-скрины). Rive — для интерактивных персонажей с состояниями. Наш use case = Rive без сомнений.


3. Четыре Концепта Персонажа

🟣 Концепт A: Дышащий Орб РЕКОМЕНДУЕМЫЙ

Визуал: Сфера диаметром ~120–160pt, светящаяся изнутри, с мягким bloom-эффектом. Базовый цвет — глубокий индиго (#3D2B8F), с внутренним свечением меняющим оттенок по состоянию.

5 состояний:

Состояние Визуал Цвет Поведение
Idle/Breathing Медленное расширение-сжатие (±8%), 4 сек цикл Индиго → тёмно-синий Почти незаметное «дыхание»
Listening Лёгкая пульсация (синхронно с голосом), размер +голос/амплитуда Синий → бирюзовый (#00C4CC) Активная реакция на звук
Thinking Медленное вращение внутренних частиц/градиента, ±5% размер Индиго → фиолетовый (#7B4FD4) Внутреннее движение, «обработка»
Speaking Ритмичные волны-пульсы, согласованные с TTS Тёплый фиолетовый (#9B59B6) Лёгкие ряби от центра
Happy Быстрое «прыжковое» расширение (+20%), возврат Тёплый персиковый (#FF6B6B → оранжевый) Burst-эффект
Concerned Медленное сжатие (-10%), глубже цвет Тёмно-синий (#1A237E) Серьёзность, не пугающая

Техническая реализация в Rive:

  1. Основная форма: эллипс с несколькими вложенными эллипсами (layered glow)
  2. Внутренний градиент: радиальный, управляется через Number input colorShift
  3. Размер: scale контролируется через audioAmplitude + дополнительные keyframe анимации для каждого состояния
  4. Bloom/glow: Vector Feathering в Rive (доступен с 2025) — нативный, без растра
  5. State machine: 1 Boolean isActive + 1 Number audioAmplitude + 1 Enum emotionState

SwiftUI реализация (если без Rive — чистый SwiftUI):

struct BreathingOrb: View {
    @State private var isPulsing = false
    @State private var audioAmplitude: CGFloat = 0
    var emotionColor: Color = .indigo
    
    var body: some View {
        ZStack {
            // Внешнее свечение
            Circle()
                .fill(emotionColor.opacity(0.15))
                .frame(width: 200 + audioAmplitude * 40)
                .blur(radius: 20)
            // Средний слой
            Circle()
                .fill(
                    RadialGradient(
                        colors: [emotionColor.opacity(0.8), emotionColor],
                        center: .center, startRadius: 10, endRadius: 70
                    )
                )
                .frame(width: 130 + audioAmplitude * 30)
                .scaleEffect(isPulsing ? 1.05 : 0.97)
            // Внутренняя яркость
            Circle()
                .fill(Color.white.opacity(0.3))
                .frame(width: 50)
                .offset(x: -15, y: -15)
                .blur(radius: 8)
        }
        .animation(.easeInOut(duration: 2).repeatForever(autoreverses: true), value: isPulsing)
        .onAppear { isPulsing = true }
    }
}

Эмоциональный тон: спокойствие, присутствие, безопасность. Пользователь ощущает, что рядом «кто-то» — но не человек, не робот. «Существо света».

Сложность реализации:

  • DIY SwiftUI: 4/10 (можно сделать за день)
  • Rive с дизайнером: 6/10 (1–2 недели работы дизайнера)

🟡 Концепт B: Поле Частиц

Визуал: 50–200 светящихся точек диаметром 2–6pt, формирующих разные паттерны в зависимости от состояния.

Состояния через паттерны:

  • Idle: медленное дрейфование, как звёзды
  • Listening: частицы притягиваются к центру, образуя «ухо»-форму
  • Thinking: хаотичное кружение в определённой зоне
  • Speaking: волнообразное движение от центра
  • Happy: взрыв наружу + возврат

Реализация голосовой реактивности:

// SpriteKit или SwiftUI Canvas
// audioAmplitude → силовое поле, отталкивающее частицы
// Каждая частица: позиция = basePosition + sin(time + index) * amplitude * 20

Сложность:

  • DIY SwiftUI Canvas/SpriteKit: 7/10
  • Rive: 8/10 (частицы в Rive нетривиальны, лучше через Scripting/Luau)
  • Metal: 5/10 при наличии знаний Metal Shaders

Плюсы: ощущение интеллекта, «нейронная сеть», уникально
Минусы: более сложная реализация, может отвлекать от рефлексии; плохо читается при нервозности пользователя


🟢 Концепт C: Флюидный Blob

Визуал: органическая морфирующая форма — как капля ртути или живая клетка. Постоянно деформируется, «перетекает».

Референсы в продуктах:

  • iOS 17 Dynamic Island — blob-морфинг для уведомлений → пользователи полюбили
  • Headspace «sleep sounds» анимации — флюидные формы воды
  • Adobe Creative Cloud онбординг (2021) — морфирующие blob-переходы

Реализация:

Вариант 1 — SwiftUI Canvas + CGPath:

// Генерация blob через control points
func blobPath(points: [CGPoint], smoothing: CGFloat) -> Path {
    // Catmull-Rom сплайны между точками
    // audioAmplitude → смещение контрольных точек
}

Вариант 2 — Metal Shader (лучший результат):

// Метаболы (metaballs) — классическая техника для blob
// distance = sqrt(dx*dx + dy*dy)
// field += radius*radius / distance
// Если field > threshold → рисуем пиксель

Вариант 3 — Rive с mesh deformation:

  • Rive поддерживает Meshes (деформация сетки) — дизайнер создаёт blob-форму, задаёт точки деформации, управляемые через inputs
  • Сложность в Rive: 8/10

Голосовая реактивность: amплитуда → displacement map → blob «вздрагивает» синхронно с речью

Плюсы: максимальная «живость», визуально уникален, метафора «жидкого интеллекта»
Минусы: сложная реализация без дизайнера, может вызывать тревогу (нестабильная форма ≠ спокойствие)

Сложность: DIY Metal: 8/10, DIY SwiftUI: 6/10, Rive с дизайнером: 7/10


🔵 Концепт D: Аудио-Волна

Визуал: waveform-визуализация, реагирующая на голос — как старый Siri (iOS 7–9) или осциллограф.

Варианты формы:

  • Классическая waveform (вертикальные полосы переменной высоты)
  • Синусоида (гладкая волна)
  • Круговая waveform (волна по окружности)
  • «Сердцебиение» (EKG-стиль)

SwiftUI реализация (круговая):

struct AudioWaveformView: View {
    var amplitudes: [Float]  // массив ~64 значений
    var color: Color = .purple
    
    var body: some View {
        Canvas { context, size in
            let center = CGPoint(x: size.width/2, y: size.height/2)
            let baseRadius: CGFloat = 60
            
            var path = Path()
            for (i, amp) in amplitudes.enumerated() {
                let angle = Double(i) / Double(amplitudes.count) * 2 * .pi
                let r = baseRadius + CGFloat(amp) * 40
                let x = center.x + cos(angle) * r
                let y = center.y + sin(angle) * r
                if i == 0 { path.move(to: CGPoint(x: x, y: y)) }
                else { path.addLine(to: CGPoint(x: x, y: y)) }
            }
            path.closeSubpath()
            context.fill(path, with: .color(color.opacity(0.7)))
            context.stroke(path, with: .color(color), lineWidth: 2)
        }
    }
}

Плюсы:

  • Максимально простая реализация (DIY: 2/10)
  • Мгновенная связь аудио → визуал понятна пользователю
  • Ощущение «технологичности»

Минусы для психологического контекста:

  • Холодный, технический — не тёплый
  • Siri давно переехал от waveform именно потому что она не вызывала эмпатии
  • Плохо передаёт «мышление» — только «говорение»/«слушание»
  • Нет визуального способа показать «обеспокоенность» или «радость»

Вывод: Концепт D хорош как дополнительный элемент (показывать waveform голоса пользователя внизу экрана), но НЕ как основной персонаж.


4. Поиск Motion Дизайнера

4.1 Где Искать

Платформа Специфика Ссылка
Rive Marketplace «For Hire» Дизайнеры специально рекламируют Rive-навыки rive.app/marketplace/for-hire
Twitter/X @rive_app Официальный аккаунт; в ответах и RT часто появляются авторы twitter.com/rive_app
Dribbble #rive Фильтр по тегу; смотри работы со state machine видео dribbble.com/tags/rive
Upwork Поиск «Rive animation designer»; ~$3080/час upwork.com
Toptal Топ 3% фрилансеров; $80–150/час; медленный онбординг toptal.com
Discord Rive Community #for-hire канал; сообщество ~15K человек discord.com/invite/FGjmaTr
Behance Поиск «Rive character animation»; не всегда указывают Rive явно behance.net
LinkedIn «Rive motion designer iOS» → фильтр по навыкам linkedin.com

Самый быстрый путь: Rive Discord #for-hire + Rive Marketplace For Hire


4.2 Шаблон Брифа для Motion Дизайнера

## Проект: AI-компаньон для приложения психологической рефлексии

### Контекст
Мобильное приложение (iOS/Android) для ежедневной психологической рефлексии с AI-компаньоном.
Аудитория: взрослые 25–45 лет, работают со стрессом, ищут поддержку.

### Задача
Разработать абстрактного анимированного персонажа — визуальное лицо AI-компаньона.

**НЕ**: человек, робот, животное.  
**ДА**: орб, blob, абстрактная форма — тёплая, живая, доверительная.

### Концепт (базовый)
[Концепт A: Дышащий орб] — светящаяся сфера ~140pt, с мягким bloom-эффектом.
Референсы: ChatGPT voice mode orb, Apple Siri (iOS 16), Headspace Andy (стиль — не копия).

### Состояния (5 обязательных + idle)
1. **Idle/Breathing** — медленное дыхание, базовый цвет (индиго/фиолетовый)
2. **Listening** — реактивный к амплитуде звука через Number input `audioAmplitude` (0.01.0)
3. **Thinking** — внутреннее движение, «обрабатываю»
4. **Speaking** — пульс/волны при TTS
5. **Happy/Positive** — тёплый burst
6. **Concerned/Serious** — сдержанность, глубокий цвет

### Технические требования
- **Инструмент**: Rive (обязательно)
- **Deliverable**: `.riv` файл с настроенной State Machine
- **State Machine inputs**:
  - `audioAmplitude` (Number, 0.01.0) — голосовая реактивность
  - `emotionState` (Enum: idle/listening/thinking/speaking/happy/concerned)
- **Размер артборда**: 300×300pt (векторный, масштабируемый)
- **Производительность**: файл должен работать в 60fps на iPhone 12 и новее
- Использовать Vector Feathering для glow (не PNG/растр)

### Что включает deliverable
- [ ] Концепт (2–3 варианта визуала) — для согласования
- [ ] Финальный `.riv` файл со state machine
- [ ] Короткое видео-демо всех состояний (GIF или MP4)
- [ ] Инструкция по inputs для разработчика (1 страница)

### Бюджет ориентировочно
$300–600 (уточняем после концепта)

### Сроки
Концепт: 3–5 дней  
Финальный файл: 7–14 дней после согласования

### Контакт / Портфолио
Прошу показать примеры работ с Rive State Machine (не просто Lottie/AE).

4.3 Реалистичная Стоимость

Уровень дизайнера Профиль Концепт + 5 состояний + .riv Сроки
Junior Rive Upwork, 1–2 года опыта $150300 1021 день
Mid-level Upwork/Dribbble, портфолио Rive $300600 sweet spot 714 дней
Senior/Студия Toptal, известные имена $8002000+ 1430 дней
Готовый шаблон Marketplace + кастомизация $050 + $100200 dev time 25 дней

Реалистичная цена для MVP: $300500 за полный пакет (концепт + 5 состояний + .riv файл)


4.4 Готовые Персонажи на Маркетплейсах

Rive Marketplace (rive.app/marketplace):

  • Теги для поиска: orb, blob, character, loader, mascot
  • Большинство файлов бесплатны для просмотра/ремикса с attribution
  • Некоторые продаются за $5–30

Рекомендуемый поиск:

  • site:rive.app/community orb animation
  • site:rive.app/community blob character state machine

LottieFiles (для Lottie) — не подходит для нашего interactive use case, но можно найти вдохновение: lottiefiles.com/search?q=orb+ai+character

Другие источники готовых Rive файлов:

  • awesome-rive: github.com/rive-app/awesome-rive — кураторский список
  • Gumroad + поиск «Rive character»
  • Creative Market + поиск «Rive animation»

5. DIY Вариант

5.1 Можно ли Без Дизайнера?

Да, и вот почему это разумно для TestFlight MVP:

  • Для бета-тестирования важно проверить концепцию взаимодействия, а не полировку
  • SwiftUI Canvas + TimelineView позволяет сделать «достаточно хороший» орб за 1–2 дня
  • После валидации концепции → нанять дизайнера для production-версии

5.2 Минимальная Версия для TestFlight

Что должна уметь (MVP):

  • Визуально отличаться в 3+ состояниях (idle, listening, speaking)
  • Реагировать на амплитуду микрофона (пульс/размер)
  • Плавные переходы между состояниями (withAnimation + ease)
  • Работать в 60fps без прыжков
  • Не выглядеть «сломанной» при любом сценарии

Что НЕ нужно для MVP:

  • Профессиональный Rive-файл
  • Все 6 эмоциональных состояний
  • Идеальный bloom/glow

5.3 Полный SwiftUI Орб — Структура Кода

import SwiftUI
import AVFoundation

// MARK: - Состояния персонажа
enum CompanionState {
    case idle, listening, thinking, speaking, happy, concerned
    
    var color: Color {
        switch self {
        case .idle:       return Color(hex: "#3D2B8F")  // индиго
        case .listening:  return Color(hex: "#00C4CC")  // бирюзовый
        case .thinking:   return Color(hex: "#7B4FD4")  // фиолетовый
        case .speaking:   return Color(hex: "#9B59B6")  // тёплый фиолетовый
        case .happy:      return Color(hex: "#FF6B6B")  // персиковый
        case .concerned:  return Color(hex: "#1A237E")  // тёмно-синий
        }
    }
    
    var pulseSpeed: Double {
        switch self {
        case .idle: return 3.0
        case .listening: return 1.5
        case .thinking: return 2.0
        case .speaking: return 0.8
        case .happy: return 0.5
        case .concerned: return 4.0
        }
    }
    
    var baseScale: CGFloat {
        switch self {
        case .idle: return 1.0
        case .listening: return 1.05
        case .thinking: return 0.97
        case .speaking: return 1.03
        case .happy: return 1.15
        case .concerned: return 0.92
        }
    }
}

// MARK: - Орб
struct CompanionOrbView: View {
    var state: CompanionState
    var audioAmplitude: Float  // 0.0...1.0
    
    @State private var isBreathing = false
    @State private var rotationAngle: Double = 0
    
    private let baseSize: CGFloat = 130
    
    var body: some View {
        let amplitudeBoost = CGFloat(audioAmplitude) * 35
        let currentSize = baseSize * state.baseScale + amplitudeBoost
        
        ZStack {
            // Слой 3: дальнее свечение
            Circle()
                .fill(state.color.opacity(0.08))
                .frame(width: currentSize + 70)
                .blur(radius: 30)
            
            // Слой 2: среднее свечение
            Circle()
                .fill(state.color.opacity(0.2))
                .frame(width: currentSize + 30)
                .blur(radius: 15)
            
            // Слой 1: основной орб
            Circle()
                .fill(
                    RadialGradient(
                        colors: [
                            state.color.opacity(0.6),
                            state.color,
                            state.color.opacity(0.85)
                        ],
                        center: .init(x: 0.35, y: 0.3),
                        startRadius: 5,
                        endRadius: currentSize / 2
                    )
                )
                .frame(width: currentSize, height: currentSize)
                .scaleEffect(isBreathing ? 1.04 : 0.97)
            
            // Внутренний блик (имитация 3D)
            Ellipse()
                .fill(
                    LinearGradient(
                        colors: [.white.opacity(0.4), .clear],
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    )
                )
                .frame(width: currentSize * 0.45, height: currentSize * 0.25)
                .offset(x: -currentSize * 0.12, y: -currentSize * 0.18)
                .blur(radius: 4)
            
            // Вращающийся внутренний элемент (для состояния "thinking")
            if state == .thinking {
                Circle()
                    .trim(from: 0, to: 0.3)
                    .stroke(state.color.opacity(0.5), lineWidth: 2)
                    .frame(width: currentSize * 0.6)
                    .rotationEffect(.degrees(rotationAngle))
            }
        }
        .animation(
            .easeInOut(duration: state.pulseSpeed).repeatForever(autoreverses: true),
            value: isBreathing
        )
        .animation(.easeInOut(duration: 0.4), value: state)
        .animation(.linear(duration: 0.05), value: audioAmplitude)
        .onAppear {
            isBreathing = true
            withAnimation(.linear(duration: 2).repeatForever(autoreverses: false)) {
                rotationAngle = 360
            }
        }
    }
}

// MARK: - Контейнер с микрофоном
struct CompanionContainer: View {
    @StateObject private var mic = MicrophoneMonitor()
    @State var companionState: CompanionState = .idle
    
    var body: some View {
        VStack {
            Spacer()
            CompanionOrbView(state: companionState, audioAmplitude: mic.amplitude)
                .frame(width: 220, height: 220)
            Spacer()
        }
        .background(Color(hex: "#0D0D1A"))  // тёмный фон
        .onAppear { mic.startMonitoring() }
    }
}

Оценка результата без дизайнера: 6.5/10 — выглядит профессионально для бета-тестирования при правильно подобранных цветах.


6. Сводная Таблица Концептов

Критерий A: Орб B: Частицы C: Blob D: Waveform
Эмоциональное тепло
DIY сложность 4/10 7/10 6/10 2/10
Голосовая реактивность Отлично Хорошо Хорошо Отлично
Rive реализация 6/10 8/10 7/10 3/10
Для психологии ⚠️
Уникальность Средняя Высокая Высокая Низкая
Стоимость дизайнера $300500 $400700 $400600 $150300

7. Рекомендация и Следующие Шаги

Рекомендованный Концепт: A (Дышащий Орб) с элементами C (Blob)

Почему:

  1. Орб — самый доказанный паттерн (Siri, ChatGPT, Alexa) — пользователи уже знают этот «язык»
  2. Добавление slight fluid-деформации (±blob) делает его уникальным и «живым»
  3. Лучший баланс тепла + технологичности для психологического контекста
  4. Реалистичен для DIY MVP
  5. Хорошо масштабируется до профессиональной Rive-версии

Цветовая палитра:

  • Base: #3D2B8F (тёмный индиго)
  • Listening: #00C4CC (бирюзовый)
  • Thinking: #7B4FD4 (фиолетовый)
  • Speaking: #9B59B6
  • Happy: #FF6B6B
  • Concerned: #1A237E
  • Background: #0D0D1A (почти чёрный)

Путь 1: DIY для TestFlight (Рекомендую НАЧАТЬ ЗДЕСЬ)

Что делать:

  1. Реализовать SwiftUI орб из кода выше (1–2 дня)
  2. Подключить AVAudioEngine (1 день)
  3. Добавить 3 состояния: idle / listening / speaking (1 день)
  4. Запустить TestFlight бета, собрать отзывы (2 недели)
  5. Решить — нанимать дизайнера или нет

Стоимость: $0 (только время разработчика)
Срок до TestFlight: ~57 дней


Путь 2: Hire Motion Designer (После Валидации Концепции)

Когда нанимать: после того как бета подтвердит что персонаж нужен и работает
Где искать: Rive Discord #for-hire → Rive Marketplace for-hire → Upwork

Бюджет: $400–600 за полный пакет
Что получить: концепт (2–3 варианта) + .riv файл + state machine + инструкция

Конкретные шаги:

  1. Написать бриф по шаблону выше
  2. Разместить в Rive Discord #for-hire
  3. Попросить показать работы именно с Rive State Machine
  4. Начать с концепта ($50–100 за концепт) → подтвердить → финальный файл

Инструменты и Ресурсы

Ресурс Ссылка
Rive Editor (бесплатно) editor.rive.app
Rive iOS Runtime github.com/rive-app/rive-ios
Rive Docs iOS rive.app/docs/runtimes/apple/apple
Rive Docs State Machine rive.app/docs/runtimes/state-machines
Rive Community Discord discord.com/invite/FGjmaTr
Rive Marketplace rive.app/marketplace
awesome-rive github.com/rive-app/awesome-rive
Видео: Build iOS App with Rive (DesignCode, 187K views) youtube.com/watch?v=h4vyOz4Tztg
Видео: Build Interactive Characters in Rive (67K views) youtube.com/watch?v=-vuQvvFiR8s
Видео: Glowing Jellyfish (blob референс) youtube.com/watch?v=4oH0jV-L-dU
Видео: Rive State Machine Tutorial youtube.com/watch?v=oDhdGmXco00

Документ создан агентом исследования. Обновить после TestFlight бета (2 недели).