import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/divination_layout.dart'; import '../models/history_entry_model.dart'; import '../models/syncretic_session_model.dart'; import '../models/tradition_model.dart'; import '../models/word_model.dart'; import '../services/ai_oracle_service.dart'; import '../services/traditions_repository.dart'; import '../services/words_repository.dart'; import '../widgets/divination_instruction_modal.dart'; import 'symbol_detail_screen.dart'; import '../services/oracle_master_prompt_generator.dart'; class GeomancyFigure { final String id; final String fullName; final String latinName; final String arabicName; final String russianName; final List lines; // 4 rows: 1 or 2 dots (Fire, Air, Water, Earth) final String element; final String planet; final String group; final String lore; final int? primaryWordId; final TraditionStation station; const GeomancyFigure({ required this.id, required this.fullName, required this.latinName, required this.arabicName, required this.russianName, required this.lines, required this.element, required this.planet, required this.group, required this.lore, this.primaryWordId, required this.station, }); String get signature => lines.join(','); int get totalDots => lines.reduce((a, b) => a + b); bool get isEven => totalDots % 2 == 0; static List combineLines(List a, List b) { return List.generate(4, (i) => (a[i] + b[i]) % 2 == 0 ? 2 : 1); } } class GeomancyDivinationScreen extends StatefulWidget { final String? initialQuestion; const GeomancyDivinationScreen({super.key, this.initialQuestion}); @override State createState() => _GeomancyDivinationScreenState(); } class _GeomancyDivinationScreenState extends State with SingleTickerProviderStateMixin { late TabController _tabController; final _questionController = TextEditingController(); final _repo = WordsRepository(); final _aiService = AiOracleService(); List _allFigures = []; final Map _figureBySig = {}; DivinationLayout _selectedLayout = DivinationLayouts.geomancyLayouts[0]; // Sand animation / casting state bool _isCasting = false; List _currentSandDotCounts = [0, 0, 0, 0]; // Drawn results for 1-figure and 3-figure layouts final List<({GeomancyFigure figure, String positionTitle})> _drawnResults = []; // Shield state (4 Mothers cast, others derived) final List _shieldMothers = []; List _shieldDaughters = []; List _shieldNephews = []; GeomancyFigure? _shieldRightWitness; GeomancyFigure? _shieldLeftWitness; GeomancyFigure? _shieldJudge; GeomancyFigure? _shieldReconciler; GeomancyFigure? _inspectedShieldFigure; String? _inspectedShieldTitle; bool _savedToSession = false; bool _isAiLoading = false; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); if (widget.initialQuestion != null) { _questionController.text = widget.initialQuestion!; } else { final q = _repo.currentSyncreticSession.mainQuestion; if (q != null && q.trim().isNotEmpty) { _questionController.text = q.trim(); } } _loadGeomancyFigures(); } void _loadGeomancyFigures() { final canon = TraditionsRepository().byId('geomancy'); if (canon == null) return; final parsed = []; for (final st in canon.stations) { final rawLines = st.symbol.split(RegExp(r'[\r\n]+')); final lines = []; for (final l in rawLines) { final clean = l.replaceAll(' ', '').trim(); if (clean.isNotEmpty) { lines.add(clean.length); } } if (lines.length != 4) continue; String latin = st.id; String arabic = ''; String russian = ''; final m = RegExp(r'^\d*\s*([A-Za-z\s]+)\s*\(([^/]+)/([^)]+)\)').firstMatch(st.name); if (m != null) { latin = m.group(1)?.trim() ?? st.id; arabic = m.group(2)?.trim() ?? ''; russian = m.group(3)?.trim() ?? ''; } else { latin = st.name; } final parts = st.group.split('·').map((p) => p.trim()).toList(); final element = parts.length > 1 ? parts[1] : 'Земля'; final planet = parts.length > 2 ? parts[2] : 'Марс'; final fig = GeomancyFigure( id: st.id, fullName: st.name, latinName: latin, arabicName: arabic, russianName: russian.isNotEmpty ? russian : latin, lines: lines, element: element, planet: planet, group: st.group, lore: st.lore, primaryWordId: st.primary?.id, station: st, ); parsed.add(fig); _figureBySig[fig.signature] = fig; } setState(() { _allFigures = parsed; }); } @override void dispose() { _tabController.dispose(); _questionController.dispose(); super.dispose(); } void _onLayoutChanged(DivinationLayout layout) { setState(() { _selectedLayout = layout; _reset(); }); HapticFeedback.selectionClick(); } void _reset() { setState(() { _drawnResults.clear(); _shieldMothers.clear(); _shieldDaughters.clear(); _shieldNephews.clear(); _shieldRightWitness = null; _shieldLeftWitness = null; _shieldJudge = null; _shieldReconciler = null; _inspectedShieldFigure = null; _inspectedShieldTitle = null; _savedToSession = false; _currentSandDotCounts = [0, 0, 0, 0]; }); } void _deriveShield() { if (_shieldMothers.length != 4) return; final m1 = _shieldMothers[0]; final m2 = _shieldMothers[1]; final m3 = _shieldMothers[2]; final m4 = _shieldMothers[3]; // 4 Daughters: final d1Sig = [m1.lines[0], m2.lines[0], m3.lines[0], m4.lines[0]].join(','); final d2Sig = [m1.lines[1], m2.lines[1], m3.lines[1], m4.lines[1]].join(','); final d3Sig = [m1.lines[2], m2.lines[2], m3.lines[2], m4.lines[2]].join(','); final d4Sig = [m1.lines[3], m2.lines[3], m3.lines[3], m4.lines[3]].join(','); final d1 = _figureBySig[d1Sig] ?? m1; final d2 = _figureBySig[d2Sig] ?? m2; final d3 = _figureBySig[d3Sig] ?? m3; final d4 = _figureBySig[d4Sig] ?? m4; _shieldDaughters = [d1, d2, d3, d4]; // 4 Nephews: // N1 = M1 + M2, N2 = M3 + M4, N3 = D1 + D2, N4 = D3 + D4 final n1Sig = GeomancyFigure.combineLines(m1.lines, m2.lines).join(','); final n2Sig = GeomancyFigure.combineLines(m3.lines, m4.lines).join(','); final n3Sig = GeomancyFigure.combineLines(d1.lines, d2.lines).join(','); final n4Sig = GeomancyFigure.combineLines(d3.lines, d4.lines).join(','); final n1 = _figureBySig[n1Sig] ?? m1; final n2 = _figureBySig[n2Sig] ?? m2; final n3 = _figureBySig[n3Sig] ?? d1; final n4 = _figureBySig[n4Sig] ?? d2; _shieldNephews = [n1, n2, n3, n4]; // 2 Witnesses: // Right Witness (W1) = N1 + N2 (outer situation/querent's question) // Left Witness (W2) = N3 + N4 (inner soul/querent themselves) final w1Sig = GeomancyFigure.combineLines(n1.lines, n2.lines).join(','); final w2Sig = GeomancyFigure.combineLines(n3.lines, n4.lines).join(','); _shieldRightWitness = _figureBySig[w1Sig] ?? n1; _shieldLeftWitness = _figureBySig[w2Sig] ?? n2; // Judge = W1 + W2 final jSig = GeomancyFigure.combineLines(_shieldRightWitness!.lines, _shieldLeftWitness!.lines).join(','); _shieldJudge = _figureBySig[jSig] ?? _shieldRightWitness!; // Reconciler = Judge + M1 final rSig = GeomancyFigure.combineLines(_shieldJudge!.lines, m1.lines).join(','); _shieldReconciler = _figureBySig[rSig] ?? _shieldJudge!; _inspectedShieldFigure = _shieldJudge; _inspectedShieldTitle = 'Судья (Итоговое Разрешение)'; } void _addFigureToSpread(GeomancyFigure fig) { if (_selectedLayout.id == 'geomancy_shield') { if (_shieldMothers.length < 4) { final completed = (_shieldMothers.length + 1 == 4); setState(() { _shieldMothers.add(fig); if (completed) { _deriveShield(); } }); if (completed) { WidgetsBinding.instance.addPostFrameCallback((_) { _saveToSyncreticSession(silent: false); }); } } } else { if (_drawnResults.length < _selectedLayout.count) { final completed = (_drawnResults.length + 1 == _selectedLayout.count); final posTitle = _selectedLayout.positionTitles[_drawnResults.length]; setState(() { _drawnResults.add((figure: fig, positionTitle: posTitle)); }); if (completed) { WidgetsBinding.instance.addPostFrameCallback((_) { _saveToSyncreticSession(silent: false); }); } } } } Future _performSandRitualCast() async { if (_isCasting) return; final isShield = _selectedLayout.id == 'geomancy_shield'; final isComplete = isShield ? _shieldMothers.length >= 4 : _drawnResults.length >= _selectedLayout.count; if (isComplete) return; setState(() { _isCasting = true; _currentSandDotCounts = [0, 0, 0, 0]; }); final rng = Random(); final counts = []; final lines = []; for (int row = 0; row < 4; row++) { HapticFeedback.lightImpact(); final dotCount = 7 + rng.nextInt(15); counts.add(dotCount); lines.add(dotCount % 2 == 1 ? 1 : 2); setState(() { _currentSandDotCounts = List.from(counts)..addAll(List.filled(4 - counts.length, 0)); }); await Future.delayed(const Duration(milliseconds: 140)); } final sig = lines.join(','); final fig = _figureBySig[sig] ?? _allFigures[rng.nextInt(_allFigures.length)]; HapticFeedback.mediumImpact(); setState(() { _isCasting = false; _addFigureToSpread(fig); }); } Future _castAllRemainingSteps() async { final isShield = _selectedLayout.id == 'geomancy_shield'; final target = isShield ? 4 : _selectedLayout.count; final current = isShield ? _shieldMothers.length : _drawnResults.length; final remaining = target - current; for (int i = 0; i < remaining; i++) { await _performSandRitualCast(); await Future.delayed(const Duration(milliseconds: 120)); } } WordItem? _resolveLinkedWord(int? wordId) { if (wordId == null) return null; return _repo.allWords.firstWhere( (w) => w.sessionNumber == wordId, orElse: () => _repo.allWords.firstWhere( (w) => w.id == wordId, orElse: () => _repo.allWords.first, ), ); } Future _saveToSyncreticSession({bool silent = false}) async { final isShield = _selectedLayout.id == 'geomancy_shield'; final items = []; if (isShield) { if (_shieldMothers.length < 4 || _shieldJudge == null) return; for (int i = 0; i < 4; i++) { final m = _shieldMothers[i]; final word = _resolveLinkedWord(m.primaryWordId); items.add(ReadingSignItem( title: '${i + 1}-я Мать: ${m.fullName}', subtitle: '${m.element} • ${m.planet}', symbol: m.station.symbol, lore: m.lore, linkedWord: word, )); } for (int i = 0; i < 4; i++) { final d = _shieldDaughters[i]; final word = _resolveLinkedWord(d.primaryWordId); items.add(ReadingSignItem( title: '${i + 1}-я Дочь: ${d.fullName}', subtitle: '${d.element} • ${d.planet}', symbol: d.station.symbol, lore: d.lore, linkedWord: word, )); } for (int i = 0; i < 4; i++) { final n = _shieldNephews[i]; final word = _resolveLinkedWord(n.primaryWordId); items.add(ReadingSignItem( title: '${i + 1}-й Племянник: ${n.fullName}', subtitle: '${n.element} • ${n.planet}', symbol: n.station.symbol, lore: n.lore, linkedWord: word, )); } if (_shieldRightWitness != null) { items.add(ReadingSignItem( title: 'Правый Свидетель (Внешнее): ${_shieldRightWitness!.fullName}', subtitle: '${_shieldRightWitness!.element} • ${_shieldRightWitness!.planet}', symbol: _shieldRightWitness!.station.symbol, lore: _shieldRightWitness!.lore, linkedWord: _resolveLinkedWord(_shieldRightWitness!.primaryWordId), )); } if (_shieldLeftWitness != null) { items.add(ReadingSignItem( title: 'Левый Свидетель (Внутреннее): ${_shieldLeftWitness!.fullName}', subtitle: '${_shieldLeftWitness!.element} • ${_shieldLeftWitness!.planet}', symbol: _shieldLeftWitness!.station.symbol, lore: _shieldLeftWitness!.lore, linkedWord: _resolveLinkedWord(_shieldLeftWitness!.primaryWordId), )); } items.add(ReadingSignItem( title: 'СУДЬЯ (Итог): ${_shieldJudge!.fullName}', subtitle: '${_shieldJudge!.element} • ${_shieldJudge!.planet}', symbol: _shieldJudge!.station.symbol, lore: _shieldJudge!.lore, linkedWord: _resolveLinkedWord(_shieldJudge!.primaryWordId), )); if (_shieldReconciler != null) { items.add(ReadingSignItem( title: 'Примиритель: ${_shieldReconciler!.fullName}', subtitle: '${_shieldReconciler!.element} • ${_shieldReconciler!.planet}', symbol: _shieldReconciler!.station.symbol, lore: _shieldReconciler!.lore, linkedWord: _resolveLinkedWord(_shieldReconciler!.primaryWordId), )); } } else { for (final r in _drawnResults) { final word = _resolveLinkedWord(r.figure.primaryWordId); items.add(ReadingSignItem( title: '${r.positionTitle}: ${r.figure.fullName}', subtitle: '${r.figure.element} • ${r.figure.planet}', symbol: r.figure.station.symbol, lore: r.figure.lore, linkedWord: word, )); } } final reading = SyncreticReading( id: 'reading_${DateTime.now().millisecondsSinceEpoch}', systemId: 'geomancy', systemName: "Арабская Геомантия ('Илм ар-рамл)", systemIcon: '⌛', timestamp: DateTime.now(), layoutTitle: _selectedLayout.title, inputMethod: _tabController.index == 0 ? 'sand_ritual' : 'manual_selection', items: items, question: _questionController.text.trim(), ); await _repo.addReadingToSyncreticSession(reading, question: _questionController.text.trim()); if (mounted) { setState(() => _savedToSession = true); if (!silent) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(isShield ? 'Великий Щит Геомантии (16 фигур) сохранён в сессию дня!' : 'Расклад геомантии (${items.length} фигур) сохранён в сессию дня!'), backgroundColor: const Color(0xFF2E7D32), behavior: SnackBarBehavior.floating, duration: const Duration(seconds: 3), ), ); } } } void _copySpreadSummary() { final buffer = StringBuffer(); buffer.writeln("=== АРАБСКАЯ ГЕОМАНТИЯ ('ИЛМ АР-РАМЛ) ==="); if (_questionController.text.trim().isNotEmpty) { buffer.writeln('Вопрос: «${_questionController.text.trim()}»'); } buffer.writeln('Расклад: ${_selectedLayout.title}\n'); final isShield = _selectedLayout.id == 'geomancy_shield'; if (isShield) { buffer.writeln('СУДЬЯ: ${_shieldJudge?.fullName ?? "—"}'); buffer.writeln('ПРИМИРИТЕЛЬ: ${_shieldReconciler?.fullName ?? "—"}'); buffer.writeln('Правый свидетель (Внешнее): ${_shieldRightWitness?.fullName ?? "—"}'); buffer.writeln('Левый свидетель (Внутреннее): ${_shieldLeftWitness?.fullName ?? "—"}\n'); buffer.writeln('4 МАТЕРИ:'); for (int i = 0; i < _shieldMothers.length; i++) { buffer.writeln(' М${i + 1}: ${_shieldMothers[i].fullName}'); } buffer.writeln('\n4 ДОЧЕРИ:'); for (int i = 0; i < _shieldDaughters.length; i++) { buffer.writeln(' Д${i + 1}: ${_shieldDaughters[i].fullName}'); } buffer.writeln('\n4 ПЛЕМЯННИКА:'); for (int i = 0; i < _shieldNephews.length; i++) { buffer.writeln(' П${i + 1}: ${_shieldNephews[i].fullName}'); } } else { for (final r in _drawnResults) { buffer.writeln('${r.positionTitle}: ${r.figure.fullName} (${r.figure.element} • ${r.figure.planet})'); buffer.writeln(' Смысл: ${r.figure.lore}'); } } Clipboard.setData(ClipboardData(text: buffer.toString())); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Расклад геомантии скопирован в буфер обмена!'), behavior: SnackBarBehavior.floating, ), ); } String _buildMasterPrompt() { final isShield = _selectedLayout.id == 'geomancy_shield'; if (isShield) { GeomancyPromptFigure toPromptFigure(GeomancyFigure f, String pos) { return GeomancyPromptFigure( position: pos, name: f.fullName, latinName: f.latinName, arabicName: f.arabicName, lines: f.lines, element: f.element, planet: f.planet, lore: f.lore, linkedWord: _resolveLinkedWord(f.primaryWordId), ); } return OracleMasterPromptGenerator.generateGeomancyMasterPrompt( question: _questionController.text.trim(), layoutTitle: _selectedLayout.title, isShield: true, mothers: List.generate( _shieldMothers.length, (i) => toPromptFigure(_shieldMothers[i], 'Мать #' + (i + 1).toString()), ), daughters: List.generate( _shieldDaughters.length, (i) => toPromptFigure(_shieldDaughters[i], 'Дочь #' + (i + 1).toString()), ), nephews: List.generate( _shieldNephews.length, (i) => toPromptFigure(_shieldNephews[i], 'Племянник #' + (i + 1).toString()), ), rightWitness: _shieldRightWitness != null ? toPromptFigure(_shieldRightWitness!, 'Правый Свидетель') : null, leftWitness: _shieldLeftWitness != null ? toPromptFigure(_shieldLeftWitness!, 'Левый Свидетель') : null, judge: _shieldJudge != null ? toPromptFigure(_shieldJudge!, 'Верховный Судья') : null, reconciler: _shieldReconciler != null ? toPromptFigure(_shieldReconciler!, 'Примиритель') : null, ); } else { final figures = _drawnResults.map((r) { return GeomancyPromptFigure( position: r.positionTitle, name: r.figure.fullName, latinName: r.figure.latinName, arabicName: r.figure.arabicName, lines: r.figure.lines, element: r.figure.element, planet: r.figure.planet, lore: r.figure.lore, linkedWord: _resolveLinkedWord(r.figure.primaryWordId), ); }).toList(); return OracleMasterPromptGenerator.generateGeomancyMasterPrompt( question: _questionController.text.trim(), layoutTitle: _selectedLayout.title, isShield: false, figures: figures, ); } } void _showMasterPromptModal() { final prompt = _buildMasterPrompt(); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF100E19), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (ctx) => DraggableScrollableSheet( initialChildSize: 0.8, minChildSize: 0.4, maxChildSize: 0.95, expand: false, builder: (_, scrollController) => Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: const Color(0xFFFFD54F).withValues(alpha: 0.4), borderRadius: BorderRadius.circular(2), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Row( children: [ Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F), size: 24), SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Мастер-Промпт Геомантии ('Илм ар-рамл)", style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), ), Text( 'Для Grok, Claude 3.5 Sonnet, Gemini 1.5 Pro, GPT-4o', style: TextStyle(color: Color(0xFF90A4AE), fontSize: 11), ), ], ), ], ), IconButton( icon: const Icon(Icons.close_rounded, color: Color(0xFF90A4AE)), onPressed: () => Navigator.pop(ctx), ), ], ), const SizedBox(height: 12), Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF2A2338), borderRadius: BorderRadius.circular(8), ), child: Text( '${prompt.length} символов • ${prompt.split(RegExp(r"\s+")).length} слов', style: const TextStyle(color: Color(0xFFFFD54F), fontSize: 11, fontWeight: FontWeight.bold), ), ), const Spacer(), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), ), onPressed: () { Clipboard.setData(ClipboardData(text: prompt)); HapticFeedback.heavyImpact(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Мастер-промпт геомантии скопирован! Вставьте в Grok или Claude.'), backgroundColor: Color(0xFF388E3C), behavior: SnackBarBehavior.floating, ), ); }, icon: const Icon(Icons.copy_rounded, size: 16), label: const Text('Скопировать всё', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12.5)), ), ], ), const SizedBox(height: 12), Expanded( child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF07060B), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFF2A2338), width: 1.0), ), child: SingleChildScrollView( controller: scrollController, child: SelectableText( prompt, style: const TextStyle( color: Color(0xFFECEFF1), fontFamily: 'monospace', fontSize: 12.5, height: 1.45, ), ), ), ), ), ], ), ), ), ); } Future _requestAiInterpretation() async { final isShield = _selectedLayout.id == 'geomancy_shield'; final words = []; if (isShield) { if (_shieldJudge == null) return; final jWord = _resolveLinkedWord(_shieldJudge!.primaryWordId); if (jWord != null) { words.add(SessionWord.fromWordItem(jWord, positionTitle: 'Судья: ${_shieldJudge!.fullName}')); } if (_shieldRightWitness != null) { final wWord = _resolveLinkedWord(_shieldRightWitness!.primaryWordId); if (wWord != null) { words.add(SessionWord.fromWordItem(wWord, positionTitle: 'Правый Свидетель: ${_shieldRightWitness!.fullName}')); } } if (_shieldLeftWitness != null) { final wWord = _resolveLinkedWord(_shieldLeftWitness!.primaryWordId); if (wWord != null) { words.add(SessionWord.fromWordItem(wWord, positionTitle: 'Левый Свидетель: ${_shieldLeftWitness!.fullName}')); } } for (int i = 0; i < _shieldMothers.length; i++) { final mWord = _resolveLinkedWord(_shieldMothers[i].primaryWordId); if (mWord != null) { words.add(SessionWord.fromWordItem(mWord, positionTitle: 'Мать #${i + 1}: ${_shieldMothers[i].fullName}')); } } } else { for (final r in _drawnResults) { final word = _resolveLinkedWord(r.figure.primaryWordId); if (word != null) { words.add(SessionWord.fromWordItem(word, positionTitle: '${r.positionTitle}: ${r.figure.fullName}')); } } } if (words.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Сначала сформируйте расклад геомантии!')), ); return; } setState(() => _isAiLoading = true); try { final q = _questionController.text.trim().isNotEmpty ? _questionController.text.trim() : 'Срез геомантического расклада: ключевые векторы стихий и вердикт Судьи.'; final promptText = StringBuffer(); promptText.writeln("РАСКЛАД АРАБСКОЙ ГЕОМАНТИИ ('ИЛМ АР-РАМЛ):"); promptText.writeln('Расклад: ${_selectedLayout.title}'); if (isShield) { promptText.writeln('Вердикт Судьи: ${_shieldJudge?.fullName} (${_shieldJudge?.element}, ${_shieldJudge?.planet})'); promptText.writeln('Правый свидетель (Внешний мир): ${_shieldRightWitness?.fullName}'); promptText.writeln('Левый свидетель (Внутренний мир): ${_shieldLeftWitness?.fullName}'); promptText.writeln('Примиритель: ${_shieldReconciler?.fullName}'); } promptText.writeln('\nДай сакральный синтез песка: как стихии взаимодействуют, где конфликт или поддержка, и каков главный совет геомантии.'); final interpretation = await _aiService.interpretSession( words: words, question: '$q\n\n$promptText', ); if (mounted) { setState(() => _isAiLoading = false); _showAiInterpretationModal(interpretation); } } catch (e) { if (mounted) { setState(() => _isAiLoading = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Ошибка ИИ-толкования: $e')), ); } } } void _showAiInterpretationModal(String text) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: const Color(0xFF121018), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (ctx) => DraggableScrollableSheet( initialChildSize: 0.8, minChildSize: 0.4, maxChildSize: 0.95, expand: false, builder: (_, scroll) => Container( padding: const EdgeInsets.fromLTRB(20, 12, 20, 24), child: ListView( controller: scroll, children: [ Center( child: Container( width: 44, height: 4, decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(2), ), ), ), const SizedBox(height: 14), const Row( children: [ Text('⌛', style: TextStyle(fontSize: 22)), SizedBox(width: 8), Text( 'Толкование Песка (ИИ-Оракул)', style: TextStyle( color: Color(0xFFFFD54F), fontSize: 17, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 14), SelectableText( text, style: const TextStyle(color: Color(0xFFECEFF1), fontSize: 14, height: 1.5), ), ], ), ), ), ); } Widget _buildDotsVisual(List lines, {double dotSize = 9, double spacing = 6, Color? color}) { final activeColor = color ?? const Color(0xFFFFD54F); return Column( mainAxisSize: MainAxisSize.min, children: lines.map((count) { return Padding( padding: EdgeInsets.symmetric(vertical: spacing / 2), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: List.generate(count, (idx) { return Container( margin: EdgeInsets.symmetric(horizontal: count == 2 ? 5 : 0), width: dotSize, height: dotSize, decoration: BoxDecoration( shape: BoxShape.circle, color: activeColor, boxShadow: [ BoxShadow( color: activeColor.withValues(alpha: 0.4), blurRadius: 4, spreadRadius: 1, ), ], ), ); }), ), ); }).toList(), ); } Widget _buildLayoutSelector() { return Container( decoration: BoxDecoration( color: const Color(0xFF14121E), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.2)), ), padding: const EdgeInsets.all(4), child: Row( children: DivinationLayouts.geomancyLayouts.map((layout) { final isSelected = _selectedLayout.id == layout.id; return Expanded( child: InkWell( onTap: () => _onLayoutChanged(layout), borderRadius: BorderRadius.circular(12), child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), decoration: BoxDecoration( color: isSelected ? const Color(0xFF332616) : Colors.transparent, borderRadius: BorderRadius.circular(12), border: Border.all( color: isSelected ? const Color(0xFFFFD54F) : Colors.transparent, ), ), child: Column( children: [ Text( layout.title.split('(').first.trim(), textAlign: TextAlign.center, style: TextStyle( color: isSelected ? const Color(0xFFFFD54F) : const Color(0xFF8C93B8), fontSize: 12, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), Text( layout.id == 'geomancy_1' ? '1 знак' : (layout.id == 'geomancy_3' ? '3 знака' : '16 знаков'), style: TextStyle( color: isSelected ? const Color(0xFFFFE082) : const Color(0xFF61688B), fontSize: 10, ), ), ], ), ), ), ); }).toList(), ), ); } Widget _buildSandRitualTab() { final isShield = _selectedLayout.id == 'geomancy_shield'; final currentCount = isShield ? _shieldMothers.length : _drawnResults.length; final totalCount = isShield ? 4 : _selectedLayout.count; final isComplete = currentCount >= totalCount; String stepLabel; if (isShield) { stepLabel = isComplete ? 'Все 4 Матери нанесены! Щит рассчитан.' : 'Нанесение ${currentCount + 1}-й Матери (${_selectedLayout.positionTitles[currentCount]})'; } else { stepLabel = isComplete ? 'Расклад сформирован!' : 'Фигура ${currentCount + 1} из $totalCount: ${_selectedLayout.positionTitles[currentCount]}'; } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: const Color(0xFF1E1A29), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.3)), ), child: Row( children: [ const Text('⏳', style: TextStyle(fontSize: 16)), const SizedBox(width: 8), Expanded( child: Text( stepLabel, style: const TextStyle( color: Color(0xFFFFD54F), fontSize: 12, fontWeight: FontWeight.bold, ), ), ), Text( '$currentCount/$totalCount', style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 11, fontWeight: FontWeight.bold), ), ], ), ), const SizedBox(height: 14), // Interactive Sand Box Container( height: 180, padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF241C16), Color(0xFF15101A)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), border: Border.all( color: _isCasting ? const Color(0xFFFFD54F) : const Color(0xFF8D6E63).withValues(alpha: 0.5), width: _isCasting ? 2 : 1, ), boxShadow: [ BoxShadow( color: const Color(0xFFFFB74D).withValues(alpha: 0.08), blurRadius: 16, ), ], ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: List.generate(4, (rowIdx) { final rowNames = ['Огонь (Голова)', 'Воздух (Шея)', 'Вода (Тело)', 'Земля (Стопы)']; final count = _currentSandDotCounts[rowIdx]; final parity = count == 0 ? null : (count % 2 == 1 ? 1 : 2); return Row( children: [ SizedBox( width: 95, child: Text( rowNames[rowIdx], style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 10, fontWeight: FontWeight.bold), ), ), Expanded( child: Container( height: 24, alignment: Alignment.center, decoration: BoxDecoration( color: const Color(0xFF0F0C14), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.white10), ), child: count == 0 ? const Text('••• песок •••', style: TextStyle(color: Colors.white24, fontSize: 10)) : Text( '${"· " * min(count, 12)}${count > 12 ? "..." : ""} ($count)', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFFFFD54F), fontSize: 11, letterSpacing: 2), ), ), ), const SizedBox(width: 10), Container( width: 44, height: 24, alignment: Alignment.center, decoration: BoxDecoration( color: parity == null ? Colors.transparent : (parity == 1 ? const Color(0xFF3E2723) : const Color(0xFF263238)), borderRadius: BorderRadius.circular(6), border: Border.all( color: parity == null ? Colors.white12 : (parity == 1 ? const Color(0xFFFFB74D) : const Color(0xFF80DEEA)), ), ), child: Text( parity == null ? '—' : (parity == 1 ? '· (1)' : '·· (2)'), style: TextStyle( color: parity == 1 ? const Color(0xFFFFD54F) : const Color(0xFF80DEEA), fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ], ); }), ), ), const SizedBox(height: 14), // Casting Action Buttons Row( children: [ Expanded( flex: 3, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFFD54F), foregroundColor: Colors.black, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), onPressed: (isComplete || _isCasting) ? null : _performSandRitualCast, icon: _isCasting ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black), ) : const Icon(Icons.grain_rounded, size: 20), label: Text( _isCasting ? 'Песок осыпается...' : (isComplete ? 'Расклад заполнен' : 'Нанести точки на песок'), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), ), ), ), if (!isComplete) ...[ const SizedBox(width: 8), Expanded( flex: 2, child: OutlinedButton( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFFFFD54F), side: const BorderSide(color: Color(0xFFFFD54F)), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), onPressed: _isCasting ? null : _castAllRemainingSteps, child: const Text('Все сразу', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)), ), ), ], ], ), ], ); } Widget _buildManualCatalogTab() { final isShield = _selectedLayout.id == 'geomancy_shield'; final currentCount = isShield ? _shieldMothers.length : _drawnResults.length; final totalCount = isShield ? 4 : _selectedLayout.count; final isComplete = currentCount >= totalCount; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( isComplete ? 'Все позиции заполнены. Нажмите «Сброс» для нового расклада.' : 'Выберите фигуру для: ${isShield ? "Матери #${currentCount + 1}" : _selectedLayout.positionTitles[currentCount]}', style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 12), ), const SizedBox(height: 10), GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 1.8, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemCount: _allFigures.length, itemBuilder: (ctx, idx) { final fig = _allFigures[idx]; return InkWell( onTap: isComplete ? null : () => _addFigureToSpread(fig), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: const Color(0xFF14121E), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.2)), ), child: Row( children: [ _buildDotsVisual(fig.lines, dotSize: 7, spacing: 4), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( fig.russianName, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color(0xFFFFD54F), fontSize: 12, fontWeight: FontWeight.bold, ), ), Text( fig.latinName, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 10), ), const SizedBox(height: 2), Text( '${fig.element} • ${fig.planet}', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFF80DEEA), fontSize: 9.5), ), ], ), ), ], ), ), ); }, ), ], ); } Widget _buildShieldFigureBadge({ required GeomancyFigure? figure, required String label, required String role, bool isSupreme = false, }) { final isSelected = figure != null && _inspectedShieldFigure?.signature == figure.signature; return InkWell( onTap: figure == null ? null : () { setState(() { _inspectedShieldFigure = figure; _inspectedShieldTitle = '$role: ${figure.russianName}'; }); HapticFeedback.selectionClick(); }, borderRadius: BorderRadius.circular(12), child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), decoration: BoxDecoration( color: isSupreme ? const Color(0xFF332211) : (isSelected ? const Color(0xFF2A2038) : const Color(0xFF14121E)), borderRadius: BorderRadius.circular(12), border: Border.all( color: isSupreme ? const Color(0xFFFFD54F) : (isSelected ? const Color(0xFF80DEEA) : const Color(0xFFFFD54F).withValues(alpha: 0.25)), width: isSupreme ? 1.5 : 1.0, ), boxShadow: isSupreme ? [ BoxShadow( color: const Color(0xFFFFD54F).withValues(alpha: 0.2), blurRadius: 8, ) ] : null, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( label, style: TextStyle( color: isSupreme ? const Color(0xFFFFD54F) : const Color(0xFF8C93B8), fontSize: 9, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), if (figure != null) ...[ _buildDotsVisual(figure.lines, dotSize: 5, spacing: 3), const SizedBox(height: 4), Text( figure.russianName, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: isSupreme ? const Color(0xFFFFD54F) : Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ] else ...[ const Text('···', style: TextStyle(color: Colors.white24, fontSize: 10)), ], ], ), ), ); } Widget _buildShieldHierarchyView() { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF0F0D16), borderRadius: BorderRadius.circular(18), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.3)), ), child: Column( children: [ const Text( 'ВЕЛИКИЙ ЩИТ ГЕОМАНТИИ', style: TextStyle( color: Color(0xFFFFD54F), fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 1.2, ), ), const SizedBox(height: 10), // Row 1: Judge & Reconciler Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( flex: 4, child: _buildShieldFigureBadge( figure: _shieldJudge, label: 'СУДЬЯ', role: 'Судья (Вердикт)', isSupreme: true, ), ), const SizedBox(width: 8), Expanded( flex: 3, child: _buildShieldFigureBadge( figure: _shieldReconciler, label: 'ПРИМИРИТЕЛЬ', role: 'Примиритель (Судья + М1)', ), ), ], ), const SizedBox(height: 8), // Row 2: Witnesses (Right & Left) Row( children: [ Expanded( child: _buildShieldFigureBadge( figure: _shieldLeftWitness, label: 'ЛЕВЫЙ СВИДЕТЕЛЬ', role: 'Левый Свидетель (Внутреннее)', ), ), const SizedBox(width: 8), Expanded( child: _buildShieldFigureBadge( figure: _shieldRightWitness, label: 'ПРАВЫЙ СВИДЕТЕЛЬ', role: 'Правый Свидетель (Внешнее)', ), ), ], ), const SizedBox(height: 8), // Row 3: 4 Nephews Row( children: List.generate(4, (idx) { final n = _shieldNephews.length > (3 - idx) ? _shieldNephews[3 - idx] : null; return Expanded( child: Padding( padding: EdgeInsets.only(right: idx < 3 ? 4 : 0), child: _buildShieldFigureBadge( figure: n, label: 'П${4 - idx}', role: 'Племянник #${4 - idx}', ), ), ); }), ), const SizedBox(height: 8), // Row 4: 4 Daughters Row( children: List.generate(4, (idx) { final d = _shieldDaughters.length > (3 - idx) ? _shieldDaughters[3 - idx] : null; return Expanded( child: Padding( padding: EdgeInsets.only(right: idx < 3 ? 4 : 0), child: _buildShieldFigureBadge( figure: d, label: 'Д${4 - idx}', role: 'Дочь #${4 - idx}', ), ), ); }), ), const SizedBox(height: 8), // Row 5: 4 Mothers Row( children: List.generate(4, (idx) { final m = _shieldMothers.length > (3 - idx) ? _shieldMothers[3 - idx] : null; return Expanded( child: Padding( padding: EdgeInsets.only(right: idx < 3 ? 4 : 0), child: _buildShieldFigureBadge( figure: m, label: 'М${4 - idx}', role: 'Мать #${4 - idx}', ), ), ); }), ), ], ), ); } Widget _buildFigureDetailCard({ required GeomancyFigure figure, required String positionTitle, }) { final linkedWord = _resolveLinkedWord(figure.primaryWordId); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF161320), borderRadius: BorderRadius.circular(18), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: const Color(0xFF0F0C14), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)), ), child: _buildDotsVisual(figure.lines, dotSize: 8, spacing: 5), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( positionTitle, style: const TextStyle( color: Color(0xFFFFD54F), fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 0.8, ), ), const SizedBox(height: 2), Text( figure.russianName, style: const TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold, ), ), Text( '${figure.latinName} (${figure.arabicName})', style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 11.5), ), const SizedBox(height: 6), Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: const Color(0xFF263238), borderRadius: BorderRadius.circular(6), ), child: Text( figure.element, style: const TextStyle(color: Color(0xFF80DEEA), fontSize: 10, fontWeight: FontWeight.bold), ), ), const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: const Color(0xFF33202C), borderRadius: BorderRadius.circular(6), ), child: Text( figure.planet, style: const TextStyle(color: Color(0xFFFF80AB), fontSize: 10, fontWeight: FontWeight.bold), ), ), const SizedBox(width: 6), Text( figure.isEven ? 'Чёт (внутр.)' : 'Нечет (вектор)', style: const TextStyle(color: Color(0xFF61688B), fontSize: 10), ), ], ), ], ), ), ], ), const SizedBox(height: 10), Text( figure.lore, style: const TextStyle(color: Color(0xFFCFD8DC), fontSize: 12.5, height: 1.4), ), if (linkedWord != null) ...[ const SizedBox(height: 12), InkWell( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => SymbolDetailScreen(word: linkedWord)), ); }, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: const Color(0xFF241C16), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFFFB74D).withValues(alpha: 0.4)), ), child: Row( children: [ Text(linkedWord.emoji.isNotEmpty ? linkedWord.emoji : '🔗', style: const TextStyle(fontSize: 16)), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Связанный знак 999: #${linkedWord.formattedSessionNumber} «${linkedWord.word.toUpperCase()}»', style: const TextStyle( color: Color(0xFFFFD54F), fontSize: 11.5, fontWeight: FontWeight.bold, ), ), if (linkedWord.core.isNotEmpty) Text( linkedWord.core, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFF8C93B8), fontSize: 10), ), ], ), ), const Icon(Icons.arrow_forward_ios_rounded, size: 12, color: Color(0xFFFFD54F)), ], ), ), ), ], ], ), ); } @override Widget build(BuildContext context) { final isShield = _selectedLayout.id == 'geomancy_shield'; final hasResults = isShield ? _shieldJudge != null : _drawnResults.isNotEmpty; return Scaffold( backgroundColor: const Color(0xFF0C0A12), appBar: AppBar( title: const Row( children: [ Text('⌛', style: TextStyle(fontSize: 20)), SizedBox(width: 8), Text( "Геомантия ('Илм ар-рамл)", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), ], ), actions: [ IconButton( tooltip: 'Инструкция по Геомантии', icon: const Icon(Icons.help_outline_rounded, color: Color(0xFFFFD54F)), onPressed: () => DivinationInstructionModal.show(context, 'geomancy'), ), if (hasResults) ...[ IconButton( tooltip: 'Мастер-Промпт для Нейросетей', icon: const Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F)), onPressed: _showMasterPromptModal, ), IconButton( tooltip: 'Сбросить расклад', icon: const Icon(Icons.refresh_rounded), onPressed: _reset, ), ], ], bottom: TabBar( controller: _tabController, indicatorColor: const Color(0xFFFFD54F), tabs: const [ Tab(icon: Icon(Icons.grain_rounded, size: 18), text: 'Ритуал Песка'), Tab(icon: Icon(Icons.grid_view_rounded, size: 18), text: '16 Фигур'), ], ), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Question Field TextField( controller: _questionController, style: const TextStyle(color: Colors.white, fontSize: 14), decoration: InputDecoration( hintText: 'Вопрос к песку и стихиям...', hintStyle: const TextStyle(color: Color(0xFF8C93B8), fontSize: 13), filled: true, fillColor: const Color(0xFF14121E), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFF2A2338)), ), prefixIcon: const Icon(Icons.psychology_alt_rounded, color: Color(0xFFFFD54F), size: 20), ), ), const SizedBox(height: 12), // Layout Selector _buildLayoutSelector(), const SizedBox(height: 16), // Tab Content (Ritual or Manual) AnimatedBuilder( animation: _tabController, builder: (ctx, _) { return _tabController.index == 0 ? _buildSandRitualTab() : _buildManualCatalogTab(); }, ), const SizedBox(height: 20), // Results Section if (hasResults) ...[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'РЕЗУЛЬТАТ РАСКЛАДА', style: TextStyle( color: Color(0xFFFFD54F), fontSize: 11.5, fontWeight: FontWeight.w900, letterSpacing: 1.0, ), ), Row( children: [ IconButton( tooltip: 'Скопировать сводку', icon: const Icon(Icons.copy_rounded, size: 18, color: Color(0xFF8C93B8)), onPressed: _copySpreadSummary, ), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: _savedToSession ? const Color(0xFF1B5E20) : const Color(0xFF2E7D32), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), onPressed: _savedToSession ? null : () => _saveToSyncreticSession(silent: false), icon: Icon(_savedToSession ? Icons.check_circle_rounded : Icons.bookmark_add_rounded, size: 16), label: Text(_savedToSession ? 'В сессии ✓' : 'В сессию', style: const TextStyle(fontSize: 11)), ), ], ), ], ), const SizedBox(height: 10), // Shield Layout or List of Figures if (isShield) ...[ _buildShieldHierarchyView(), const SizedBox(height: 12), if (_inspectedShieldFigure != null) _buildFigureDetailCard( figure: _inspectedShieldFigure!, positionTitle: _inspectedShieldTitle ?? 'Инспекция Фигуры', ), ] else ...[ ..._drawnResults.map((r) => _buildFigureDetailCard( figure: r.figure, positionTitle: r.positionTitle, )), ], const SizedBox(height: 12), // AI Interpretation Button OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFFFFD54F), side: const BorderSide(color: Color(0xFFFFD54F)), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), onPressed: _showMasterPromptModal, icon: const Icon(Icons.psychology_alt_rounded, size: 18), label: const Text( 'Мастер-Промпт для Нейросетей (Grok / Claude / GPT)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13), ), ), const SizedBox(height: 10), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF33202C), foregroundColor: const Color(0xFFFF80AB), side: const BorderSide(color: Color(0xFFFF80AB)), padding: const EdgeInsets.symmetric(vertical: 13), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), onPressed: _isAiLoading ? null : _requestAiInterpretation, icon: _isAiLoading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFFFF80AB)), ) : const Icon(Icons.auto_awesome_rounded, size: 18), label: Text( _isAiLoading ? 'ИИ-Оракул читает песок...' : 'ИИ-Толкование Песка', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), ), ), ], ], ), ), ); } }