import '../widgets/external_symbols_browser.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/word_model.dart'; import '../services/traditions_repository.dart'; import '../services/words_repository.dart'; import '../widgets/gradient_star_badge.dart'; import '../widgets/traditions_atlas_view.dart'; import 'symbol_detail_screen.dart'; class CatalogScreen extends StatefulWidget { final String? initialAtlasMode; final String? initialTraditionId; const CatalogScreen({ super.key, this.initialAtlasMode, this.initialTraditionId, }); @override State createState() => _CatalogScreenState(); } class _CatalogScreenState extends State { final _repo = WordsRepository(); final _traditions = TraditionsRepository(); final _searchController = TextEditingController(); String _selectedFilter = 'Все'; String _sortBy = 'id'; // 'id', 'alpha', 'rank', 'layers', 'system' String _atlasMode = 'words'; // 'words' | 'canon' String? _selectedTraditionId; String? _focusStationId; List _filteredWords = []; final List _filters = [ 'Все', '🌐 Другие символы', '✨ Все системы', 'ᚱ Руны', '᚛ Огам', '✡️ Каббала', '⚗️ Алхимия', '🎴 Таро', '🪶 Ленорман', '☯️ И Цзин', '⌛ Геомантия', '♈ Гороскоп', '🐉 Китайский зодиак', '🌌 Космология', '📅 Дни недели', '🗓️ Месяцы года', '🔢 Нумерология', 'Мои правки', 'Со слоями', 'Архетипы', 'Действия', ]; int _getSystemOrder(String desc) { if (desc.contains('[Руны:')) return 1; if (desc.contains('[Огам:')) return 2; if (desc.contains('[Каббала:')) return 3; if (desc.contains('[Алхимия:')) return 4; if (desc.contains('[Таро:')) return 5; if (desc.contains('[Ленорман:')) return 6; if (desc.contains('[И Цзин:')) return 7; if (desc.contains('[Геомантия:')) return 8; if (desc.contains('[Додекада Созвездий:')) return 9; if (desc.contains('[Китайский гороскоп:')) return 10; if (desc.contains('[Космология:')) return 11; if (desc.contains('[Септенер:')) return 12; if (desc.contains('[Додекада:')) return 13; if (desc.contains('[Нумерология:') || desc.contains('[Матрица:')) return 14; if (desc.contains('[')) return 15; return 999; } @override void initState() { super.initState(); if (widget.initialAtlasMode != null) { _atlasMode = widget.initialAtlasMode!; } if (widget.initialTraditionId != null) { _selectedTraditionId = widget.initialTraditionId; } _applyFilter(); _searchController.addListener(_applyFilter); _traditions.init().then((_) { if (mounted) setState(() {}); }); } @override void dispose() { _searchController.removeListener(_applyFilter); _searchController.dispose(); super.dispose(); } void _applyFilter() { final query = _searchController.text.trim(); List list; if (query.isEmpty) { list = _repo.allWords; } else { list = _repo.search(query); } // Apply category / tag filter if (_selectedFilter != 'Все') { switch (_selectedFilter) { case '✨ Все системы': list = list.where((w) => _getSystemOrder(w.description) < 999).toList(); break; case 'ᚱ Руны': list = list.where((w) => w.description.contains('[Руны:')).toList(); break; case '᚛ Огам': list = list.where((w) => w.description.contains('[Огам:')).toList(); break; case '✡️ Каббала': list = list.where((w) => w.description.contains('[Каббала:')).toList(); break; case '⚗️ Алхимия': list = list.where((w) => w.description.contains('[Алхимия:')).toList(); break; case '🎴 Таро': list = list.where((w) => w.description.contains('[Таро:')).toList(); break; case '🪶 Ленорман': list = list.where((w) => w.description.contains('[Ленорман:')).toList(); break; case '☯️ И Цзин': list = list.where((w) => w.description.contains('[И Цзин:')).toList(); break; case '⌛ Геомантия': list = list.where((w) => w.description.contains('[Геомантия:')).toList(); break; case '♈ Гороскоп': list = list.where((w) => w.description.contains('[Додекада Созвездий:')).toList(); break; case '🐉 Китайский зодиак': list = list.where((w) => w.description.contains('[Китайский гороскоп:')).toList(); break; case '🌌 Космология': list = list.where((w) => w.description.contains('[Космология:')).toList(); break; case '📅 Дни недели': list = list.where((w) => w.description.contains('[Септенер:')).toList(); break; case '🗓️ Месяцы года': list = list.where((w) => w.description.contains('[Додекада:')).toList(); break; case '🔢 Нумерология': list = list.where((w) => w.description.contains('[Нумерология:') || w.description.contains('[Матрица:')).toList(); break; case 'Мои правки': list = list.where((w) => w.isUserEdited).toList(); break; case 'Со слоями': list = list.where((w) => w.layers.isNotEmpty).toList(); break; case 'Архетипы': list = list .where((w) => w.category.toLowerCase().contains('архетип') || w.family.toLowerCase().contains('totem') || w.operator == 'substance') .toList(); break; case 'Действия': list = list .where((w) => w.operator == 'shifter' || w.category.toLowerCase().contains('действие')) .toList(); break; } } // Apply Sorting switch (_sortBy) { case 'system': list = List.of(list)..sort((a, b) { final sysA = _getSystemOrder(a.description); final sysB = _getSystemOrder(b.description); if (sysA != sysB) return sysA.compareTo(sysB); return a.word.compareTo(b.word); }); break; case 'alpha': list = List.of(list)..sort((a, b) => a.word.compareTo(b.word)); break; case 'rank': list = List.of(list)..sort((a, b) { final r = a.lookup.compareTo(b.lookup); if (r != 0) return r; return a.sessionNumber.compareTo(b.sessionNumber); }); break; case 'layers': list = List.of(list)..sort((a, b) { final diff = b.layers.length.compareTo(a.layers.length); if (diff != 0) return diff; return a.sessionNumber.compareTo(b.sessionNumber); }); break; case 'id': default: list = List.of(list)..sort((a, b) => a.sessionNumber.compareTo(b.sessionNumber)); break; } setState(() { _filteredWords = list; }); } Future _openDetail(WordItem word) async { HapticFeedback.selectionClick(); await Navigator.push( context, MaterialPageRoute( builder: (context) => SymbolDetailScreen(word: word), ), ); if (mounted) { setState(() { _applyFilter(); }); } } static const _tagToCanonId = { 'Руны': 'runes', 'Огам': 'ogham', 'Каббала': 'kabbalah', 'Алхимия': 'alchemy', 'Таро': 'tarot', 'Ленорман': 'lenormand', 'И Цзин': 'iching', 'Геомантия': 'geomancy', 'Додекада Созвездий': 'western_zodiac', 'Китайский гороскоп': 'zodiac', 'Космология': 'cosmology', 'Септенер': 'days_of_week', 'Дни недели': 'days_of_week', 'Планетарный Септенер': 'days_of_week', 'Додекада': 'months_of_year', 'Месяцы года': 'months_of_year', 'Зодиак': 'western_zodiac', 'Нумерология': 'numerology', 'Матрица': 'numerology', }; void _openTaggedStation(String sys, String detail) { final canonId = _tagToCanonId[sys]; if (canonId == null) return; final trad = _traditions.byId(canonId); if (trad == null) return; final hay = detail.toLowerCase(); var bestScore = 0; String? bestId; for (final st in trad.stations) { var score = 0; final name = st.name.toLowerCase(); if (name.isNotEmpty && (hay.contains(name) || name.contains(hay))) { score += 12; } for (final tok in name.split(RegExp(r'[^0-9a-zа-яё]+'))) { if (tok.length > 2 && hay.contains(tok)) score += 2; } final sid = st.id.replaceAll('_', ' ').toLowerCase(); if (sid.isNotEmpty && hay.contains(sid)) score += 4; final prim = st.primary?.word.toLowerCase(); if (prim != null && prim.isNotEmpty && hay.contains(prim)) score += 3; if (score > bestScore) { bestScore = score; bestId = st.id; } } final filter = TraditionsRepository.filterToCanonId.entries .where((e) => e.value == canonId); _searchController.removeListener(_applyFilter); _searchController.clear(); _searchController.addListener(_applyFilter); setState(() { _atlasMode = 'canon'; _selectedTraditionId = canonId; _focusStationId = bestScore >= 2 ? bestId : trad.stations.first.id; if (filter.isNotEmpty) _selectedFilter = filter.first.key; }); } Future _openWordById(int id, [String name = '']) async { final named = name.trim().isEmpty ? null : _repo.getWordByName(name.trim()); if (named != null) { await _openDetail(named); return; } if (name.trim().isNotEmpty) return; final word = _repo.getWordById(id) ?? _repo.getWordBySessionNumber(id); if (word == null) return; await _openDetail(word); } void _showSyncDialog() { final count = _repo.customEditsCount; showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: const Color(0xFF1E2238), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: const Row( children: [ Icon(Icons.sync_alt_rounded, color: Color(0xFFFFD54F)), SizedBox(width: 8), Text('Синхронизация правок', style: TextStyle(color: Color(0xFFFFF9C4), fontSize: 18, fontWeight: FontWeight.bold)), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Ваших правок на телефоне: $count', style: const TextStyle(color: Color(0xFFE8EAF6), fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(height: 8), const Text( 'Вы можете выгрузить все свои исправления в виде JSON и перенести на ПК в релиз, либо загрузить ранее сохраненные правки.', style: TextStyle(color: Color(0xFFB0BEC5), fontSize: 13, height: 1.4), ), const SizedBox(height: 16), ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2A2D4A), foregroundColor: const Color(0xFFFFD54F), minimumSize: const Size.fromHeight(42), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), icon: const Icon(Icons.copy_rounded, size: 18), label: const Text('Скопировать JSON правок'), onPressed: () { final jsonStr = _repo.exportCustomEditsJson(); Clipboard.setData(ClipboardData(text: jsonStr)); HapticFeedback.mediumImpact(); Navigator.pop(ctx); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('JSON с правками ($count шт.) скопирован в буфер! Передайте его на ПК.'), backgroundColor: const Color(0xFF2E7D32), behavior: SnackBarBehavior.floating, ), ); }, ), const SizedBox(height: 8), OutlinedButton.icon( style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF81C784), side: const BorderSide(color: Color(0xFF81C784)), minimumSize: const Size.fromHeight(42), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), icon: const Icon(Icons.file_download_outlined, size: 18), label: const Text('Импортировать JSON правок'), onPressed: () { Navigator.pop(ctx); _showImportDialog(); }, ), if (count > 0) ...[ const SizedBox(height: 8), TextButton.icon( style: TextButton.styleFrom( foregroundColor: const Color(0xFFE57373), minimumSize: const Size.fromHeight(36), ), icon: const Icon(Icons.delete_outline_rounded, size: 16), label: const Text('Сбросить все мои правки'), onPressed: () async { Navigator.pop(ctx); _confirmClearAll(); }, ), ], ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Закрыть', style: TextStyle(color: Color(0xFF9FA8DA))), ), ], ), ); } void _showImportDialog() { final controller = TextEditingController(); showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: const Color(0xFF1E2238), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), title: const Text('Импорт JSON правок', style: TextStyle(color: Color(0xFFFFF9C4), fontSize: 17, fontWeight: FontWeight.bold)), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Вставьте текст экспортированного JSON:', style: TextStyle(color: Color(0xFFB0BEC5), fontSize: 13)), const SizedBox(height: 8), TextField( controller: controller, maxLines: 6, style: const TextStyle(color: Colors.white, fontSize: 12, fontFamily: 'monospace'), decoration: InputDecoration( hintText: '{\n "знак": { "core": "...", ... }\n}', hintStyle: const TextStyle(color: Color(0xFF546E7A)), filled: true, fillColor: const Color(0xFF151829), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Отмена', style: TextStyle(color: Color(0xFF9FA8DA))), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: const Color(0xFF2E7D32)), onPressed: () async { final text = controller.text.trim(); if (text.isEmpty) return; final messenger = ScaffoldMessenger.of(context); final nav = Navigator.of(ctx); final count = await _repo.importCustomEditsJson(text); if (!mounted) return; nav.pop(); setState(() => _applyFilter()); messenger.showSnackBar( SnackBar( content: Text('Успешно импортировано правок: $count'), backgroundColor: const Color(0xFF2E7D32), behavior: SnackBarBehavior.floating, ), ); }, child: const Text('Применить'), ), ], ), ); } void _confirmClearAll() { showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: const Color(0xFF1E2238), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: const Text('Удалить ВСЕ правки?', style: TextStyle(color: Color(0xFFE57373), fontWeight: FontWeight.bold)), content: const Text( 'Вы действительно хотите сбросить все сделанные вами редакции знаков? База вернется к состоянию из релиза.', style: TextStyle(color: Color(0xFFE0E0E0)), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Отмена', style: TextStyle(color: Color(0xFF9FA8DA))), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: const Color(0xFFE57373)), onPressed: () async { Navigator.pop(ctx); await _repo.clearAllCustomEdits(); if (mounted) { setState(() => _applyFilter()); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Все пользовательские правки удалены'), behavior: SnackBarBehavior.floating, ), ); } }, child: const Text('Удалить всё'), ), ], ), ); } @override Widget build(BuildContext context) { final count = _filteredWords.length; return Scaffold( backgroundColor: const Color(0xFF0C0E1A), appBar: AppBar( title: const Row( children: [ GradientStar(size: 22), SizedBox(width: 8), Text( 'Справочник 999', style: TextStyle(fontWeight: FontWeight.w900), ), ], ), actions: [ IconButton( tooltip: 'Синхронизация и экспорт правок', icon: Badge( isLabelVisible: _repo.customEditsCount > 0, label: Text('${_repo.customEditsCount}'), backgroundColor: const Color(0xFF81C784), child: const Icon(Icons.sync_alt_rounded, color: Color(0xFFFFD54F)), ), onPressed: _showSyncDialog, ), if (_atlasMode == 'words') PopupMenuButton( tooltip: 'Сортировка', icon: const Icon(Icons.sort_rounded, color: Color(0xFFFFD54F)), initialValue: _sortBy, onSelected: (val) { setState(() { _sortBy = val; _applyFilter(); }); }, itemBuilder: (context) => const [ PopupMenuItem( value: 'system', child: Row( children: [ Icon(Icons.auto_awesome_rounded, size: 18, color: Color(0xFFFFD54F)), SizedBox(width: 8), Text('По сакральным системам'), ], ), ), PopupMenuItem( value: 'id', child: Row( children: [ Icon(Icons.format_list_numbered, size: 18), SizedBox(width: 8), Text('По номеру (#000..#999)'), ], ), ), PopupMenuItem( value: 'alpha', child: Row( children: [ Icon(Icons.sort_by_alpha, size: 18), SizedBox(width: 8), Text('По алфавиту (А..Я)'), ], ), ), PopupMenuItem( value: 'layers', child: Row( children: [ Icon(Icons.layers_rounded, size: 18), SizedBox(width: 8), Text('Сначала со слоями'), ], ), ), PopupMenuItem( value: 'rank', child: Row( children: [ Icon(Icons.military_tech_rounded, size: 18), SizedBox(width: 8), Text('По рангу (A → B → C)'), ], ), ), ], ), ], ), body: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: _atlasMode == 'canon' ? 'Поиск по станции, лору или слову...' : 'Поиск по слову, номеру #000, закону...', hintStyle: const TextStyle(color: Color(0xFF8C93B8), fontSize: 14), prefixIcon: const Icon(Icons.search_rounded, color: Color(0xFFFFD54F)), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear_rounded, size: 18), onPressed: () { _searchController.clear(); }, ) : null, filled: true, fillColor: const Color(0xFF151829), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF2A2D4A)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF2A2D4A)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFFFFD54F), width: 1.5), ), ), ), ), // Filters row SizedBox( height: 38, child: SingleChildScrollView( key: const Key('catalog-filters'), scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ for (final filter in _filters) ...[ if (filter != _filters.first) const SizedBox(width: 8), ChoiceChip( label: Text(filter), labelStyle: TextStyle( color: filter == _selectedFilter ? const Color(0xFF1B1D30) : const Color(0xFFD1D5DB), fontSize: 12, fontWeight: filter == _selectedFilter ? FontWeight.bold : FontWeight.normal, ), selected: filter == _selectedFilter, selectedColor: const Color(0xFFFFD54F), backgroundColor: const Color(0xFF151829), side: BorderSide( color: filter == _selectedFilter ? const Color(0xFFFFD54F) : const Color(0xFF2A2D4A), ), onSelected: (selected) { if (selected) { setState(() { _selectedFilter = filter; final canonId = TraditionsRepository.filterToCanonId[filter]; if (canonId != null) { _atlasMode = 'canon'; _selectedTraditionId = canonId; } else { _atlasMode = 'words'; _selectedTraditionId = null; } _applyFilter(); }); } }, ), ], ], ), ), ), if (_atlasMode == 'words' && _selectedFilter != '🌐 Другие символы') Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), child: Row( children: [ Flexible( child: Text( 'Найдено знаков: $count', style: const TextStyle( color: Color(0xFF8C93B8), fontSize: 12, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), const Flexible( child: Text( 'Нажмите для полного досье', style: TextStyle( color: Color(0xFF5C6280), fontSize: 11, fontStyle: FontStyle.italic, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), Expanded( child: _selectedFilter == '🌐 Другие символы' ? ExternalSymbolsBrowser(searchQuery: _searchController.text.trim()) : _atlasMode == 'canon' && _selectedTraditionId != null && _traditions.byId(_selectedTraditionId!) != null ? TraditionsAtlasView( traditions: _traditions.traditions, selected: _traditions.byId(_selectedTraditionId!), searchQuery: _searchController.text.trim(), focusStationId: _focusStationId, onSelectTradition: (t) { setState(() { _selectedTraditionId = t.id; _focusStationId = null; final match = TraditionsRepository.filterToCanonId.entries .where((e) => e.value == t.id); if (match.isNotEmpty) { _selectedFilter = match.first.key; } }); }, onBackToOverview: () { setState(() { _selectedTraditionId = null; _selectedFilter = 'Все'; _atlasMode = 'words'; }); }, onOpenWord: _openWordById, ) : _filteredWords.isEmpty ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ GradientStar(size: 40), SizedBox(height: 16), Text( 'Символ не найден', style: TextStyle( color: Color(0xFF8C93B8), fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ) : ListView.separated( padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), itemCount: _filteredWords.length, separatorBuilder: (context, index) => const SizedBox(height: 10), itemBuilder: (context, index) { final item = _filteredWords[index]; return _buildSymbolCard(item); }, ), ), ], ), ); } List _buildSystemBadges(String desc) { final List badges = []; final reg = RegExp(r'\[([А-Яа-яЁёA-Za-z\s]+):([^\]]+)\]'); for (final m in reg.allMatches(desc)) { final sys = m.group(1)?.trim() ?? ''; final detail = m.group(2)?.trim() ?? ''; Color color; String icon; switch (sys) { case 'Руны': color = const Color(0xFF90CAF9); icon = 'ᚱ'; break; case 'Огам': color = const Color(0xFFA5D6A7); icon = '᚛'; break; case 'Каббала': color = const Color(0xFFCE93D8); icon = '✡️'; break; case 'Алхимия': color = const Color(0xFFFFCC80); icon = '⚗️'; break; case 'Таро': color = const Color(0xFFEF9A9A); icon = '🎴'; break; case 'Ленорман': color = const Color(0xFF80CBC4); icon = '🪶'; break; case 'И Цзин': color = const Color(0xFFECEFF1); icon = '☯️'; break; case 'Геомантия': color = const Color(0xFFFFAB91); icon = '⌛'; break; case 'Додекада Созвездий': color = const Color(0xFF80DEEA); icon = '♈'; break; case 'Китайский гороскоп': color = const Color(0xFFFFE082); icon = '🐉'; break; case 'Космология': color = const Color(0xFFB39DDB); icon = '🌌'; break; default: color = const Color(0xFFFFD54F); icon = '✨'; break; } badges.add( GestureDetector( onTap: () => _openTaggedStation(sys, detail), child: Container( margin: const EdgeInsets.only(right: 6, bottom: 4), padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(6), border: Border.all(color: color.withValues(alpha: 0.35)), ), child: Text( '$icon ${WordItemLocalization.localizeSystem(sys)}: $detail', style: TextStyle( color: color, fontSize: 11, fontWeight: FontWeight.w600, ), ), ), ), ); } return badges; } Widget _buildSymbolCard(WordItem item) { final previewText = item.displayMeaning; final systemBadges = _buildSystemBadges(item.description); return InkWell( onTap: () => _openDetail(item), borderRadius: BorderRadius.circular(18), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFF151829), borderRadius: BorderRadius.circular(18), border: Border.all( color: item.layers.isNotEmpty ? const Color(0xFFFFD54F).withValues(alpha: 0.35) : const Color(0xFF2A2D4A), ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.2), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Top Row: Number, Word, Pills Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF2A2D4A), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.4)), ), child: Text( '#${item.formattedSessionNumber}', style: const TextStyle( color: Color(0xFFFFD54F), fontWeight: FontWeight.w900, fontSize: 13, ), ), ), const SizedBox(width: 10), Expanded( child: Text( item.word.toUpperCase(), style: const TextStyle( color: Color(0xFFFFF9C4), fontWeight: FontWeight.w900, fontSize: 17, letterSpacing: 0.8, ), ), ), if (item.isUserEdited) Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration( color: const Color(0xFF2E7D32).withValues(alpha: 0.3), borderRadius: BorderRadius.circular(6), border: Border.all(color: const Color(0xFF81C784)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.edit_note_rounded, color: Color(0xFFA5D6A7), size: 12), SizedBox(width: 3), Text('Правка', style: TextStyle(color: Color(0xFFA5D6A7), fontWeight: FontWeight.bold, fontSize: 11)), ], ), ), if (item.layers.isNotEmpty) Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), margin: const EdgeInsets.only(right: 6), decoration: BoxDecoration( color: const Color(0xFF1E2238), borderRadius: BorderRadius.circular(6), border: Border.all(color: const Color(0xFFFFD54F).withValues(alpha: 0.5)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.layers_rounded, color: Color(0xFFFFD54F), size: 12), const SizedBox(width: 4), Text( '${item.layers.length}', style: const TextStyle( color: Color(0xFFFFD54F), fontWeight: FontWeight.bold, fontSize: 11, ), ), ], ), ), const Icon(Icons.chevron_right_rounded, color: Color(0xFF8C93B8), size: 20), ], ), const SizedBox(height: 8), // Law / Core snippet Text( previewText, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color(0xFFC5CAE9), fontSize: 13, height: 1.4, ), ), const SizedBox(height: 10), // System Badges if (systemBadges.isNotEmpty) ...[ const SizedBox(height: 8), Wrap( children: systemBadges, ), ], // Category & POS Badges const SizedBox(height: 8), Wrap( spacing: 6, children: [ if (item.category.isNotEmpty) _miniBadge(item.category, const Color(0xFFCE93D8)), _miniBadge(item.partOfSpeech, const Color(0xFF80D8FF)), ], ), ], ), ), ); } Widget _miniBadge(String text, Color color) { return Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all(color: color.withValues(alpha: 0.3)), ), child: Text( text, style: TextStyle( color: color, fontSize: 11, fontWeight: FontWeight.w600, ), ), ); } }