Update Saqel Platform: 2026-09-12 08:50:01
This commit is contained in:
@@ -37,7 +37,7 @@ class AppStrings {
|
||||
// Student Dashboard
|
||||
static String get tawjihiCurriculum => isArabic ? 'المنهاج الوزاري المعتمد 📚' : 'Ministry Curriculum 📚';
|
||||
static String get readinessScore => isArabic ? 'مؤشر الجاهزية للوزاري' : 'Tawjihi Readiness Score';
|
||||
static String get socraticCheckpoint => isArabic ? 'فحص سقراطي 🎯' : 'Socratic Checkpoint 🎯';
|
||||
static String get socraticCheckpoint => isArabic ? 'فحص الفهم 🎯' : 'Knowledge Checkpoint 🎯';
|
||||
static String get noLessonsFound => isArabic ? 'لا توجد دروس مسجلة للمرحلة الحالية' : 'No lessons recorded for this grade yet';
|
||||
|
||||
// Guardian Dashboard
|
||||
|
||||
@@ -45,7 +45,7 @@ class SocraticCheckpointModel {
|
||||
questionId: json['question_id'] is num
|
||||
? (json['question_id'] as num).toInt()
|
||||
: int.tryParse(json['question_id']?.toString() ?? '0') ?? 0,
|
||||
questionText: json['question']?.toString() ?? json['question_text']?.toString() ?? 'سؤال فحص الفهم السقراطي',
|
||||
questionText: json['question']?.toString() ?? json['question_text']?.toString() ?? 'سؤال فحص الفهم والاستيعاب',
|
||||
timestampSeconds: (json['timestamp_seconds'] as num?)?.toInt() ?? 120,
|
||||
rewindSecondsOnFail: (json['rewind_seconds'] as num?)?.toInt() ?? 45,
|
||||
hint: json['hint']?.toString() ?? '',
|
||||
|
||||
@@ -30,6 +30,9 @@ import 'islamic_interactive_lab_view.dart';
|
||||
import 'history_interactive_timeline_view.dart';
|
||||
import 'math_interactive_lab_view.dart';
|
||||
import 'english_interactive_lab_view.dart';
|
||||
import '../virtual_labs/labs_gallery_screen.dart';
|
||||
import '../virtual_labs/labs_registry.dart';
|
||||
import '../virtual_labs/subject_virtual_labs_view.dart';
|
||||
import '../../../data/models/exam_model.dart';
|
||||
import '../../../data/repositories/app_repositories.dart';
|
||||
import '../../../data/repositories/curriculum_repository.dart';
|
||||
@@ -98,6 +101,7 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
widget.subject.title.contains('دراسات');
|
||||
|
||||
bool get _hasLab =>
|
||||
Grade10LabsRegistry.bySubjectNormalized(widget.subject.title).isNotEmpty ||
|
||||
_isMathSubject ||
|
||||
_isEnglishSubject ||
|
||||
_isChemistrySubject ||
|
||||
@@ -109,46 +113,57 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
_isHistorySubject;
|
||||
|
||||
Tab get _dynamicLabTab {
|
||||
if (_isMathSubject) {
|
||||
final norm = Grade10LabsRegistry.normalizeSubject(widget.subject.title);
|
||||
if (_isMathSubject || norm == 'الرياضيات') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.function, size: 18),
|
||||
text: 'معمل جيوجبرا للرياضيات 📐',
|
||||
text: 'مختبر الرياضيات والهندسة 📐',
|
||||
);
|
||||
} else if (_isEnglishSubject) {
|
||||
} else if (_isEnglishSubject || norm == 'اللغة الإنجليزية') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.headphones, size: 18),
|
||||
text: 'مختبر الصوتيات والقواعد 🎧',
|
||||
);
|
||||
} else if (_isArabicSubject) {
|
||||
} else if (_isArabicSubject || norm == 'العربية لغتي') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.arrow_branch, size: 18),
|
||||
text: 'مختبر الإعراب والعروض 📜',
|
||||
);
|
||||
} else if (_isIslamicSubject) {
|
||||
} else if (_isIslamicSubject || norm == 'التربية الإسلامية') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.money_dollar_circle_fill, size: 18),
|
||||
text: 'حاسبة المواريث والتجويد ⚖️',
|
||||
);
|
||||
} else if (_isHistorySubject) {
|
||||
} else if (_isHistorySubject || norm == 'تاريخ الأردن') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.time_solid, size: 18),
|
||||
text: 'الخط الزمني لتاريخ الأردن 🏛️',
|
||||
);
|
||||
} else if (_isChemistrySubject) {
|
||||
} else if (_isChemistrySubject || norm == 'الكيمياء') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.flame_fill, size: 18),
|
||||
text: 'مختبر الكيمياء والطيوف ⚗️',
|
||||
);
|
||||
} else if (_isBiologySubject) {
|
||||
} else if (_isBiologySubject || norm == 'العلوم الحياتية') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.viewfinder, size: 18),
|
||||
text: 'مختبر الأحياء والمجهر 🔬',
|
||||
);
|
||||
} else if (_isEarthSciencesSubject) {
|
||||
} else if (_isEarthSciencesSubject || norm == 'علوم الأرض والبيئة') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.globe, size: 18),
|
||||
text: 'مختبر علوم الأرض والصخور 🌍',
|
||||
);
|
||||
} else if (norm == 'الثقافة المالية') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.chart_bar_square_fill, size: 18),
|
||||
text: 'مختبر الثقافة المالية والجدوى 📊',
|
||||
);
|
||||
} else if (norm == 'المهارات الرقمية') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.desktopcomputer, size: 18),
|
||||
text: 'مختبر الخوارزميات والبرمجة 💻',
|
||||
);
|
||||
} else {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.bolt_fill, size: 18),
|
||||
@@ -158,25 +173,44 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
}
|
||||
|
||||
Widget get _dynamicLabWidget {
|
||||
Widget? specializedTool;
|
||||
String? specializedTitle;
|
||||
|
||||
if (_isMathSubject) {
|
||||
return const MathInteractiveLabView();
|
||||
specializedTool = const MathInteractiveLabView();
|
||||
specializedTitle = 'معمل جيوجبرا 📐';
|
||||
} else if (_isEnglishSubject) {
|
||||
return const EnglishInteractiveLabView();
|
||||
specializedTool = const EnglishInteractiveLabView();
|
||||
specializedTitle = 'مدرب الصوتيات 🎙️';
|
||||
} else if (_isArabicSubject) {
|
||||
return const ArabicInteractiveLabView();
|
||||
specializedTool = const ArabicInteractiveLabView();
|
||||
specializedTitle = 'شجرة الإعراب 📜';
|
||||
} else if (_isIslamicSubject) {
|
||||
return const IslamicInteractiveLabView();
|
||||
specializedTool = const IslamicInteractiveLabView();
|
||||
specializedTitle = 'حاسبة المواريث ⚖️';
|
||||
} else if (_isHistorySubject) {
|
||||
return const HistoryInteractiveTimelineView();
|
||||
specializedTool = const HistoryInteractiveTimelineView();
|
||||
specializedTitle = 'الخط الزمني الشامل 🏛️';
|
||||
} else if (_isChemistrySubject) {
|
||||
return const ChemistryInteractiveLabView();
|
||||
specializedTool = const ChemistryInteractiveLabView();
|
||||
specializedTitle = 'طيف ذرة بور ⚗️';
|
||||
} else if (_isBiologySubject) {
|
||||
return const BiologyInteractiveLabView();
|
||||
specializedTool = const BiologyInteractiveLabView();
|
||||
specializedTitle = 'المجهر الرقمي 🔬';
|
||||
} else if (_isEarthSciencesSubject) {
|
||||
return const EarthSciencesInteractiveLabView();
|
||||
} else {
|
||||
return const PhysicsInteractiveLabView();
|
||||
specializedTool = const EarthSciencesInteractiveLabView();
|
||||
specializedTitle = 'دورة الصخور والطبقات 🌍';
|
||||
} else if (_isPhysicsSubject) {
|
||||
specializedTool = const PhysicsInteractiveLabView();
|
||||
specializedTitle = 'محاكاة المتجهات ⚡';
|
||||
}
|
||||
|
||||
return SubjectVirtualLabsView(
|
||||
subjectTitle: widget.subject.title,
|
||||
primaryColor: widget.subject.primaryColor,
|
||||
specializedToolWidget: specializedTool,
|
||||
specializedToolTitle: specializedTitle,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -207,6 +241,23 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
widget.subject.title,
|
||||
style: AppTypography.titleLarge.copyWith(color: Colors.white, fontSize: 18),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'المختبرات الافتراضية لكل درس',
|
||||
icon: const Icon(CupertinoIcons.lab_flask_solid,
|
||||
color: AppColors.saqelCyan, size: 22),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (_) => const Scaffold(
|
||||
backgroundColor: AppColors.darkBackground,
|
||||
body: SafeArea(child: VirtualLabsGalleryScreen()),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
indicatorColor: widget.subject.primaryColor,
|
||||
@@ -297,11 +348,17 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 15),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${unit.lessons.length} دروس تفاعلية • فحص فهم سقراطي',
|
||||
'${unit.lessons.length} دروس تفاعلية • فحص فهم مستمر',
|
||||
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
|
||||
),
|
||||
children: unit.lessons.map((lesson) {
|
||||
final hasVid = lesson.hasVideo;
|
||||
final lab = Grade10LabsRegistry.findForLesson(
|
||||
subjectTitle: widget.subject.title,
|
||||
lessonTitle: lesson.title,
|
||||
lessonId: lesson.id,
|
||||
curriculumLessonId: lesson.curriculumLessonId,
|
||||
);
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
@@ -313,69 +370,128 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
: Colors.white.withAlpha(15),
|
||||
),
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
|
||||
leading: Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
gradient: hasVid
|
||||
? LinearGradient(
|
||||
colors: [widget.subject.primaryColor, widget.subject.secondaryColor],
|
||||
)
|
||||
: null,
|
||||
color: hasVid ? null : Colors.white.withAlpha(10),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: hasVid ? null : Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Icon(
|
||||
hasVid ? CupertinoIcons.play_fill : CupertinoIcons.film,
|
||||
color: hasVid ? Colors.black : AppColors.textSecondaryDark,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
lesson.title,
|
||||
style: TextStyle(
|
||||
color: hasVid ? Colors.white : Colors.white70,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${lesson.durationSeconds ~/ 60} دقيقة',
|
||||
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
|
||||
leading: Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
color: hasVid
|
||||
? AppColors.saqelCyan.withAlpha(25)
|
||||
: Colors.white.withAlpha(12),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
gradient: hasVid
|
||||
? LinearGradient(
|
||||
colors: [widget.subject.primaryColor, widget.subject.secondaryColor],
|
||||
)
|
||||
: null,
|
||||
color: hasVid ? null : Colors.white.withAlpha(10),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: hasVid ? null : Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Text(
|
||||
hasVid ? '${lesson.checkpointsCount} فحوصات' : 'قريباً',
|
||||
style: TextStyle(
|
||||
color: hasVid ? AppColors.saqelCyan : AppColors.textSecondaryDark,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
child: Icon(
|
||||
hasVid ? CupertinoIcons.play_fill : CupertinoIcons.film,
|
||||
color: hasVid ? Colors.black : AppColors.textSecondaryDark,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
lesson.title,
|
||||
style: TextStyle(
|
||||
color: hasVid ? Colors.white : Colors.white70,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${lesson.durationSeconds ~/ 60} دقيقة',
|
||||
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: hasVid
|
||||
? AppColors.saqelCyan.withAlpha(25)
|
||||
: Colors.white.withAlpha(12),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
hasVid ? '${lesson.checkpointsCount} فحوصات' : 'قريباً',
|
||||
style: TextStyle(
|
||||
color: hasVid ? AppColors.saqelCyan : AppColors.textSecondaryDark,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Icon(
|
||||
hasVid ? CupertinoIcons.chevron_back : CupertinoIcons.clock,
|
||||
color: hasVid ? AppColors.textSecondaryDark : Colors.white24,
|
||||
size: 16,
|
||||
),
|
||||
onTap: () {
|
||||
_showLessonVideoSelector(context, lesson);
|
||||
},
|
||||
),
|
||||
if (lab != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => Grade10LabsRegistry.openLab(context, lab),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.saqelCyan.withValues(alpha: 0.25)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(CupertinoIcons.lab_flask_solid, color: AppColors.saqelCyan, size: 14),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'المختبر الافتراضي: ${lab.lessonAr}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'بدء التجربة',
|
||||
style: TextStyle(
|
||||
color: AppColors.saqelCyan,
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 3),
|
||||
Icon(CupertinoIcons.arrow_left, color: AppColors.saqelCyan, size: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Icon(
|
||||
hasVid ? CupertinoIcons.chevron_back : CupertinoIcons.clock,
|
||||
color: hasVid ? AppColors.textSecondaryDark : Colors.white24,
|
||||
size: 16,
|
||||
),
|
||||
onTap: () {
|
||||
_showLessonVideoSelector(context, lesson);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
@@ -804,6 +920,13 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
|
||||
/// Empty state when no video exists yet for this lesson (Zero Mock Videos)
|
||||
void _showNoVideoAvailableSheet(BuildContext context, CurriculumLessonItemModel lesson) {
|
||||
final lab = Grade10LabsRegistry.findForLesson(
|
||||
subjectTitle: widget.subject.title,
|
||||
lessonTitle: lesson.title,
|
||||
lessonId: lesson.id,
|
||||
curriculumLessonId: lesson.curriculumLessonId,
|
||||
);
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: AppColors.darkSurface,
|
||||
@@ -848,16 +971,37 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'لا يوجد شرح مصور منشور لهذا الدرس بعد. يمكنك قراءة شروحات الدرس المعتمدة أو الانتقال للمختبر التفاعلي.',
|
||||
'لا يوجد شرح مصور منشور لهذا الدرس بعد. يمكنك خوض التجربة الافتراضية للدرس مباشرة أو قراءة الشروحات المعتمدة.',
|
||||
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 13, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (lab != null) ...[
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.saqelCyan,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
Grade10LabsRegistry.openLab(context, lab);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.lab_flask_solid, size: 18, color: Colors.black),
|
||||
label: Text('فتح المختبر الافتراضي: ${lab.lessonAr} 🧪',
|
||||
style: const TextStyle(fontWeight: FontWeight.w900, fontSize: 13.5)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.appleBlue,
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: AppColors.darkCardBorder),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
@@ -883,19 +1027,18 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.saqelCyan,
|
||||
side: BorderSide(color: AppColors.saqelCyan.withOpacity(0.5)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
_tabController.animateTo(4);
|
||||
_tabController.animateTo(1);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.lab_flask_solid, size: 18),
|
||||
label: const Text('الانتقال إلى المختبر التفاعلي للمادة 🧪', style: TextStyle(fontWeight: FontWeight.w700)),
|
||||
icon: const Icon(CupertinoIcons.arrow_left_circle, size: 16),
|
||||
label: const Text('استعراض كافة تجارب المادة (تبويب المختبر) 🧪',
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -423,7 +423,7 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'اختر من بين 16 مبحثاً دراسياً لبدء شروحات الفيديو التفاعلية، أوراق العمل، وبنك الأسئلة السقراطي',
|
||||
'اختر من بين 16 مبحثاً دراسياً لبدء شروحات الفيديو التفاعلية، أوراق العمل، وبنك الأسئلة المعتمد',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 13, height: 1.4),
|
||||
),
|
||||
@@ -688,7 +688,7 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'تقييم حقيقي مبني على الكويزات والفحوصات السقراطية',
|
||||
'تقييم حقيقي مبني على الكويزات وفحوصات الفهم',
|
||||
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondaryDark, fontSize: 12),
|
||||
),
|
||||
],
|
||||
@@ -937,7 +937,7 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
|
||||
_buildGuardianStat('الامتحانات المنجزة بنجاح', '${selectedChild.examsPassed} من ${selectedChild.examsTotal}'),
|
||||
_buildGuardianStat('دفتر الأخطاء الذكي', '8 من أصل 10 فجوات تم شفاؤها وإتقانها 🏆'),
|
||||
_buildGuardianStat('نسبة الحضور ومشاهدة الحصص', '96% (28 حصة مكتملة)'),
|
||||
_buildGuardianStat('الفحوصات السقراطية التفاعلية', '18 فحصاً مجتازاً بنجاح ✨'),
|
||||
_buildGuardianStat('فحوصات الفهم التفاعلية', '18 فحصاً مجتازاً بنجاح ✨'),
|
||||
_buildGuardianStat('الرقم الوطني المشفر', selectedChild.nationalId ?? 'مسجل بالهاتف'),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -945,7 +945,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
Icon(CupertinoIcons.sparkles, color: AppColors.saqelCyan, size: 14),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'لوحة الشرح السقراطية التفاعلية 💡',
|
||||
'لوحة الشرح والاستيعاب التفاعلية 💡',
|
||||
style: TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.w700),
|
||||
),
|
||||
],
|
||||
@@ -1003,7 +1003,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
if (cp != null)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.question_circle_fill, size: 16),
|
||||
label: const Text('فحص الفهم السقراطي ⚡'),
|
||||
label: const Text('فحص الفهم والاستيعاب ⚡'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.appleBlue,
|
||||
foregroundColor: Colors.white,
|
||||
|
||||
@@ -0,0 +1,814 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — SPEAKING FLUENTLY (ART OF APOLOGY & VALUES OF TOLERANCE)
|
||||
/// Lesson: أتحدثُ بطالقةٍ — فنّ الاعتذار وقيمُ التسامح
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_01/lesson_02.md (pages 10-12)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Faithful to the source only. Nothing here is invented:
|
||||
/// - The speech-building order (مقدّمة ← عرض ← خاتمة) from p.11.
|
||||
/// - The tone skill (تنغيم يُوظَّف بما يناسب أغراض الحديث والمشاعر، بلا
|
||||
/// افتعالٍ ولا مبالغة) and the value «إن من موجبات المغفرة بذل السلام
|
||||
/// وحُسن الكلام» from p.11.
|
||||
/// - The apology-culture five questions (لماذا/إلى من/متى/كيف/ثمار) from
|
||||
/// the p.12 shape, applied to the p.12 situation (سخرية في جلسة ← انسحاب
|
||||
/// الزميل ← مسارعةُ صاحِب السخرية إلى الاعتذار).
|
||||
/// No demo mastery/income. The identity stays flagged for academic review
|
||||
/// (identity.statusLabel) until released + a server bundle is supplied.
|
||||
/// ============================================================================
|
||||
|
||||
const List<(String, String)> _speechParts = [
|
||||
('أ', 'مقدّمة: أُبيّن فيها الفكرةَ التي تُمثِّل الموقفَ، معبِّرًا عن رأيي'),
|
||||
('ب', 'عرضٌ: لغةٌ واضحةٌ بتلخيصٍ يُبيّن بعضَ القيمِ والدروسِ المستفادة'),
|
||||
('ج', 'خاتمة: أختمُ حديثي'),
|
||||
];
|
||||
const List<String> _speechShort = ['المقدمة', 'العرض', 'الخاتمة'];
|
||||
|
||||
const List<(String, String, String)> _apologyAxes = [
|
||||
(
|
||||
'لماذا أعتذرُ؟',
|
||||
'لأنّني تسبّبتُ بجرحِ زميلي، وأُصحّحُ الخطأَ صدقًا',
|
||||
'لأُثبِتَ أنّني محقٌّ في سخريتي'
|
||||
),
|
||||
(
|
||||
'إلى من أعتذرُ؟',
|
||||
'إلى الزميلِ الذي انسحب، ببذلِ السلامِ له مباشرةً',
|
||||
'إلى بقيةِ الحاضرين فقط'
|
||||
),
|
||||
(
|
||||
'متى أعتذرُ؟',
|
||||
'مسارعةً هادئةً بعدما رأيتُ أماراتِ الحزنِ على وجهه',
|
||||
'لاحقًا حين ينتهي الوقتُ وتُنسى الجلسة'
|
||||
),
|
||||
(
|
||||
'كيف أعتذرُ؟',
|
||||
'بكلامٍ حَسَنٍ واضح، بنبرةٍ بلا افتعالٍ ولا مبالغة',
|
||||
'بمزاحٍ خفيفٍ لا يعترفُ بالخطأ'
|
||||
),
|
||||
(
|
||||
'ما ثمارُ الاعتذار؟',
|
||||
'حُسنُ الكلامِ وبذلُ السلامِ يستجلبانِ المغفرةَ والتوادّ',
|
||||
'لا ثمرةَ له سوى إنهاءِ الجلسة'
|
||||
),
|
||||
];
|
||||
|
||||
class ArabicApologyLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicApologyLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicApologyLabView> createState() => _ArabicApologyLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicApologyLabViewState extends State<ArabicApologyLabView> {
|
||||
static const List<String> _canonicalSpeech = ['أ', 'ب', 'ج'];
|
||||
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — بنية الحديث (مقدمة/عرض/خاتمة).
|
||||
final List<String> _placedSpeech = [];
|
||||
int _speechWrong = 0;
|
||||
int _flashSpeech = -1;
|
||||
bool _speechSolved = false;
|
||||
|
||||
// Activity 2 — التنغيم الصوتي.
|
||||
int? _tone; // 0 هادئ صادق، 1 ساخر، 2 متوتر، 3 مبالغ.
|
||||
|
||||
// Activity 3 — اعتذار الجلسة (ثقافة الاعتذار الخمس).
|
||||
final List<int?> _answers = List<int?>.filled(5, null);
|
||||
|
||||
bool get _apologySolved => _answers.every((a) => a == 0);
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _speechSolved;
|
||||
if (_activity == 1) return _tone != null;
|
||||
return _apologySolved;
|
||||
}
|
||||
|
||||
void _tapSpeech(String letter) {
|
||||
if (_speechSolved || _placedSpeech.contains(letter)) return;
|
||||
setState(() {
|
||||
_placedSpeech.add(letter);
|
||||
if (_placedSpeech.length == 3) {
|
||||
var correct = true;
|
||||
for (var i = 0; i < _canonicalSpeech.length; i++) {
|
||||
if (_placedSpeech[i] != _canonicalSpeech[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (correct) {
|
||||
_speechSolved = true;
|
||||
saqelTick();
|
||||
} else {
|
||||
_speechWrong++;
|
||||
_flashSpeech = _canonicalSpeech.indexOf(_placedSpeech[0]);
|
||||
_placedSpeech.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetSpeech() {
|
||||
setState(() {
|
||||
_placedSpeech.clear();
|
||||
_speechWrong = 0;
|
||||
_flashSpeech = -1;
|
||||
_speechSolved = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _pickTone(int v) {
|
||||
setState(() {
|
||||
_tone = v;
|
||||
});
|
||||
}
|
||||
|
||||
void _answerAxis(int i, int v) {
|
||||
setState(() {
|
||||
_answers[i] = v;
|
||||
if (_apologySolved) saqelTick();
|
||||
});
|
||||
}
|
||||
|
||||
void _resetApology() {
|
||||
setState(() {
|
||||
for (var i = 0; i < _answers.length; i++) {
|
||||
_answers[i] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _SpeechArcPainter(
|
||||
placed: List.of(_placedSpeech),
|
||||
canonical: _canonicalSpeech,
|
||||
solved: _speechSolved,
|
||||
flash: _flashSpeech,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _ToneScenePainter(tone: _tone),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _ApologyBridgePainter(answers: _answers),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['بنية الحديث', 'نبرة الصوت', 'اعتذار الجلسة'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
final nextLetter = _placedSpeech.length < 3
|
||||
? _canonicalSpeech[_placedSpeech.length]
|
||||
: null;
|
||||
c.addAll([
|
||||
Text(
|
||||
_speechSolved
|
||||
? 'أحسنتَ بناءَ حديثِك: مقدّمةٌ تُبيّن الفكرة، ثمّ عرضٌ بلغةٍ واضحةٍ يعرضُ الدروسَ، ثمّ خاتمة.'
|
||||
: 'رتِّب بناءَ الحديثِ قبلَ الاعتذار. اضغط الأساس «${nextLetter ?? 'التالي'}» الحالي.',
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _speechParts.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: _placedSpeech.contains(_speechParts[i].$1)
|
||||
? Colors.white.withValues(alpha: 0.06)
|
||||
: (_flashSpeech == i
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.02)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap:
|
||||
_speechSolved ? null : () => _tapSpeech(_speechParts[i].$1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _placedSpeech.contains(_speechParts[i].$1)
|
||||
? AppColors.saqelCyan.withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: _placedSpeech.contains(_speechParts[i].$1)
|
||||
? AppColors.saqelCyan
|
||||
: Colors.black.withValues(alpha: 0.35),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_speechParts[i].$1,
|
||||
style: TextStyle(
|
||||
color: _placedSpeech.contains(_speechParts[i].$1)
|
||||
? Colors.black
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_speechParts[i].$2,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_speechWrong >= 2 && !_speechSolved)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'خطوة صغيرة: يبدأ الحديثُ من المقدّمة «أ» ثمّ العرض «ب» ثمّ الخاتمة «ج».',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _resetSpeech,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('صفّر الترتيب'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أوظّفُ التنغيمَ بما يناسبُ أغراضَ الحديثِ والمشاعرِ دونَ افتعالٍ أو مبالغة. غيِّر النبرةَ ولاحظْ أثرَها على مَن يتلقّى.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabSegments<int>(
|
||||
labels: const ['هادئٌ صادقٌ', 'ساخِرٌ', 'متوتّرٌ', 'مُبالِغٌ'],
|
||||
values: const [0, 1, 2, 3],
|
||||
current: _tone ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: _pickTone,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_tone != null)
|
||||
LabPill(
|
||||
switch (_tone!) {
|
||||
0 =>
|
||||
'تنغيمٌ بلا افتعال: بذلُ السلامِ وحُسنُ الكلام — يُقبَلُ الاعتذارُ.',
|
||||
1 =>
|
||||
'السخريةُ تُحيي الجرحَ وتُبعدُ الآخر (انظر موقف الجلسة في الكتاب).',
|
||||
2 => 'التوتّرُ يُصعّبُ التقبُّلَ ويُضعفُ الأثر.',
|
||||
_ => 'المبالغةُ افتعالٌ يُضعفُ الصدقَ والثقةَ.',
|
||||
},
|
||||
color:
|
||||
_tone == 0 ? const Color(0xFF30D158) : const Color(0xFFFF9F0A),
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'في جلسةِ الأصدقاء سخِرَ أحدُ الحاضرين من رأيِ زميلٍ، فتسبّبَ بضحكِ الجميع، فانسحبَ الزميلُ وبانت أماراتُ الحزنِ على وجهِه، فسارعَ الساخرُ إلى الاعتذار. اخترْ سلوكَ الاعتذارِ المناسبَ في كلِّ محور.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _apologyAxes.length; i++) ...[
|
||||
Text(
|
||||
_apologyAxes[i].$1,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF94A3B8),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: [_apologyAxes[i].$2, _apologyAxes[i].$3],
|
||||
values: const [0, 1],
|
||||
current: _answers[i] ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: (v) => _answerAxis(i, v),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (_apologySolved)
|
||||
const LabPill(
|
||||
'اعتذارٌ مقبولٌ: بذلُ السلامِ وحُسنُ الكلام — التواصلُ عاد.',
|
||||
color: Color(0xFF30D158))
|
||||
else if (_answers.every((a) => a != null))
|
||||
const LabPill(
|
||||
'راجعْ الخيارات: الاعتذارُ المقبولُ صادقٌ وهادئٌ وسريعٌ إلى المعني مباشرةً.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _resetApology,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('إعادة المحاولة'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question: 'ما الذي يجعلُ الاعتذارَ مقبولًا في موقفِ الجلسة؟',
|
||||
options: const [
|
||||
'الصدقُ وحُسنُ الكلامِ وبذلُ السلامِ بنبرةٍ بلا افتعالٍ ولا مبالغة',
|
||||
'ارتفاعُ الصوتِ وإظهارُ الغضبِ والضيق',
|
||||
'السكوتُ حتى ينسى الجميعُ الموقفَ',
|
||||
'إلقاءُ اللومِ على الزميلِ نفسِه',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicApologyLabIdentity,
|
||||
titleAr: 'مختبر فنّ الاعتذار',
|
||||
subtitleAr: 'أتحدثُ بطلاقة — فنّ الاعتذار وقيمُ التسامح',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 10-12. بنية الحديث والنبرة وثقافة الاعتذار بحسب كلمات الكتاب وتُعتمد بعد المراجعة الأكاديمية. لا تُسجَّل أي إتقان محليًا.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — بنية الحديث (قوس المقدمة/العرض/الخاتمة).
|
||||
// ---------------------------------------------------------------------------
|
||||
class _SpeechArcPainter extends CustomPainter {
|
||||
final List<String> placed;
|
||||
final List<String> canonical;
|
||||
final bool solved;
|
||||
final int flash;
|
||||
|
||||
const _SpeechArcPainter({
|
||||
required this.placed,
|
||||
required this.canonical,
|
||||
required this.solved,
|
||||
required this.flash,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final baseY = h * 0.74;
|
||||
final span = w * 0.30;
|
||||
|
||||
// Speech arc.
|
||||
final arc = Path()
|
||||
..moveTo(cx - span, baseY)
|
||||
..quadraticBezierTo(cx, baseY - h * 0.38, cx + span, baseY);
|
||||
canvas.drawPath(
|
||||
arc,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.18)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2,
|
||||
);
|
||||
|
||||
for (var i = 0; i < canonical.length; i++) {
|
||||
final t = i / (canonical.length - 1);
|
||||
final x = cx + (2 * t - 1) * span;
|
||||
final y = baseY - h * 0.38 * (1 - (1 - (2 * t - 1).abs()) * 1.0);
|
||||
final isPlaced = placed.contains(canonical[i]) || (solved && i < 3);
|
||||
final isCurrent = placed.isNotEmpty && i == placed.length - 1 && !solved;
|
||||
final flashColor =
|
||||
flash == i ? const Color(0xFFFF375F) : AppColors.saqelCyan;
|
||||
final nodeColor = isPlaced
|
||||
? (flash == i ? const Color(0xFFFF375F) : AppColors.saqelCyan)
|
||||
: const Color(0xFF102035);
|
||||
|
||||
canvas.drawCircle(
|
||||
Offset(x, y),
|
||||
18,
|
||||
Paint()
|
||||
..color = nodeColor
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
Offset(x, y),
|
||||
18,
|
||||
Paint()
|
||||
..color = (isCurrent || isPlaced)
|
||||
? flashColor.withValues(alpha: 0.8)
|
||||
: Colors.white.withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = isCurrent ? 2.5 : 1.2,
|
||||
);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, y),
|
||||
canonical[i],
|
||||
const TextStyle(
|
||||
color: Colors.black, fontWeight: FontWeight.w900, fontSize: 13));
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, y + 30),
|
||||
isPlaced ? '${_speechShort[i]} ✓' : _speechShort[i],
|
||||
const TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 10.5, height: 1.2));
|
||||
}
|
||||
|
||||
if (solved) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.12),
|
||||
'مقدّمةٌ ← عرضٌ ← خاتمة: بعدَ الاعتذارِ يعودُ التواصلُ — «إنّ من موجباتِ المغفرةِ بذلَ السلامِ وحُسنَ الكلام».',
|
||||
const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86,
|
||||
);
|
||||
} else if (flash >= 0) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.10),
|
||||
'التسلسلُ غيرُ صحيح — أعدِ القراءة: تبدأُ المقدّمةُ ثمّ العرضُ ثمّ الخاتمة.',
|
||||
const TextStyle(
|
||||
color: Color(0xFFFF375F),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600),
|
||||
maxWidth: w * 0.8,
|
||||
);
|
||||
} else {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.10),
|
||||
'افحصْ عناصرَ الحديثِ أدناه ثمّ رتِّبْها على القوس.',
|
||||
const TextStyle(color: Colors.white70, fontSize: 11),
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.12, baseY + 8),
|
||||
Offset(w * 0.88, baseY + 8),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.12)
|
||||
..strokeWidth = 1.5);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _SpeechArcPainter old) =>
|
||||
old.placed != placed ||
|
||||
old.solved != solved ||
|
||||
old.flash != flash ||
|
||||
old.canonical != canonical;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — أثر النبرة الصوتية في مَن يسمع.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _ToneScenePainter extends CustomPainter {
|
||||
final int? tone;
|
||||
|
||||
const _ToneScenePainter({required this.tone});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final floor = h * 0.80;
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(0, floor),
|
||||
Offset(w, floor),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.15)
|
||||
..strokeWidth = 2);
|
||||
|
||||
// Speaker (right).
|
||||
final speaker = Offset(cx + w * 0.28, floor - h * 0.12);
|
||||
_drawPerson(canvas, speaker,
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.8));
|
||||
_centerText(canvas, speaker + const Offset(0, -62), '«أعتذِرُ إليك...»',
|
||||
const TextStyle(color: Colors.white, fontSize: 12.5),
|
||||
maxWidth: 190);
|
||||
|
||||
// Tone wave above the speaker.
|
||||
if (tone != null) {
|
||||
_drawWave(canvas, Offset(cx - w * 0.16, floor - h * 0.30),
|
||||
Offset(cx + w * 0.16, floor - h * 0.30), tone!);
|
||||
}
|
||||
|
||||
// Listener (left).
|
||||
final listener = Offset(cx - w * 0.28, floor - h * 0.12);
|
||||
final listenerColor = switch (tone) {
|
||||
0 => const Color(0xFF30D158),
|
||||
1 => const Color(0xFFFF375F),
|
||||
2 => const Color(0xFFFF9F0A),
|
||||
3 => const Color(0xFFFF9F0A),
|
||||
_ => Colors.white,
|
||||
};
|
||||
_drawPerson(canvas, listener, color: listenerColor.withValues(alpha: 0.85));
|
||||
if (tone != null) {
|
||||
final label = switch (tone) {
|
||||
0 => 'قُبِلَ: بذلُ السلامِ وحُسنُ الكلام',
|
||||
1 => 'انسحبَ الزميلُ بهدوءٍ من الجلسة',
|
||||
2 => 'التوتّرُ يُصعِّبُ التقبُّلَ',
|
||||
_ => 'المبالغةُ افتعالٌ يُضعفُ الثقةَ',
|
||||
};
|
||||
_centerText(
|
||||
canvas,
|
||||
listener + const Offset(0, -54),
|
||||
label,
|
||||
const TextStyle(
|
||||
color: Colors.white, fontSize: 11.5, fontWeight: FontWeight.w700),
|
||||
maxWidth: 190);
|
||||
}
|
||||
|
||||
// Reaction line between the two when a tone is chosen.
|
||||
if (tone != null) {
|
||||
final from = speaker + const Offset(-6, -24);
|
||||
final to = listener + const Offset(6, -24);
|
||||
canvas.drawLine(
|
||||
from,
|
||||
to,
|
||||
Paint()
|
||||
..color = listenerColor.withValues(alpha: 0.6)
|
||||
..strokeWidth = 1.6,
|
||||
);
|
||||
_arrow(canvas, from, to, listenerColor.withValues(alpha: 0.8));
|
||||
}
|
||||
}
|
||||
|
||||
void _drawWave(Canvas canvas, Offset a, Offset b, int tone) {
|
||||
final path = Path()..moveTo(a.dx, a.dy);
|
||||
const n = 16;
|
||||
final amp = switch (tone) {
|
||||
0 => 8.0,
|
||||
1 => 14.0,
|
||||
2 => 12.0,
|
||||
_ => 18.0,
|
||||
};
|
||||
final color = switch (tone) {
|
||||
0 => const Color(0xFF30D158),
|
||||
1 => const Color(0xFFFF375F),
|
||||
2 => const Color(0xFFFF9F0A),
|
||||
_ => const Color(0xFFFF9F0A),
|
||||
};
|
||||
for (var i = 1; i <= n; i++) {
|
||||
final t = i / n;
|
||||
final mag = (tone == 0) ? (t < 0.5 ? amp * 0.6 : amp) : amp + t * 8;
|
||||
final m = i.isEven ? mag : -mag;
|
||||
path.lineTo(a.dx + (b.dx - a.dx) * t, a.dy + m);
|
||||
}
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.8
|
||||
..strokeCap = StrokeCap.round,
|
||||
);
|
||||
_centerText(canvas, Offset((a.dx + b.dx) / 2, a.dy - 26), 'نبرة الصوت',
|
||||
const TextStyle(color: Color(0xFF94A3B8), fontSize: 10));
|
||||
}
|
||||
|
||||
void _drawPerson(Canvas canvas, Offset foot, {required Color color}) {
|
||||
final head = foot - const Offset(0, 30);
|
||||
canvas.drawCircle(head, 11, Paint()..color = color);
|
||||
canvas.drawLine(
|
||||
head + const Offset(0, 11),
|
||||
foot - const Offset(0, 8),
|
||||
Paint()
|
||||
..color = color
|
||||
..strokeWidth = 2.2
|
||||
..strokeCap = StrokeCap.round);
|
||||
canvas.drawLine(
|
||||
foot - const Offset(0, 8),
|
||||
foot,
|
||||
Paint()
|
||||
..color = color
|
||||
..strokeWidth = 2
|
||||
..strokeCap = StrokeCap.round);
|
||||
canvas.drawLine(
|
||||
foot - const Offset(0, 8),
|
||||
foot - const Offset(6, 0),
|
||||
Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1.8
|
||||
..strokeCap = StrokeCap.round);
|
||||
canvas.drawLine(
|
||||
head + const Offset(2, 4),
|
||||
head + const Offset(12, 0),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.55)
|
||||
..strokeWidth = 1.6
|
||||
..strokeCap = StrokeCap.round);
|
||||
}
|
||||
|
||||
void _arrow(Canvas canvas, Offset from, Offset to, Color color) {
|
||||
final path = Path()
|
||||
..moveTo(to.dx, to.dy)
|
||||
..lineTo(to.dx + 6, to.dy - 4)
|
||||
..lineTo(to.dx + 6, to.dy + 4)
|
||||
..close();
|
||||
canvas.drawPath(path, Paint()..color = color);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _ToneScenePainter old) => old.tone != tone;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — جسر الاعتذار: ألواح ثقافة الاعتذار الخمس.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _ApologyBridgePainter extends CustomPainter {
|
||||
final List<int?> answers;
|
||||
|
||||
const _ApologyBridgePainter({required this.answers});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final gapY = h * 0.30;
|
||||
final base = h * 0.76;
|
||||
final solved = answers.every((a) => a == 0);
|
||||
|
||||
// River.
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.1, base),
|
||||
Offset(w * 0.9, base),
|
||||
Paint()
|
||||
..color = const Color(0xFF0071E3).withValues(alpha: 0.45)
|
||||
..strokeWidth = 3);
|
||||
|
||||
// Two banks.
|
||||
_bank(canvas, Offset(w * 0.08, base - 40), 'المُسِيءُ');
|
||||
_bank(canvas, Offset(w * 0.92, base - 40), 'المتأذِّي');
|
||||
|
||||
// Bridge deck line.
|
||||
final deckY = base - gapY;
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.12, deckY),
|
||||
Offset(w * 0.88, deckY),
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2);
|
||||
|
||||
// Five planks.
|
||||
const n = 5;
|
||||
for (var i = 0; i < n; i++) {
|
||||
final fx = w * (0.12 + 0.76 * ((i + 0.5) / n));
|
||||
final filled = answers[i] != null && answers[i] == 0;
|
||||
final wrong = answers[i] != null && answers[i] == 1;
|
||||
final color = filled
|
||||
? const Color(0xFF30D158)
|
||||
: (wrong ? const Color(0xFFFF375F) : const Color(0xFF102035));
|
||||
final rect = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: Offset(fx, deckY), width: w * 0.11, height: 16),
|
||||
const Radius.circular(5),
|
||||
);
|
||||
canvas.drawRRect(
|
||||
rect, Paint()..color = color.withValues(alpha: wrong ? 0.35 : 1.0));
|
||||
if (wrong) {
|
||||
canvas.drawRRect(
|
||||
rect,
|
||||
Paint()
|
||||
..color = const Color(0xFFFF375F)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
if (solved) {
|
||||
// Sun of connection over the bridge.
|
||||
canvas.drawCircle(
|
||||
Offset(cx, deckY - 26), 14, Paint()..color = const Color(0xFFFF9F0A));
|
||||
_centerText(canvas, Offset(cx, deckY - 26), '✦',
|
||||
const TextStyle(color: Colors.black, fontWeight: FontWeight.w900));
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.10),
|
||||
'اكتملَ جسرُ الاعتذار: أعادَ حسنُ الكلامِ التواصلَ بينَ الجانبين.',
|
||||
const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800),
|
||||
maxWidth: w * 0.86,
|
||||
);
|
||||
} else {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.10),
|
||||
'اخترْ سلوكَ الاعتذارِ المناسبَ لكلِّ محورٍ ليتّصلَ الجسرُ بالجدولِ أعلاه.',
|
||||
const TextStyle(color: Colors.white70, fontSize: 11),
|
||||
maxWidth: w * 0.86,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _bank(Canvas canvas, Offset center, String label) {
|
||||
canvas.drawCircle(center, 26, Paint()..color = const Color(0xFF102035));
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
26,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5);
|
||||
_centerText(canvas, center + const Offset(0, 44), label,
|
||||
const TextStyle(color: Colors.white, fontSize: 11));
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _ApologyBridgePainter old) =>
|
||||
!listEquals(old.answers, answers);
|
||||
}
|
||||
|
||||
bool listEquals<T>(List<T?> a, List<T?> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — WRITING AN APOLOGY & TOLERANCE LETTER
|
||||
/// Lesson: أكتبُ محتوى (كتابة رسالة اعتذار وتسامح)
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_01/lesson_04.md
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// FIDELITY NOTE (recorded honestly): the spec binds lesson_04 to pages 22-24,
|
||||
/// but in the extracted corpus those pages contain «أسلوب الشرط», while the
|
||||
/// letter-writing content of this lesson (رسالة زينة إلى سلمى and the elements
|
||||
/// of a successful apology letter) is actually extracted at the END of
|
||||
/// lesson_03.md (pages 19-21, the PDF's "الدرس الرابع: أكتب محتوى رسالة
|
||||
/// شخصية إلكترونية"). This lab models ONLY the letter content, taken verbatim
|
||||
/// from that extraction. The page-range mismatch is flagged for the academic
|
||||
/// reviewer before any release.
|
||||
/// Nothing invented here: letter elements, the success criteria (تأشير
|
||||
/// الخطأ، إظهار الأذى، الندم وطلب الصفح، اللغة البسيطة وأدوات الربط) and the
|
||||
/// letter paragraphs are all from the source extraction. No demo mastery.
|
||||
/// ============================================================================
|
||||
|
||||
// Letter elements of the personal apology letter (from the زينة→سلمى sample).
|
||||
const List<(String, String, String)> _letterElements = [
|
||||
(
|
||||
'«إلى: salma.mohammed@gmail.com»',
|
||||
'عنصرُ المستلِم',
|
||||
'عنصرُ جدولِ المحتويات'
|
||||
),
|
||||
('«موضوع: اعتذار»', 'عنصرُ الموضوع', 'عنصرُ التذييل'),
|
||||
(
|
||||
'«صديقتي العزيزةُ سلمى، السلامُ عليكم»',
|
||||
'عنصرُ التحيةِ والسلامِ',
|
||||
'عنصرُ الخاتمة'
|
||||
),
|
||||
(
|
||||
'«أودُّ أن أعتذرَ إليكِ عمّا بدَرَ مني أمس»',
|
||||
'عنصرُ المقدمة',
|
||||
'عنصرُ العنوان'
|
||||
),
|
||||
(
|
||||
'«أعترفُ لكِ بخطئي، وأنا نادمةٌ على ما فعلتُ»',
|
||||
'عنصرُ صلبِ الرسالةِ: الاعتذار',
|
||||
'عنصرُ التوقيع'
|
||||
),
|
||||
('«صديقتُكِ المحبّة: زينة حسين»', 'عنصرُ الخاتمةِ والتوقيع', 'عنصرُ التذييل'),
|
||||
];
|
||||
|
||||
// The four paragraphs of the letter in their true order.
|
||||
const List<(String, String)> _letterParts = [
|
||||
('أ', '«صديقتي العزيزةُ سلمى، السلامُ عليكم.» — التحية'),
|
||||
('ب', '«أودُّ أن أعتذرَ إليكِ عما بدَرَ مني أمس.» — المقدمة'),
|
||||
(
|
||||
'ج',
|
||||
'«أعترفُ لكِ بخطئي، وأنا نادمةٌ على ما فعلتُ ولم أتعمَّدِ الإساءةَ الشخصيةَ إليك.» — صلبُ الرسالة'
|
||||
),
|
||||
('د', '«صديقتُكِ المحبّة: زينة حسين.» — الخاتمةُ والتوقيع'),
|
||||
];
|
||||
|
||||
class ArabicApologyLetterLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicApologyLetterLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicApologyLetterLabView> createState() =>
|
||||
_ArabicApologyLetterLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicApologyLetterLabViewState
|
||||
extends State<ArabicApologyLetterLabView> {
|
||||
static const List<String> _canonical = ['أ', 'ب', 'ج', 'د'];
|
||||
// Display order is scrambled so the ordering is a real task.
|
||||
static const List<int> _displayOrder = [2, 0, 3, 1]; // ج، أ، د، ب
|
||||
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — عناصر الرسالة الشخصية.
|
||||
final List<int?> _elements = List<int?>.filled(6, null);
|
||||
|
||||
// Activity 2 — معايير رسالة الاعتذار الناجحة.
|
||||
bool _pointOut = false; // أشير إلى الخطأ بوضوح
|
||||
bool _showHarm = false; // أُظهر الأذى الذي سبّبه
|
||||
bool _regret = false; // أُعبّر عن الندم وطلب الصفح
|
||||
bool _overExplain = false; // خاطئ: تبريرٌ مبالغٌ فيه
|
||||
bool _noGreeting = false; // خاطئ: البدء بلا تحية
|
||||
|
||||
// Activity 3 — ترتيب فقرات الرسالة.
|
||||
final List<String> _placed = [];
|
||||
int _wrongCount = 0;
|
||||
int _flash = -1;
|
||||
bool _solved = false;
|
||||
|
||||
bool get _elementsSolved => _elements.every((e) => e == 0);
|
||||
|
||||
bool get _criteriaDone =>
|
||||
_pointOut && _showHarm && _regret && !_overExplain && !_noGreeting;
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _elementsSolved;
|
||||
if (_activity == 1) return _criteriaDone;
|
||||
return _solved;
|
||||
}
|
||||
|
||||
void _tapPart(String letter) {
|
||||
if (_solved || _placed.contains(letter)) return;
|
||||
setState(() {
|
||||
_placed.add(letter);
|
||||
if (_placed.length == 4) {
|
||||
var correct = true;
|
||||
for (var i = 0; i < _canonical.length; i++) {
|
||||
if (_placed[i] != _canonical[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (correct) {
|
||||
_solved = true;
|
||||
saqelTick();
|
||||
} else {
|
||||
_wrongCount++;
|
||||
_flash = _canonical.indexOf(_placed[0]);
|
||||
_placed.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetParts() {
|
||||
setState(() {
|
||||
_placed.clear();
|
||||
_wrongCount = 0;
|
||||
_flash = -1;
|
||||
_solved = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _LetterSheetPainter(elements: _elements),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _EnvelopePainter(
|
||||
pointOut: _pointOut,
|
||||
showHarm: _showHarm,
|
||||
regret: _regret,
|
||||
overExplain: _overExplain,
|
||||
noGreeting: _noGreeting,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _LetterChainPainter(
|
||||
placed: List.of(_placed),
|
||||
canonical: _canonical,
|
||||
solved: _solved,
|
||||
flash: _flash,
|
||||
displayOrder: _displayOrder,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['عناصرُ الرسالة', 'معاييرُ الاعتذار', 'ترتيبُ الرسالة'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أملأ خريطةَ عناصرِ الرسالةِ الشخصيةِ من نموذجِ رسالةِ زينةَ إلى سلمى.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _letterElements.length; i++) ...[
|
||||
Text(
|
||||
_letterElements[i].$1,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: [_letterElements[i].$2, _letterElements[i].$3],
|
||||
values: const [0, 1],
|
||||
current: _elements[i] ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: (v) => setState(() {
|
||||
_elements[i] = v;
|
||||
if (_elementsSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (_elementsSolved)
|
||||
const LabPill('رسالتُك اكتملت: كلُّ عنصرٍ في موضعِه الصحيح ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'ما الذي يجعلُ رسالةَ الاعتذارِ ناجحةً؟ فعِّلْ معاييرَ الكتابةِ الصحيحة وأوقفِ السلوكَ الخاطئ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabToggle(
|
||||
label: 'أشيرُ إلى الخطأِ بوضوحٍ وأصِفُ ما اقترفتُه',
|
||||
value: _pointOut,
|
||||
onChanged: (v) => setState(() => _pointOut = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أُظهرُ الأذى الذي سبّبَه خطئي في الطرفِ الآخر',
|
||||
value: _showHarm,
|
||||
onChanged: (v) => setState(() => _showHarm = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أُعبّرُ عن الندمِ وطلَبِ الصفحِ في الخاتمة',
|
||||
value: _regret,
|
||||
onChanged: (v) => setState(() => _regret = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أُطيلُ في التبريرِ والدفاعِ عن كلِّ خطأ (سلوكٌ خاطئ)',
|
||||
value: _overExplain,
|
||||
onChanged: (v) => setState(() => _overExplain = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أبدأُ رسالتي فجأةً من غيرِ تحيةٍ ولا مقدمات (سلوكٌ خاطئ)',
|
||||
value: _noGreeting,
|
||||
onChanged: (v) => setState(() => _noGreeting = v),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_criteriaDone)
|
||||
const LabPill(
|
||||
'معاييرُ رسالةِ الاعتذارِ الناجحةِ مكتملة: خطأٌ وأذىً وندمٌ ولطف ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'أكمل: وضّحِ الخطأ، وأظهِرِ الأذى، وعنِ النَّدمِ، بلغةٍ بسيطة، وتجنّبِ التبرير.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else {
|
||||
c.addAll(const [
|
||||
Text(
|
||||
'أتممتَ ترتيبَ رسالةِ الاعتذار: تحيةٌ ثمّ مقدمةٌ ثمّ صلبٌ ثمّ خاتمة.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
]);
|
||||
if (!_solved) {
|
||||
c.add(Text(
|
||||
'اضغط الفقرة «${_canonical[_placed.length]}» الحالية (رتِّب التحية فالمقدمة فالصلب فالخاتمة).',
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
));
|
||||
c.add(const SizedBox(height: 10));
|
||||
}
|
||||
c.addAll([
|
||||
for (final idx in _displayOrder)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: _placed.contains(_letterParts[idx].$1)
|
||||
? Colors.white.withValues(alpha: 0.06)
|
||||
: (_flash == idx
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.02)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _solved ? null : () => _tapPart(_letterParts[idx].$1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _placed.contains(_letterParts[idx].$1)
|
||||
? AppColors.saqelCyan.withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: _placed.contains(_letterParts[idx].$1)
|
||||
? AppColors.saqelCyan
|
||||
: Colors.black.withValues(alpha: 0.35),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_letterParts[idx].$1,
|
||||
style: TextStyle(
|
||||
color: _placed.contains(_letterParts[idx].$1)
|
||||
? Colors.black
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_letterParts[idx].$2,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_wrongCount >= 2 && !_solved)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'خطوة صغيرة: تبدأ الرسالةُ بالتحيةِ ثمّ المقدمةِ ثمّ صلبِ الاعتذارِ ثمّ الخاتمة والتوقيع.',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _resetParts,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('صفّر الترتيب'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question: 'أيُّ عبارةٍ تُظهرُ أثرَ خطأِ زينةَ في صديقتِها سلمى؟',
|
||||
options: const [
|
||||
'«مما سبّب في إحراجِكِ أمامَ زميلاتِكِ في المجموعة» — إظهارُ الأذى',
|
||||
'«سلامُ اللهِ عليكِ وزملائكِ جميعًا»',
|
||||
'تكرارُ كلمةِ «اعتذار» في كلِّ سطر',
|
||||
'حذفُ التحيةِ والبدءُ مباشرةً',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicApologyLetterLabIdentity,
|
||||
titleAr: 'مختبر رسالة الاعتذار',
|
||||
subtitleAr: 'أكتبُ محتوى — رسالةُ اعتذارٍ وتسامح (زينة إلى سلمى)',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 22-24 بحسب بطاقة المواصفة. ملاحظة مطابقة: نص الدرس (الرسالة وعناصرها) مستخلص فعليًا في نهاية lesson_03.md (صفحات 19-21) وسياقات 22-24 تحوي أسلوب الشرط — يحتاج قرار مرجع الأكاديمي قبل النشر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — بطاقة عناصر الرسالة على ورقة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _LetterSheetPainter extends CustomPainter {
|
||||
final List<int?> elements;
|
||||
|
||||
const _LetterSheetPainter({required this.elements});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final solved = elements.every((e) => e == 0);
|
||||
|
||||
// Sheet.
|
||||
final sheet = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(
|
||||
center: Offset(cx, h * 0.50), width: w * 0.78, height: h * 0.52),
|
||||
const Radius.circular(10));
|
||||
canvas.drawRRect(sheet, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
sheet,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.30)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
|
||||
// Six element slots.
|
||||
const n = 6;
|
||||
final top = h * 0.50 - h * 0.52 / 2 + 16;
|
||||
final slotH = (h * 0.52 - 32) / n;
|
||||
for (var i = 0; i < n; i++) {
|
||||
final slot =
|
||||
Rect.fromLTWH(w * 0.14, top + i * slotH, w * 0.72, slotH - 6);
|
||||
final correct = elements[i] == 0;
|
||||
final wrong = elements[i] == 1;
|
||||
final color = correct
|
||||
? const Color(0xFF30D158)
|
||||
: (wrong ? const Color(0xFFFF375F) : const Color(0xFF102035));
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(slot, const Radius.circular(6)),
|
||||
Paint()..color = color.withValues(alpha: wrong ? 0.35 : 1.0),
|
||||
);
|
||||
if (correct || wrong) {
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(slot, const Radius.circular(6)),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.45)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
}
|
||||
_centerText(
|
||||
canvas,
|
||||
slot.center,
|
||||
'${i + 1}',
|
||||
const TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.w800, fontSize: 10));
|
||||
}
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.16),
|
||||
solved
|
||||
? 'الرسالةُ الشخصيةُ مكتملةُ العناصر ✓'
|
||||
: 'ملؤُّ عناصرِ الرسالةِ من النموذجِ المكتوب',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _LetterSheetPainter old) =>
|
||||
!listEquals(old.elements, elements);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — ظرف الرسالة ومواصفاتها.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _EnvelopePainter extends CustomPainter {
|
||||
final bool pointOut;
|
||||
final bool showHarm;
|
||||
final bool regret;
|
||||
final bool overExplain;
|
||||
final bool noGreeting;
|
||||
|
||||
const _EnvelopePainter({
|
||||
required this.pointOut,
|
||||
required this.showHarm,
|
||||
required this.regret,
|
||||
required this.overExplain,
|
||||
required this.noGreeting,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final top = h * 0.28;
|
||||
|
||||
final env = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(
|
||||
center: Offset(cx, top + 34), width: w * 0.66, height: 68),
|
||||
const Radius.circular(8));
|
||||
canvas.drawRRect(env, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
env,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.30)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
final flap = Path()
|
||||
..moveTo(cx - w * 0.33, top + 2)
|
||||
..lineTo(cx, top + 40)
|
||||
..lineTo(cx + w * 0.33, top + 2)
|
||||
..close();
|
||||
canvas.drawPath(flap, Paint()..color = const Color(0xFF102035));
|
||||
canvas.drawPath(
|
||||
flap,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.30)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
|
||||
final goodCount =
|
||||
(pointOut ? 1 : 0) + (showHarm ? 1 : 0) + (regret ? 1 : 0);
|
||||
final badCount = (overExplain ? 1 : 0) + (noGreeting ? 1 : 0);
|
||||
final done = pointOut && showHarm && regret && !overExplain && !noGreeting;
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.16),
|
||||
done
|
||||
? 'الظرفُ صالحٌ للإرسال — رسالةُ اعتذارٍ ناجحة ✓'
|
||||
: 'وازِنْ معاييرَ الكتابةِ الصحيحة',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.84);
|
||||
|
||||
// Indicator bars.
|
||||
_bar(canvas, Offset(cx, h * 0.58), 'وضوحُ الخطأ وأثرُه والندمُ وطلبُ الصفح',
|
||||
goodCount, 3, const Color(0xFF30D158));
|
||||
_bar(canvas, Offset(cx, h * 0.72), 'تبريرٌ مبالغٌ فيه / بدءٌ بلا تحية',
|
||||
badCount, 2, const Color(0xFFFF375F));
|
||||
}
|
||||
|
||||
void _bar(Canvas canvas, Offset cx, String label, int count, int total,
|
||||
Color color) {
|
||||
final start = cx.dx - 80.0;
|
||||
const step = 78.0;
|
||||
for (var i = 0; i < total; i++) {
|
||||
final rect = Rect.fromLTWH(start + i * step, cx.dy - 20, 72, 10);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, const Radius.circular(5)),
|
||||
Paint()
|
||||
..color = i < count ? color : Colors.white.withValues(alpha: 0.10));
|
||||
}
|
||||
_centerText(canvas, Offset(cx.dx, cx.dy), label,
|
||||
const TextStyle(color: Colors.white70, fontSize: 10),
|
||||
maxWidth: 220);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _EnvelopePainter old) =>
|
||||
old.pointOut != pointOut ||
|
||||
old.showHarm != showHarm ||
|
||||
old.regret != regret ||
|
||||
old.overExplain != overExplain ||
|
||||
old.noGreeting != noGreeting;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — سلسلة فقرات الرسالة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _LetterChainPainter extends CustomPainter {
|
||||
final List<String> placed;
|
||||
final List<String> canonical;
|
||||
final bool solved;
|
||||
final int flash;
|
||||
final List<int> displayOrder;
|
||||
|
||||
const _LetterChainPainter({
|
||||
required this.placed,
|
||||
required this.canonical,
|
||||
required this.solved,
|
||||
required this.flash,
|
||||
required this.displayOrder,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final y = h * 0.62;
|
||||
|
||||
// Chain line.
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.12, y),
|
||||
Offset(w * 0.88, y),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.20)
|
||||
..strokeWidth = 2);
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
final letter = canonical[displayOrder[i]];
|
||||
final x = w * (0.14 + 0.24 * i);
|
||||
final isPlaced = placed.contains(letter);
|
||||
final color = isPlaced
|
||||
? (flash == canonical.indexOf(letter)
|
||||
? const Color(0xFFFF375F)
|
||||
: AppColors.saqelCyan)
|
||||
: const Color(0xFF102035);
|
||||
canvas.drawCircle(Offset(x, y), 15, Paint()..color = color);
|
||||
canvas.drawCircle(
|
||||
Offset(x, y),
|
||||
15,
|
||||
Paint()
|
||||
..color = isPlaced
|
||||
? Colors.white.withValues(alpha: 0.7)
|
||||
: Colors.white.withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, y),
|
||||
letter,
|
||||
const TextStyle(
|
||||
color: Colors.black, fontWeight: FontWeight.w900, fontSize: 12));
|
||||
}
|
||||
|
||||
if (solved) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.18),
|
||||
'تحيةٌ ومقدمةٌ وصلبُ الاعتذارِ وخاتمة — أشادتْ بها صديقتُها فتقابلتُها باليسير.',
|
||||
const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.84);
|
||||
} else {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.18),
|
||||
'فقراتُ الرسالةِ مترابطةٌ كأسنانِ السلسلةِ — اخترْها بالترتيبِ الصحيح.',
|
||||
const TextStyle(color: Colors.white70, fontSize: 11),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _LetterChainPainter old) =>
|
||||
old.placed != placed || old.solved != solved || old.flash != flash;
|
||||
}
|
||||
|
||||
bool listEquals<T>(List<T?> a, List<T?> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — CONDITIONAL STYLE (أسلوب الشرط)
|
||||
/// Lesson: أبني لغتي (1): أسلوبُ الشَّرطِ
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_01/lesson_05.md (pages 25-28)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Built ONLY on extractable facts from the source pages:
|
||||
/// - Dividing the tools into جازمة / غير جازمة (page 26 activities).
|
||||
/// - The three أركان: أداةُ الشرط، فعلُ الشرط، جوابُ الشرط (page 26-27).
|
||||
/// - نموذج الإعراب of «تأتِهِ» (page 27) and the analysis of
|
||||
/// «أيُّ خطأٍ تخطِئْ فعليك إصلاحُهُ» (page 26).
|
||||
/// - جملة «إذا أقبلتِ الدنيا على إنسانٍ أعارتْهُ محاسنَ غيره...» (page 26).
|
||||
/// - The أتأمل relation («دخل عليها زكريا» / «وجد عندها رزقاً») (page 25)
|
||||
/// used as the checkpoint.
|
||||
/// OCR is damaged in places; nothing was invented where the source is
|
||||
/// unclear (blanks/أكمل exercises and verse glyphs are avoided). Flagged for
|
||||
/// the academic reviewer before any release.
|
||||
/// ============================================================================
|
||||
|
||||
// أدوات مشتقة من صفحات الدرس، مع تصنيفها (جازمة / غير جازمة).
|
||||
const List<(String, int)> _conditionalTools = [
|
||||
('إنْ', 0), // جازمة
|
||||
('مَن', 0), // جازمة
|
||||
('متى', 0), // جازمة
|
||||
('أيُّ', 0), // جازمة
|
||||
('مهما', 0), // جازمة
|
||||
('إذا', 1), // غير جازمة
|
||||
('لولا', 1), // غير جازمة
|
||||
('كلَّما', 1), // غير جازمة
|
||||
];
|
||||
|
||||
// نموذج إعراب (تأتِهِ) من صفحة 27.
|
||||
const List<(String, String, String)> _iramRows = [
|
||||
(
|
||||
'«تأتِهِ»',
|
||||
'فعلٌ مضارعٌ مجزومٌ، وعلامةُ جزمِه حذفُ حرفِ العلةِ من آخرِه',
|
||||
'فعلٌ ماضٍ مرفوعٌ بالضمةِ'
|
||||
),
|
||||
(
|
||||
'الفاعلُ في «تأتِهِ»',
|
||||
'ضميرٌ مستترٌ تقديرُه (أنت)',
|
||||
'اسمٌ ظاهرٌ محلهُ الرفعُ'
|
||||
),
|
||||
(
|
||||
'الهاءُ في «تأتِهِ»',
|
||||
'ضميرٌ متصلٌّ مبنيٌّ على الكسرِ في محلِّ نصبٍ مفعولٌ به',
|
||||
'حرفُ جرٍّ زائدٌ'
|
||||
),
|
||||
];
|
||||
|
||||
// جدول تحليل أركان الشرط لعبارة صفحة 26.
|
||||
const List<(String, String, String)> _ruknRows = [
|
||||
('أداةُ الشرطِ', '«أيُّ»', '«خطأٍ»'),
|
||||
('فعلُ الشرطِ', '«تخطِئْ»', '«يُصلِحُ»'),
|
||||
('جوابُ الشرطِ', '«فعليك إصلاحُهُ»', '«أيُّ خطأٍ»'),
|
||||
];
|
||||
|
||||
class ArabicConditionalLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicConditionalLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicConditionalLabView> createState() =>
|
||||
_ArabicConditionalLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicConditionalLabViewState extends State<ArabicConditionalLabView> {
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — تصنيف الأدوات.
|
||||
final List<int?> _tools = List<int?>.filled(_conditionalTools.length, null);
|
||||
|
||||
// Activity 2 — نموذج الإعراب.
|
||||
final List<int?> _iram = List<int?>.filled(3, null);
|
||||
|
||||
// Activity 3 — جدول الأركان.
|
||||
final List<int?> _rukns = List<int?>.filled(3, null);
|
||||
|
||||
bool get _toolsSolved {
|
||||
for (var i = 0; i < _tools.length; i++) {
|
||||
if (_tools[i] != _conditionalTools[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _iramSolved => _iram.every((e) => e == 0);
|
||||
bool get _ruknsSolved => _rukns.every((e) => e == 0);
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _toolsSolved;
|
||||
if (_activity == 1) return _iramSolved;
|
||||
return _ruknsSolved;
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _BinsPainter(tools: List.of(_tools)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _IramCardPainter(iram: List.of(_iram)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _RuknTablePainter(rukns: List.of(_rukns)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['جرِّب التصنيف', 'أتمَّ الإعراب', 'حلِّلِ الأركان'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'صنِّفْ أدواتِ الشرطِ الواردةَ في الدرس: جازمةٌ أم غيرُ جازمة؟',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _conditionalTools.length; i++) ...[
|
||||
Text(
|
||||
'«${_conditionalTools[i].$1}» — أداةُ شرطٍ ...',
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: const ['جازمة', 'غيرُ جازمة'],
|
||||
values: const [0, 1],
|
||||
current: _tools[i] ?? -1,
|
||||
accent: _tools[i] == _conditionalTools[i].$2
|
||||
? const Color(0xFF30D158)
|
||||
: (_tools[i] == null
|
||||
? const Color(0xFF0071E3)
|
||||
: const Color(0xFFFF375F)),
|
||||
onSelected: (v) => setState(() {
|
||||
_tools[i] = v;
|
||||
if (_toolsSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
if (_toolsSolved)
|
||||
const LabPill('صنَّفْتَ أدواتِ الشرطِ: جازمةٌ وغيرُ جازمةٍ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'مثالٌ جازمة: «إن»، «مَن»، «متى». غيرُ الجازمة: «إذا»، «لو»، «لولا»، «كلَّما».',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أكمل نموذجَ إعراب «متى تأتِهِ تعشُو إلى ضوءِ نارِهِ» كما في الدرس.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _iramRows.length; i++) ...[
|
||||
Text(
|
||||
'«${_iramRows[i].$1}»',
|
||||
style: const TextStyle(
|
||||
color: Colors.white70, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: [
|
||||
_iramRows[i].$2,
|
||||
_iramRows[i].$3,
|
||||
],
|
||||
values: const [0, 1],
|
||||
current: _iram[i] ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: (v) => setState(() {
|
||||
_iram[i] = v;
|
||||
if (_iramSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (_iramSolved)
|
||||
const LabPill('أتممتَ نموذجَ إعرابِ (تأتِهِ) كما وردَ في الدرس ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'حلِّل «أيُّ خطأٍ تخطِئْ فعليك إصلاحُهُ» إلى أركانِ أسلوبِ الشرطِ الثلاثة.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _ruknRows.length; i++) ...[
|
||||
Text(
|
||||
'«${_ruknRows[i].$1}»',
|
||||
style: const TextStyle(
|
||||
color: Colors.white70, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: [
|
||||
_ruknRows[i].$2,
|
||||
_ruknRows[i].$3,
|
||||
],
|
||||
values: const [0, 1],
|
||||
current: _rukns[i] ?? -1,
|
||||
accent: const Color(0xFF30D158),
|
||||
onSelected: (v) => setState(() {
|
||||
_rukns[i] = v;
|
||||
if (_ruknsSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (_ruknsSolved)
|
||||
const LabPill('أكملتَ جدولَ تحليلِ أركانِ الشرطِ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الركنانِ: أداةُ الشرطِ ثمّ فعلُ الشرطِ ثمّ جوابُ الشرطِ.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question:
|
||||
'ما العلاقةُ بين جمليتَي (دخَلَ عليها زكريا) و (وجدَ عندَها رزقاً)؟',
|
||||
options: const [
|
||||
'تحقّقُ شرطِ جملةِ «دخلَ» في جملة «وجدَ»: فجازَ أن تُذكرَ أداةُ الجوابِ وبها وقعتِ النتيجةُ',
|
||||
'جملتانِ مستقلتانِ لا علاقةَ بينهما',
|
||||
'النتيجةُ تقدَّمُ على الشرطِ دائمًا',
|
||||
'لا يجوزُ ربطُهما بأداةٍ شرطيةٍ قطّ',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicConditionalLabIdentity,
|
||||
titleAr: 'مختبر أسلوب الشرط',
|
||||
subtitleAr: 'أبني لغتي (1) — أسلوبُ الشَّرطِ: أدواتٌ وأركانٌ وإعراب',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 25-28. ملاحظة مطابقة: التصنيف والأركان ونموذج إعراب «تأتِهِ» منقولون من نص الصفحات؛ حديثُ الآيات المتوضع بالرموز وفراغاتُ التمرينات غيرُ المكتملة خُطِّي عنها حتى المراجعة الأكاديمية.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — صنف الأدوات: جازمة / غير جازمة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _BinsPainter extends CustomPainter {
|
||||
final List<int?> tools;
|
||||
|
||||
const _BinsPainter({required this.tools});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final y1 = h * 0.24;
|
||||
final solved = _allCorrect();
|
||||
|
||||
_bin(canvas, Offset(w * 0.25, y1), 'أداةُ شرطٍ جازمةٌ',
|
||||
const Color(0xFF30D158), tools.where((t) => t == 0).length);
|
||||
_bin(canvas, Offset(w * 0.75, y1), 'أداةُ شرطٍ غيرُ جازمةٍ',
|
||||
const Color(0xFFFF9F0A), tools.where((t) => t == 1).length);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.14),
|
||||
solved
|
||||
? 'كلُّ أداةٍ في خانتها الصحيحة ✓'
|
||||
: 'رتِّبْ كلَّ أداةٍ تحتَ تصنيفِها',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.88),
|
||||
'الجازمةُ تفيدُ ربطَ الجوابِ بالشرطِ مع الجزمِ؛ وغيرُ الجازمةِ لا تجزمُ.',
|
||||
const TextStyle(color: Colors.white54, fontSize: 10.5),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
bool _allCorrect() {
|
||||
for (var i = 0; i < tools.length; i++) {
|
||||
if (tools[i] != _conditionalTools[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _bin(Canvas canvas, Offset c, String label, Color color, int count) {
|
||||
final r = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: c, width: 150, height: 120),
|
||||
const Radius.circular(14));
|
||||
canvas.drawRRect(r, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
r,
|
||||
Paint()
|
||||
..color = color.withValues(alpha: 0.55)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4);
|
||||
_centerText(canvas, Offset(c.dx, c.dy - 44), label,
|
||||
TextStyle(color: color, fontSize: 11, fontWeight: FontWeight.w800),
|
||||
maxWidth: 140);
|
||||
for (var i = 0; i < count; i++) {
|
||||
final x = c.dx - 45 + (i % 3) * 42;
|
||||
final yy = c.dy - 6 + (i ~/ 3) * 34;
|
||||
canvas.drawCircle(Offset(x, yy), 9, Paint()..color = color);
|
||||
}
|
||||
_centerText(canvas, Offset(c.dx, c.dy + 48), '$count/8',
|
||||
const TextStyle(color: Colors.white, fontSize: 12));
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 3,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _BinsPainter old) =>
|
||||
!listEquals(old.tools, tools);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — بطاقة نموذج الإعراب.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _IramCardPainter extends CustomPainter {
|
||||
final List<int?> iram;
|
||||
|
||||
const _IramCardPainter({required this.iram});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final cardTop = h * 0.16;
|
||||
final cardH = h * 0.56;
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.08, cardTop, w * 0.84, cardH),
|
||||
const Radius.circular(12));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.30)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, cardTop + 24),
|
||||
'متى تأتِهِ تعشُو إلى ضوءِ نارِهِ',
|
||||
const TextStyle(
|
||||
color: Colors.white, fontSize: 13, fontWeight: FontWeight.w800),
|
||||
maxWidth: w * 0.76);
|
||||
|
||||
const rows = ['«تأتِهِ»', 'الفاعلُ', 'الهاءُ'];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
final y = cardTop + 60 + i * (cardH - 84) / 3;
|
||||
final done = iram[i] == 0;
|
||||
final wrong = iram[i] == 1;
|
||||
final slot = Rect.fromCenter(
|
||||
center: Offset(cx, y),
|
||||
width: w * 0.74,
|
||||
height: (cardH - 84) / 3 - 10);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(slot, const Radius.circular(8)),
|
||||
Paint()
|
||||
..color = done
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.16)
|
||||
: (wrong
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.16)
|
||||
: const Color(0xFF102035)));
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(slot, const Radius.circular(8)),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: done ? 0.6 : 0.15)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, y - 12),
|
||||
rows[i],
|
||||
const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700));
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, y + 11),
|
||||
done
|
||||
? 'تمَّ: ${_iramRows[i].$2}'
|
||||
: (wrong ? 'لا: اخترِ الإعرابَ الواردَ في الصفحة' : '…'),
|
||||
TextStyle(
|
||||
color: done
|
||||
? const Color(0xFF30D158)
|
||||
: (wrong ? const Color(0xFFFF375F) : Colors.white54),
|
||||
fontSize: 10.5),
|
||||
maxWidth: w * 0.68);
|
||||
}
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _IramCardPainter old) =>
|
||||
!listEquals(old.iram, iram);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — جدول أركان الشرط.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _RuknTablePainter extends CustomPainter {
|
||||
final List<int?> rukns;
|
||||
|
||||
const _RuknTablePainter({required this.rukns});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final solved = rukns.every((r) => r == 0);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.14),
|
||||
solved
|
||||
? '«أيُّ خطأٍ تخطِئْ فعليك إصلاحُهُ» — أركانُها الثلاثةُ ✓'
|
||||
: 'أركانُ الشرطِ في الجدولِ: أداةٌ ثمّ فعلٌ ثمّ جوابٌ',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
const labels = ['أداةُ الشرطِ', 'فعلُ الشرطِ', 'جوابُ الشرطِ'];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
final y = h * (0.30 + i * 0.16);
|
||||
final done = rukns[i] == 0;
|
||||
final wrong = rukns[i] == 1;
|
||||
const cellH = 34.0;
|
||||
final labelRect = Rect.fromLTWH(w * 0.22, y, w * 0.30, cellH);
|
||||
final valueRect = Rect.fromLTWH(w * 0.46, y, w * 0.40, cellH);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(labelRect, const Radius.circular(7)),
|
||||
Paint()..color = const Color(0xFF102035));
|
||||
_centerText(canvas, labelRect.center, labels[i],
|
||||
const TextStyle(color: Colors.white70, fontSize: 11),
|
||||
maxWidth: labelRect.width - 8);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(valueRect, const Radius.circular(7)),
|
||||
Paint()
|
||||
..color = done
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.16)
|
||||
: (wrong
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.16)
|
||||
: const Color(0xFF0B1728)));
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(valueRect, const Radius.circular(7)),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: done ? 0.6 : 0.15)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
_centerText(
|
||||
canvas,
|
||||
valueRect.center,
|
||||
done ? '«أيّ»' : (i == 0 ? '…' : '…'),
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white38,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: valueRect.width - 8);
|
||||
}
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.90),
|
||||
'جوابُ الشرطِ قد يكونُ جملةً اسميةً أو فعليةً فعلُها ماضٍ (أستزيد).',
|
||||
const TextStyle(color: Colors.white54, fontSize: 10.5),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _RuknTablePainter old) =>
|
||||
!listEquals(old.rukns, rukns);
|
||||
}
|
||||
|
||||
bool listEquals<T>(List<T?> a, List<T?> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
+555
@@ -0,0 +1,555 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — THE INFORMATIVE (DECLARATIVE) STYLE (الأسلوب الخبري)
|
||||
/// Lesson: أبني لغتي (2): الأسلوبُ الخَبَريُّ
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_01/lesson_06.md (pages 29-31)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Built ONLY on extractable facts from the source pages:
|
||||
/// - تعريف الأسلوب الخبري: «الكلامُ الذي يحتملُ الصدقَ والكذبَ» (page 30).
|
||||
/// - جريان الكلام في علم المعاني على أسلوبين: الخبري والإنشائي، يأتيان
|
||||
/// في الجمل الاسمية والفعلية (أستزيد، page 29).
|
||||
/// - أمثلة خبرية من الدرس: «قصائدُ أحمد شوقي سهلةُ الحفظِ»، «رحلَ جارُنا
|
||||
/// صباحَ اليوم»، «أنا الذي نظر الأعمى إلى أدبي» (المتنبي)، «يقعُ وادي رم
|
||||
/// جنوب عمّان» (خبر مطابق للواقع).
|
||||
/// - أمثلة إنشائية من الدرس: «ما أجملَ أن نعيشَ سُعداء!» (تعجّب)،
|
||||
/// «لماذا نظرتُّ من النافذةِ؟» (استفهام).
|
||||
/// - تمييز صدق/كذب الخبر بمطابقته الواقع: «هو شجاعٌ كريمٌ».
|
||||
/// The reflective table on page 31 («أدوّن ما تعلمته») is a self-fill recap
|
||||
/// page; nothing was invented for it. Flagged for the academic reviewer.
|
||||
/// ============================================================================
|
||||
|
||||
// جمل الدرس مصنّفة: خبرية (0) أو إنشائية (1).
|
||||
const List<(String, int)> _styleSentences = [
|
||||
('«قصائدُ أحمد شوقي سهلةُ الحفظِ.»', 0),
|
||||
('«رحلَ جارُنا صباحَ اليومِ.»', 0),
|
||||
('«ما أجملَ أن نعيشَ سُعداء!»', 1),
|
||||
('«لماذا نظرتُّ من النافذةِ؟»', 1),
|
||||
('«أنا الذي نظرَ الأعمى إلى أدبي»', 0),
|
||||
('«يقعُ وادي رم جنوبَ عمّانَ.»', 0),
|
||||
];
|
||||
|
||||
// نقاط قواعد الأسلوب الخبري المكتملة من نص الدرس.
|
||||
const List<(String, String, String)> _ruleRows = [
|
||||
(
|
||||
'الأسلوبُ الخبريُّ: هو الكلامُ الذي يحتملُ ...',
|
||||
'الصدقَ والكذبَ',
|
||||
'الطلبَ والنهيَ'
|
||||
),
|
||||
(
|
||||
'يجري الكلامُ في علمِ المعاني في أسلوبينِ: ...',
|
||||
'الخبريِّ والإنشائيِّ',
|
||||
'الماضي والمضارع'
|
||||
),
|
||||
('يأتي الأسلوبانِ في الجملِ ...', 'الاسميةِ والفعليةِ', 'الشرطيةِ فقط'),
|
||||
];
|
||||
|
||||
// تمييز صِدْق الخبر وكذِبه بمطابقته الواقع.
|
||||
const List<(String, String, String)> _truthRows = [
|
||||
(
|
||||
'«هو شجاعٌ كريمٌ» — يقولُهُ أحدُهم عن سعيد.',
|
||||
'خبرٌ يحتملُ الصدقَ والكذبَ معًا',
|
||||
'خبرٌ لا يحتملُ إلا الكذبَ'
|
||||
),
|
||||
(
|
||||
'متى يكونُ الخبرُ صادقًا مطابقًا للواقعِ؟',
|
||||
'إذا طابَقَ الواقعَ ووصفَ الحالَ على ما هو عليه',
|
||||
'إذا خالفَ الواقعَ وابتعدَ عنهُ'
|
||||
),
|
||||
(
|
||||
'متى يكونُ الخبرُ كاذبًا؟',
|
||||
'إذا لم يُطابِقِ الواقعَ وحدّثَ بغيرِ ما هو عليه',
|
||||
'إذا كانَ اللفظُ جميلًا ومعبِّرًا'
|
||||
),
|
||||
];
|
||||
|
||||
class ArabicInformativeStyleLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicInformativeStyleLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicInformativeStyleLabView> createState() =>
|
||||
_ArabicInformativeStyleLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicInformativeStyleLabViewState
|
||||
extends State<ArabicInformativeStyleLabView> {
|
||||
int _activity = 0;
|
||||
|
||||
final List<int?> _style = List<int?>.filled(_styleSentences.length, null);
|
||||
final List<int?> _rule = List<int?>.filled(3, null);
|
||||
final List<int?> _truth = List<int?>.filled(3, null);
|
||||
|
||||
bool get _styleSolved {
|
||||
for (var i = 0; i < _style.length; i++) {
|
||||
if (_style[i] != _styleSentences[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _ruleSolved => _rule.every((e) => e == 0);
|
||||
bool get _truthSolved => _truth.every((e) => e == 0);
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _styleSolved;
|
||||
if (_activity == 1) return _ruleSolved;
|
||||
return _truthSolved;
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _StyleBinsPainter(style: List.of(_style)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _RuleCardPainter(rule: List.of(_rule)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _TruthScalePainter(truth: List.of(_truth)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['صنِّفِ الجملَ', 'أكملِ القواعدَ', 'ميّزِ الخبرَ'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'صنِّفْ جُملَ الدرس: جملةٌ خبريةٌ (تحتمل الصدق والكذب) أم جملةٌ إنشائيةٌ (تعجّب/استفهام)؟',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _styleSentences.length; i++) ...[
|
||||
Text(
|
||||
_styleSentences[i].$1,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: const ['خبريّةٌ', 'إنشائيّةٌ'],
|
||||
values: const [0, 1],
|
||||
current: _style[i] ?? -1,
|
||||
accent: _style[i] == _styleSentences[i].$2
|
||||
? const Color(0xFF30D158)
|
||||
: (_style[i] == null
|
||||
? const Color(0xFF0071E3)
|
||||
: const Color(0xFFFF375F)),
|
||||
onSelected: (v) => setState(() {
|
||||
_style[i] = v;
|
||||
if (_styleSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
if (_styleSolved)
|
||||
const LabPill('صنَّفْتَ الجملَ: خبريةٌ وإنشائيةٌ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'خبريّة: «قصائدُ أحمد شوقي...» «رحلَ جارُنا...». إنشائيّة: «ما أجملَ...!» «لماذا...؟».',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أكمل قواعدَ الأسلوبِ الخبريِّ كما وردت في الدرس.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _ruleRows.length; i++) ...[
|
||||
Text(
|
||||
'${_ruleRows[i].$1} (؟)',
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: [_ruleRows[i].$2, _ruleRows[i].$3],
|
||||
values: const [0, 1],
|
||||
current: _rule[i] ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: (v) => setState(() {
|
||||
_rule[i] = v;
|
||||
if (_ruleSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
if (_ruleSolved)
|
||||
const LabPill('أكملتَ قواعدَ الأسلوبِ الخبريِّ ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'اضبطْ ميزانَ الخبرِ: يحتملُ الصدقَ والكذبَ، فيزدانُ بالصدقِ إذا طابَقَ الواقعَ ويُستهجَنُ بالكذبِ إذا خالفه.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _truthRows.length; i++) ...[
|
||||
Text(
|
||||
_truthRows[i].$1,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: [_truthRows[i].$2, _truthRows[i].$3],
|
||||
values: const [0, 1],
|
||||
current: _truth[i] ?? -1,
|
||||
accent: const Color(0xFF30D158),
|
||||
onSelected: (v) => setState(() {
|
||||
_truth[i] = v;
|
||||
if (_truthSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
if (_truthSolved)
|
||||
const LabPill('ضبطتَ ميزانَ الخبر: صدقٌ بمطابقةِ الواقع ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question:
|
||||
'لماذا لا تُعدُّ جملةُ (ما أجملَ أن نعيشَ سُعداء!) أسلوبًا خبريًّا؟',
|
||||
options: const [
|
||||
'لأنها أسلوبُ تعجّبٍ إنشائيٌّ يعبّر عن انفعالٍ ولا يحتملُ الصدقَ والكذبَ',
|
||||
'لأنها جملةٌ طويلةٌ كثيرةُ الكلماتِ',
|
||||
'لأنها لا تبدأُ بفعلٍ ماضٍ',
|
||||
'لأنها وردتْ في لعبةٍ شعريةٍ لا تُعتبَرُ كلامًا',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicInformativeStyleLabIdentity,
|
||||
titleAr: 'مختبر الأسلوب الخبري',
|
||||
subtitleAr: 'أبني لغتي (2) — الكلامُ الخبريّ: الصدقُ والكذبُ والإنشاء',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 29-31. التعريف والأمثلة وأبيات زهير والمتنبي منقولة من نص الصفحات؛ صفحة 31 (أدوّن ما تعلمته) صفحة تلخيص ذاتية فارغة لم يُبنَ عليها. يحتاج قرار المراجع الأكاديمي قبل النشر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — ميزان الجمل: خبرية / إنشائية.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _StyleBinsPainter extends CustomPainter {
|
||||
final List<int?> style;
|
||||
|
||||
const _StyleBinsPainter({required this.style});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final solved = _allCorrect();
|
||||
final informative = style.where((s) => s == 0).length;
|
||||
final creative = style.where((s) => s == 1).length;
|
||||
|
||||
_bin(canvas, Offset(w * 0.60, h * 0.30), 'جملةٌ خبريّةٌ',
|
||||
const Color(0xFF30D158), informative);
|
||||
_bin(canvas, Offset(w * 0.40, h * 0.30), 'جملةٌ إنشائيّةٌ',
|
||||
const Color(0xFFFF9F0A), creative);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.14),
|
||||
solved
|
||||
? 'وُزِّعتْ جملُ الدرسِ كلُّها: خبريةٌ وإنشائيةٌ ✓'
|
||||
: 'ميزانُ الكلامِ: الخبريُّ يحتملُ الصدقَ والكذبَ، والإنشائيُّ لا يحتملهُما',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
// Balance beam.
|
||||
final beamY = h * 0.66;
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.25, beamY),
|
||||
Offset(w * 0.75, beamY),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.25)
|
||||
..strokeWidth = 2);
|
||||
final pivotX = w * 0.45 + w * 0.30;
|
||||
canvas.drawCircle(Offset(pivotX, beamY), 5, Paint()..color = Colors.white);
|
||||
final tilt = (informative - creative) * 0.012;
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.25, beamY + tilt * 2),
|
||||
Offset(w * 0.75, beamY - tilt * 2),
|
||||
Paint()
|
||||
..color = _binsBeamColor
|
||||
..strokeWidth = 2);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.88),
|
||||
'الخبرُ يوزنُ بمطابقةِ الواقعِ: صادقٌ إن طابَقَ، كاذبٌ إن خالَفَ.',
|
||||
const TextStyle(color: Colors.white54, fontSize: 10.5),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
bool _allCorrect() {
|
||||
for (var i = 0; i < style.length; i++) {
|
||||
if (style[i] != _styleSentences[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _bin(Canvas canvas, Offset c, String label, Color color, int count) {
|
||||
final r = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: c, width: 150, height: 110),
|
||||
const Radius.circular(14));
|
||||
canvas.drawRRect(r, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
r,
|
||||
Paint()
|
||||
..color = color.withValues(alpha: 0.55)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4);
|
||||
_centerText(canvas, Offset(c.dx, c.dy - 40), label,
|
||||
TextStyle(color: color, fontSize: 11, fontWeight: FontWeight.w800),
|
||||
maxWidth: 140);
|
||||
for (var i = 0; i < count; i++) {
|
||||
final x = c.dx - 45 + (i % 3) * 42;
|
||||
final yy = c.dy - 4 + (i ~/ 3) * 34;
|
||||
canvas.drawCircle(Offset(x, yy), 9, Paint()..color = color);
|
||||
}
|
||||
_centerText(canvas, Offset(c.dx, c.dy + 46), '$count/6',
|
||||
const TextStyle(color: Colors.white, fontSize: 12),
|
||||
maxWidth: 60);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 3,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _StyleBinsPainter old) =>
|
||||
!listEqualsAll(old.style, style);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — بطاقة قواعد الأسلوب الخبري.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _RuleCardPainter extends CustomPainter {
|
||||
final List<int?> rule;
|
||||
|
||||
const _RuleCardPainter({required this.rule});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final solved = rule.every((e) => e == 0);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.14),
|
||||
solved
|
||||
? 'قواعدُ الأسلوبِ الخبريِّ مكتملةٌ ✓'
|
||||
: 'بطاقةُ قواعدِ الأسلوبِ الخبريِّ',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.84);
|
||||
|
||||
const heads = [
|
||||
'التعريف',
|
||||
'الأسلوبانِ في علمِ المعاني',
|
||||
'محلُّ الأسلوبينِ من الجملِ',
|
||||
];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
final y = h * (0.30 + i * 0.20);
|
||||
final done = rule[i] == 0;
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.10, y, w * 0.80, h * 0.14),
|
||||
const Radius.circular(10));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = (done ? const Color(0xFF30D158) : Colors.white)
|
||||
.withValues(alpha: done ? 0.7 : 0.15)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, y + 12),
|
||||
heads[i],
|
||||
const TextStyle(
|
||||
color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800));
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, y + 36),
|
||||
done ? _ruleRows[i].$2 : '…',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white38,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.72);
|
||||
}
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.92),
|
||||
'الخبريُّ والإنشائيُّ أسلوبانِ في علمِ المعاني، وهو أحدُ علومِ البلاغةِ الثلاثة.',
|
||||
const TextStyle(color: Colors.white54, fontSize: 10.5),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _RuleCardPainter old) =>
|
||||
!listEqualsAll(old.rule, rule);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — ميزان صدق الخبر وكذبه.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _TruthScalePainter extends CustomPainter {
|
||||
final List<int?> truth;
|
||||
|
||||
const _TruthScalePainter({required this.truth});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final solved = truth.every((e) => e == 0);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.14),
|
||||
solved
|
||||
? 'ميزانُ الخبرِ مضبوطٌ: صدقٌ وكذبٌ ✓'
|
||||
: 'ضعْ الخبرَ على ميزانِ الواقعِ',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
const pans = [
|
||||
'«هو شجاعٌ كريمٌ»',
|
||||
'الخبرُ صادقٌ إذا...',
|
||||
'الخبرُ كاذبٌ إذا...'
|
||||
];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
final y = h * (0.30 + i * 0.18);
|
||||
final done = truth[i] == 0;
|
||||
final side = i == 0 ? 0 : (i == 1 ? 1 : -1);
|
||||
final tray = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.10, y, w * 0.80, h * 0.12),
|
||||
const Radius.circular(10));
|
||||
canvas.drawRRect(tray, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
tray,
|
||||
Paint()
|
||||
..color = done
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.7)
|
||||
: const Color(0xFFFF9F0A).withValues(alpha: done ? 0.7 : 0.2)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, y + 16),
|
||||
pans[i],
|
||||
const TextStyle(
|
||||
color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800),
|
||||
maxWidth: w * 0.72);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, y + 40),
|
||||
done
|
||||
? _truthRows[i].$2
|
||||
: (side == 1
|
||||
? 'انتظر: متى يصدق الخبر؟'
|
||||
: (side == -1 ? 'انتظر: متى يكذب الخبر؟' : '…')),
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white38,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.72);
|
||||
}
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _TruthScalePainter old) =>
|
||||
!listEqualsAll(old.truth, truth);
|
||||
}
|
||||
|
||||
const Color _binsBeamColor = Color(0xFF00F5D4);
|
||||
|
||||
bool listEqualsAll<T>(List<T?> a, List<T?> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC LANGUAGE — GRADE 10 (العربية لغتي)
|
||||
/// 1. ArabicSyntaxLabView — الإعراب التفاعلي (مبتدأ/خبر/فاعل/مفعول…)
|
||||
/// 2. ArabicProsodyLabView — العروض: البحر الكامل والطويل (تفعيلات)
|
||||
/// النصوص: أمثلة صفية مشكولة من مستوى الصف العاشر، موسومة كممارسة.
|
||||
/// ============================================================================
|
||||
|
||||
class ArabicSyntaxLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const ArabicSyntaxLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicSyntaxLabView> createState() => _ArabicSyntaxLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicSyntaxLabViewState extends State<ArabicSyntaxLabView> {
|
||||
int sentence = 0;
|
||||
int selectedWord = -1;
|
||||
|
||||
static const sentences = [
|
||||
{
|
||||
'text': ['العلمُ', 'نورٌ', 'يهدي', 'العقولَ'],
|
||||
'roles': [
|
||||
'مبتدأ مرفوع بالضمة',
|
||||
'خبر مرفوع بالضمة',
|
||||
'فعل مضارع مرفوع',
|
||||
'مفعول به منصوب بالفتحة'
|
||||
],
|
||||
'hint': 'جملة اسمية: ركناها المبتدأ والخبر مرفوعان.',
|
||||
},
|
||||
{
|
||||
'text': ['كتبَ', 'الطالبُ', 'الدرسَ', 'بإتقانٍ'],
|
||||
'roles': [
|
||||
'فعل ماضٍ مبني على الفتح',
|
||||
'فاعل مرفوع بالضمة',
|
||||
'مفعول به منصوب',
|
||||
'جار ومجرور (شبه جملة)'
|
||||
],
|
||||
'hint': 'جملة فعلية: فعل + فاعل + مفعول به.',
|
||||
},
|
||||
{
|
||||
'text': ['إنَّ', 'الاجتهادَ', 'طريقُ', 'النجاحِ'],
|
||||
'roles': [
|
||||
'حرف ناسخ (ينصب المبتدأ)',
|
||||
'اسم إن منصوب بالفتحة',
|
||||
'خبر إن مرفوع بالضمة',
|
||||
'مضاف إليه مجرور بالكسرة'
|
||||
],
|
||||
'hint': 'إن وأخواتها: تنصب المبتدأ وترفع الخبر.',
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = sentences[sentence];
|
||||
final words = s['text'] as List<String>;
|
||||
final roles = s['roles'] as List<String>;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'مختبر الإعراب التفاعلي',
|
||||
subtitleAr: 'المس الكلمة لتظهر وظيفتها النحوية وعلامة إعرابها',
|
||||
identity: kArabicSyntaxToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'في جملة «العلمُ نورٌ» كلمة «نورٌ» إعرابها …',
|
||||
checkpointOptions: const [
|
||||
'خبر مرفوع',
|
||||
'فاعل مرفوع',
|
||||
'مفعول به',
|
||||
'مضاف إليه'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('الجملة ${sentence + 1}/${sentences.length}'),
|
||||
if (selectedWord >= 0)
|
||||
LabPill(words[selectedWord], color: const Color(0xFF30D158)),
|
||||
],
|
||||
canvas: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: List.generate(words.length, (i) {
|
||||
final sel = i == selectedWord;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => selectedWord = i),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: sel
|
||||
? AppColors.saqelCyan
|
||||
: Colors.white.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(13),
|
||||
border: Border.all(
|
||||
color: sel ? AppColors.saqelCyan : Colors.white24,
|
||||
width: sel ? 2 : 1),
|
||||
),
|
||||
child: Text(words[i],
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white,
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w900)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (selectedWord >= 0)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF30D158).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF30D158).withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Text(roles[selectedWord],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.6)),
|
||||
)
|
||||
else
|
||||
const Text('المس أي كلمة لعرض إعرابها…',
|
||||
style: TextStyle(color: Color(0xFF64748B), fontSize: 12.5)),
|
||||
],
|
||||
),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<int>(
|
||||
labels: const ['اسمية', 'فعلية', 'ناسخة'],
|
||||
values: const [0, 1, 2],
|
||||
current: sentence,
|
||||
onSelected: (v) => setState(() {
|
||||
sentence = v;
|
||||
selectedWord = -1;
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: 'القاعدة',
|
||||
body: s['hint'] as String,
|
||||
accent: const Color(0xFF30D158),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'علامات الإعراب الأصلية: الضمة رفعاً • الفتحة نصباً • الكسرة جراً • السكون جزماً.',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 11.5, height: 1.7)),
|
||||
],
|
||||
footerNote: 'أمثلة صفية مبسطة؛ الشواهد الأدبية الكاملة من الكتاب المقرر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ArabicProsodyLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const ArabicProsodyLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicProsodyLabView> createState() => _ArabicProsodyLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicProsodyLabViewState extends State<ArabicProsodyLabView> {
|
||||
int bahr = 0;
|
||||
int beat = -1;
|
||||
|
||||
static const buhur = [
|
||||
{
|
||||
'name': 'البحر الكامل',
|
||||
'key': 'مُتَفَاعِلُنْ × 3',
|
||||
'feet': ['مُتَفَاعِلُنْ', 'مُتَفَاعِلُنْ', 'مُتَفَاعِلُنْ'],
|
||||
'pattern': ['●○●●○●○', '●○●●○●○', '●○●●○●○'],
|
||||
'verse': 'وإذا أتَتْكَ مَذَمَّتي مِنْ ناقِصٍ',
|
||||
},
|
||||
{
|
||||
'name': 'البحر الطويل',
|
||||
'key': 'فَعُولُنْ مَفَاعِيلُنْ',
|
||||
'feet': ['فَعُولُنْ', 'مَفَاعِيلُنْ', 'فَعُولُنْ', 'مَفَاعِيلُنْ'],
|
||||
'pattern': ['●○○●○', '●○○●○●○○', '●○○●○', '●○○●○●○○'],
|
||||
'verse': 'قِفَا نَبْكِ مِنْ ذِكْرَى حَبِيبٍ ومَنْزِلِ',
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final b = buhur[bahr];
|
||||
final feet = b['feet'] as List<String>;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'مختبر العروض والموسيقى الشعرية',
|
||||
subtitleAr: 'انقر التفعيلة واسمع إيقاعها البصري حركةً بسكون',
|
||||
identity: kArabicProsodyToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'تفعيلة البحر الكامل الأساسية هي …',
|
||||
checkpointOptions: const [
|
||||
'مُتَفَاعِلُنْ',
|
||||
'فَعُولُنْ',
|
||||
'مَفَاعِيلُنْ',
|
||||
'فَاعِلَاتُنْ'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(b['name'] as String, color: const Color(0xFFFFD166)),
|
||||
LabPill(b['key'] as String),
|
||||
],
|
||||
canvas: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(b['verse'] as String,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 1.8)),
|
||||
const SizedBox(height: 18),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: List.generate(feet.length, (i) {
|
||||
final sel = i == beat;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
saqelTick();
|
||||
setState(() => beat = i);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 160),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: sel
|
||||
? const Color(0xFFFFD166)
|
||||
: Colors.white.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(11),
|
||||
border: Border.all(
|
||||
color:
|
||||
sel ? const Color(0xFFFFD166) : Colors.white24),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(feet[i],
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14)),
|
||||
const SizedBox(height: 3),
|
||||
Text((b['pattern'] as List<String>)[i],
|
||||
style: TextStyle(
|
||||
color: sel
|
||||
? Colors.black87
|
||||
: const Color(0xFF00F5D4),
|
||||
fontSize: 11)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<int>(
|
||||
labels: const ['الكامل', 'الطويل'],
|
||||
values: const [0, 1],
|
||||
current: bahr,
|
||||
onSelected: (v) => setState(() {
|
||||
bahr = v;
|
||||
beat = -1;
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const LabFormulaCard(
|
||||
title: 'مفتاح التقطيع',
|
||||
body:
|
||||
'● = متحرك (حركة) • ○ = ساكن.\nقارن نطقك بالتفعيلة: كل تفعيلة وحدة إيقاعية واحدة. الزحافات (إسكان الثاني في الكامل) جواز موسيقي يُدرس مع المعلم.',
|
||||
accent: Color(0xFFFFD166),
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'التقطيع العروضي مهارة سماعية؛ تحقق من قراءتك مع معلم المادة.',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,802 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — LISTENING COMPREHENSION LAB
|
||||
/// Lesson: أستمعُ بانتباهٍ وتركيزٍ — قصة كعب بن مالك رضي الله عنه
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_01/lesson_01.md (pages 8-9)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Faithful to the source only. Nothing here is invented: the four etiquettes
|
||||
/// of listening, the four trial stages (with their page letters أ/ب/ج/د) and
|
||||
/// the cause→effect pairs are taken from the source wording. The stage
|
||||
/// ordering reflects the source page and stays flagged for academic review
|
||||
/// (identity.statusLabel). No demo mastery or income figures exist here.
|
||||
/// ============================================================================
|
||||
|
||||
const List<(String, String)> _stages = [
|
||||
('د', 'اقتراحُ رجالٍ من بني سَلَمةَ أن يعتذرَ بمثلِ ما اعتذَر المخلَّفون'),
|
||||
('أ', 'تسليمُه على النبي ﷺ في مجلسه والصلاةُ بالقرِب منه'),
|
||||
('ج', 'سماعُه صوتًا صارخًا: «يا كعبُ بنَ مالكٍ، أبشِر» أثناء صلاةِ الفجر'),
|
||||
('ب', 'مجيءُ رسولٍ من النبي ﷺ إليه بعد مرورِ أربعين ليلةً'),
|
||||
];
|
||||
|
||||
const List<String> _stageShort = [
|
||||
'اقتراح الاعتذار',
|
||||
'التسليم والصبر',
|
||||
'النداء بالبشرى',
|
||||
'مجيء الرسول'
|
||||
];
|
||||
const String _turningLetter = 'ج'; // نقطة التحول التي جاء معها الفرج
|
||||
|
||||
class ArabicListeningAttentionLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicListeningAttentionLabView(
|
||||
{super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicListeningAttentionLabView> createState() =>
|
||||
_ArabicListeningAttentionLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicListeningAttentionLabViewState
|
||||
extends State<ArabicListeningAttentionLabView> {
|
||||
static const List<String> _canonical = ['د', 'أ', 'ج', 'ب'];
|
||||
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — آداب الاستماع.
|
||||
bool _sit = false;
|
||||
bool _face = false;
|
||||
bool _gaze = false;
|
||||
bool _aware = false;
|
||||
|
||||
// Activity 2 — مراحل الابتلاء.
|
||||
final List<String> _placed = [];
|
||||
int _wrongCount = 0;
|
||||
int _flashStep = -1;
|
||||
bool _solved = false;
|
||||
|
||||
// Activity 3 — السبب والنتيجة.
|
||||
int? _choiceA; // 0..1
|
||||
int? _choiceB; // 0..1
|
||||
|
||||
bool get _etiquetteDone => _sit && _face && _gaze && _aware;
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _etiquetteDone;
|
||||
if (_activity == 1) return _solved;
|
||||
return _choiceA != null && _choiceB != null;
|
||||
}
|
||||
|
||||
void _tapStage(String letter) {
|
||||
if (_solved || _placed.contains(letter)) return;
|
||||
setState(() {
|
||||
_placed.add(letter);
|
||||
if (_placed.length == 4) {
|
||||
var correct = true;
|
||||
for (var i = 0; i < _canonical.length; i++) {
|
||||
if (_placed[i] != _canonical[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (correct) {
|
||||
_solved = true;
|
||||
saqelTick();
|
||||
} else {
|
||||
_wrongCount++;
|
||||
_flashStep = _canonical.indexOf(_placed[0]);
|
||||
_placed.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetPlaced() {
|
||||
setState(() {
|
||||
_placed.clear();
|
||||
_wrongCount = 0;
|
||||
_flashStep = -1;
|
||||
_solved = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _EtiquetteScenePainter(
|
||||
sit: _sit,
|
||||
face: _face,
|
||||
gaze: _gaze,
|
||||
aware: _aware,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _TrialStagesPainter(
|
||||
placed: List.of(_placed),
|
||||
canonical: _canonical,
|
||||
turningLetter: _turningLetter,
|
||||
solved: _solved,
|
||||
flashStep: _flashStep,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _CauseEffectPainter(
|
||||
causeA: 'سرورُ النبي ﷺ وفرحتُه بتوبةِ الله على كعبٍ وصاحبَيه',
|
||||
causeB: 'خوفُ كعبٍ من سخَط الله ورجاؤُه العفوَ والصفَح',
|
||||
resultA: 'استغفارُ النبي ﷺ للمخلَّفينَ ومبايعتُهم',
|
||||
resultB: 'إقرارُه بصدقٍ، وترجيهِ العفو، وصبرُه على الابتلاء',
|
||||
chosenA: _choiceA,
|
||||
chosenB: _choiceB,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['آداب الاستماع', 'مراحل الابتلاء', 'السبب والنتيجة'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
LabToggle(
|
||||
label: 'أجلسُ جلسةً صحيحةً',
|
||||
value: _sit,
|
||||
onChanged: (v) => setState(() => _sit = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أُقبلُ بوجهي نحوَ المتحدِّث',
|
||||
value: _face,
|
||||
onChanged: (v) => setState(() => _face = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أنظرُ إلى المتكلِّم',
|
||||
value: _gaze,
|
||||
onChanged: (v) => setState(() => _gaze = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أعي ما يقولُه بتركيزٍ',
|
||||
value: _aware,
|
||||
onChanged: (v) => setState(() => _aware = v),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_etiquetteDone)
|
||||
const LabPill('جاهزٌ للاستماعِ الواعي ✓', color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill('فعِّل آدابَ الاستماع كلَّها',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
final nextLetter = _placed.length < 4 ? _canonical[_placed.length] : null;
|
||||
c.addAll([
|
||||
Text(
|
||||
_solved
|
||||
? 'أحسنتَ ترتيبَ المراحل، ونقطةُ التحوّل (الفرج) هي المرحلة «$_turningLetter».'
|
||||
: 'رتِّب المراحلَ تصاعديًّا. اضغط المرحلة «${nextLetter ?? 'الخامسة'}» الحالية.',
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _stages.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: _placed.contains(_stages[i].$1)
|
||||
? Colors.white.withValues(alpha: 0.06)
|
||||
: (_flashStep == i
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.02)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _solved ? null : () => _tapStage(_stages[i].$1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _placed.contains(_stages[i].$1)
|
||||
? AppColors.saqelCyan.withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: _placed.contains(_stages[i].$1)
|
||||
? AppColors.saqelCyan
|
||||
: Colors.black.withValues(alpha: 0.35),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_stages[i].$1,
|
||||
style: TextStyle(
|
||||
color: _placed.contains(_stages[i].$1)
|
||||
? Colors.black
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_stageShort[i],
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_wrongCount >= 2 && !_solved)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'خطوة صغيرة: ابدأ بالمرحلة «د» — اقتراحُ الاعتذار، ثم الأصعبُ فالأصعب.',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _resetPlaced,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('صفّر الترتيب'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'اربط كلَّ سببٍ بنتيجتِه كما تتَّضح من سياق القصة.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text('السبب الأول',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF94A3B8),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700)),
|
||||
const SizedBox(height: 4),
|
||||
const Text('سرورُ النبي ﷺ وفرحتُه بتوبةِ الله على كعبٍ وصاحبَيه',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12, height: 1.6)),
|
||||
const SizedBox(height: 8),
|
||||
LabSegments<int>(
|
||||
labels: const ['استغفارُه ومبايعتُهم', 'غضبُه وتراجعُه'],
|
||||
values: const [0, 1],
|
||||
current: _choiceA ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: (v) => setState(() => _choiceA = v),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const Text('السبب الثاني',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF94A3B8),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700)),
|
||||
const SizedBox(height: 4),
|
||||
const Text('خوفُ كعبٍ من سخَط الله ورجاؤُه العفوَ والصفَح',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12, height: 1.6)),
|
||||
const SizedBox(height: 8),
|
||||
LabSegments<int>(
|
||||
labels: const ['الاعتذارُ بعذرٍ يرضي الناس', 'الصدقُ وترجِّي العفو'],
|
||||
values: const [0, 1],
|
||||
current: _choiceB ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: (v) => setState(() => _choiceB = v),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question:
|
||||
'ما الذي يُسمَّى «نقطةَ التحوّل» في قصة كعب بن مالك رضي الله عنه؟',
|
||||
options: const [
|
||||
'لحظةُ إعلانِ توبةِ الله عليه وصاحبَيه — الفرجُ بعد الصبر',
|
||||
'نهايةُ القصة من غيرِ معنى',
|
||||
'بدايةُ تخلفِه عن الغزوة',
|
||||
'خروجُ الجيشِ ثم عودتُه',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicListeningLabIdentity,
|
||||
titleAr: 'مختبر الانتباه والتركيز',
|
||||
subtitleAr:
|
||||
'قصة كعب بن مالك رضي الله عنه — الاستماع الواعي وبنية الابتلاء',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 8-9. ترتيب مراحل الابتلاء ونقطة التحول بحسب كلمات الكتاب وتُعتمد بعد المراجعة الأكاديمية. لا تُسجَّل أي إتقان محليًا.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — آداب الاستماع.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _EtiquetteScenePainter extends CustomPainter {
|
||||
final bool sit;
|
||||
final bool face;
|
||||
final bool gaze;
|
||||
final bool aware;
|
||||
|
||||
const _EtiquetteScenePainter({
|
||||
required this.sit,
|
||||
required this.face,
|
||||
required this.gaze,
|
||||
required this.aware,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2, cy = h / 2;
|
||||
final floor = cy + h * 0.32;
|
||||
|
||||
// Floor.
|
||||
canvas.drawLine(
|
||||
Offset(0, floor),
|
||||
Offset(w, floor),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.15)
|
||||
..strokeWidth = 2,
|
||||
);
|
||||
|
||||
// Speaker (right, RTL head position) with speech bubble.
|
||||
final speaker = Offset(cx + w * 0.30, floor - h * 0.10);
|
||||
_drawPerson(canvas, speaker, 1.0, true, true, true, true);
|
||||
_drawBubble(canvas, speaker + const Offset(-34, -66),
|
||||
'«مِن حُسنِ الاستماعِ الإقبالُ بالوجهِ»');
|
||||
|
||||
// Listener (left).
|
||||
final listener = Offset(cx - w * 0.30, floor - h * 0.10);
|
||||
_drawPerson(canvas, listener, 0.9, sit, face, gaze, aware);
|
||||
|
||||
// Gaze guide line to the speaker when listening attentively.
|
||||
if (gaze && face) {
|
||||
final from = listener + const Offset(-8, -26);
|
||||
final to = speaker + const Offset(0, -18);
|
||||
canvas.drawLine(
|
||||
from,
|
||||
to,
|
||||
Paint()
|
||||
..color = AppColors.saqelCyan.withValues(alpha: 0.55)
|
||||
..strokeWidth = 1.6
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
_arrow(canvas, from, to, AppColors.saqelCyan.withValues(alpha: 0.75));
|
||||
}
|
||||
}
|
||||
|
||||
void _drawBubble(Canvas canvas, Offset center, String text) {
|
||||
const r = Offset(60, 30);
|
||||
final rect =
|
||||
Rect.fromCenter(center: center, width: r.dx * 2, height: r.dy * 2);
|
||||
final bubble = RRect.fromRectAndRadius(rect, const Radius.circular(14));
|
||||
canvas.drawRRect(
|
||||
bubble,
|
||||
Paint()..color = const Color(0xFF102035).withValues(alpha: 0.95),
|
||||
);
|
||||
canvas.drawRRect(
|
||||
bubble,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.35)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2,
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 9.5, fontWeight: FontWeight.w600),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: r.dx * 2 - 14);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
void _drawPerson(Canvas canvas, Offset foot, double scale, bool sit,
|
||||
bool face, bool gaze, bool aware) {
|
||||
final head = foot - Offset(0, 34 * scale);
|
||||
final lean = sit ? 0.0 : 0.18;
|
||||
final dirX = face ? -1.0 : 1.0; // toward speaker (left of listener) or away
|
||||
// Torso.
|
||||
canvas.drawLine(
|
||||
foot,
|
||||
head - Offset(lean * 16 * scale, 22 * scale),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.85)
|
||||
..strokeWidth = 10 * scale
|
||||
..strokeCap = StrokeCap.round,
|
||||
);
|
||||
// Seat.
|
||||
canvas.drawLine(
|
||||
foot - Offset(8 * scale, 0),
|
||||
foot + Offset(8 * scale, 0),
|
||||
Paint()
|
||||
..color = AppColors.saqelCyan.withValues(alpha: 0.5)
|
||||
..strokeWidth = 4 * scale
|
||||
..strokeCap = StrokeCap.round,
|
||||
);
|
||||
// Head.
|
||||
final headC = head -
|
||||
Offset(lean * 16 * scale, 26 * scale) +
|
||||
Offset(dirX * 5 * scale, 0);
|
||||
canvas.drawCircle(
|
||||
headC, 12 * scale, Paint()..color = const Color(0xFF102035));
|
||||
canvas.drawCircle(
|
||||
headC,
|
||||
12 * scale,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.6)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4);
|
||||
// Eye — visible only when facing and gazing.
|
||||
if (gaze) {
|
||||
canvas.drawCircle(
|
||||
headC + Offset(dirX * 5 * scale, -2 * scale),
|
||||
2 * scale,
|
||||
Paint()..color = AppColors.saqelCyan,
|
||||
);
|
||||
}
|
||||
// Attention arcs when aware.
|
||||
if (aware) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(
|
||||
center: headC + Offset(-dirX * 16 * scale, 0),
|
||||
radius: (8 + i * 5) * scale,
|
||||
),
|
||||
-1.2,
|
||||
1.1,
|
||||
false,
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158).withValues(alpha: 0.6)
|
||||
..strokeWidth = 1.4
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _arrow(Canvas canvas, Offset from, Offset to, Color color) {
|
||||
final ang = (to - from).direction;
|
||||
final tip = to;
|
||||
canvas.drawLine(
|
||||
tip,
|
||||
tip - Offset.fromDirection(ang - 0.4, 7),
|
||||
Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1.6
|
||||
..strokeCap = StrokeCap.round,
|
||||
);
|
||||
canvas.drawLine(
|
||||
tip,
|
||||
tip - Offset.fromDirection(ang + 0.4, 7),
|
||||
Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1.6
|
||||
..strokeCap = StrokeCap.round,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_EtiquetteScenePainter oldDelegate) =>
|
||||
oldDelegate.sit != sit ||
|
||||
oldDelegate.face != face ||
|
||||
oldDelegate.gaze != gaze ||
|
||||
oldDelegate.aware != aware;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — مراحل الابتلاء (staircase ordering).
|
||||
// ---------------------------------------------------------------------------
|
||||
class _TrialStagesPainter extends CustomPainter {
|
||||
final List<String> placed;
|
||||
final List<String> canonical;
|
||||
final String turningLetter;
|
||||
final bool solved;
|
||||
final int flashStep;
|
||||
|
||||
const _TrialStagesPainter({
|
||||
required this.placed,
|
||||
required this.canonical,
|
||||
required this.turningLetter,
|
||||
required this.solved,
|
||||
required this.flashStep,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
const stepH = 26.0;
|
||||
final startX = w * 0.12, endX = w * 0.78;
|
||||
final baseY = h * 0.78;
|
||||
final n = canonical.length;
|
||||
|
||||
// Grid guide.
|
||||
for (var i = 0; i <= n; i++) {
|
||||
final fx = startX + (endX - startX) * i / n;
|
||||
canvas.drawLine(
|
||||
Offset(fx, baseY + 6),
|
||||
Offset(fx, h * 0.12),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.05)
|
||||
..strokeWidth = 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Staircase steps filled by placed letters.
|
||||
for (var i = 0; i < placed.length; i++) {
|
||||
final letter = placed[i];
|
||||
final slot = canonical.indexOf(letter);
|
||||
final correct = solved || slot == i;
|
||||
final x = startX + (endX - startX) * ((i + 0.5) / n);
|
||||
final topY = baseY - (i + 1) * stepH;
|
||||
final rect = Rect.fromLTWH(x - (endX - startX) / (2 * n) + 6, topY,
|
||||
(endX - startX) / n - 12, stepH);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, const Radius.circular(8)),
|
||||
Paint()
|
||||
..color =
|
||||
(correct ? const Color(0xFF30D158) : const Color(0xFFFF375F))
|
||||
.withValues(alpha: 0.16 + (flashStep == i ? 0.25 : 0)),
|
||||
);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, const Radius.circular(8)),
|
||||
Paint()
|
||||
..color =
|
||||
(correct ? const Color(0xFF30D158) : const Color(0xFFFF375F))
|
||||
.withValues(alpha: 0.65)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4,
|
||||
);
|
||||
_centerText(
|
||||
canvas,
|
||||
rect.center,
|
||||
'$letter — ${_stageShort[canonical.indexOf(letter)]}',
|
||||
const TextStyle(color: Colors.white, fontSize: 10));
|
||||
// Turning flag on the turning slot when reached.
|
||||
if (solved && letter == turningLetter) {
|
||||
_flag(canvas, rect.centerLeft - const Offset(8, 18), turningLetter);
|
||||
}
|
||||
}
|
||||
|
||||
// Slot labels.
|
||||
for (var i = 0; i < n; i++) {
|
||||
final x = startX + (endX - startX) * ((i + 0.5) / n);
|
||||
final slotRect = Rect.fromLTWH(
|
||||
x - (endX - startX) / (2 * n), baseY + 10, (endX - startX) / n, 20);
|
||||
_centerText(
|
||||
canvas,
|
||||
slotRect.center,
|
||||
placed.length > i ? 'المرحلة ${i + 1} ✓' : 'المرحلة ${i + 1}',
|
||||
const TextStyle(
|
||||
color: Color(0xFF94A3B8),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700));
|
||||
}
|
||||
|
||||
if (solved) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.14),
|
||||
'نقطة التحول «$turningLetter»: سمع كعبٌ النداءَ بالبشرى أثناء صلاة الفجر — الفرج بعد الصبر.',
|
||||
const TextStyle(
|
||||
color: Color(0xFFFF9F0A),
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.84,
|
||||
);
|
||||
} else if (flashStep >= 0) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.14),
|
||||
'خطأ في الترتيب — راجِع أيُّ المراحل تسبق غيرَها.',
|
||||
const TextStyle(
|
||||
color: Color(0xFFFF375F),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.84,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _flag(Canvas canvas, Offset tip, String letter) {
|
||||
final path = Path()
|
||||
..moveTo(tip.dx, tip.dy)
|
||||
..lineTo(tip.dx + 10, tip.dy - 12)
|
||||
..lineTo(tip.dx, tip.dy - 24);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = const Color(0xFFFF9F0A)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeJoin = StrokeJoin.round);
|
||||
_centerText(
|
||||
canvas,
|
||||
tip,
|
||||
letter,
|
||||
const TextStyle(
|
||||
color: Color(0xFFFF9F0A),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w900));
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? (center.dx > 0 ? 200 : 200));
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_TrialStagesPainter oldDelegate) =>
|
||||
oldDelegate.placed != placed || oldDelegate.solved != solved;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — السبب والنتيجة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _CauseEffectPainter extends CustomPainter {
|
||||
final String causeA;
|
||||
final String causeB;
|
||||
final String resultA;
|
||||
final String resultB;
|
||||
final int? chosenA;
|
||||
final int? chosenB;
|
||||
|
||||
const _CauseEffectPainter({
|
||||
required this.causeA,
|
||||
required this.causeB,
|
||||
required this.resultA,
|
||||
required this.resultB,
|
||||
required this.chosenA,
|
||||
required this.chosenB,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final left = w * 0.06, rightX = w * 0.60, boxW = w * 0.34;
|
||||
final rowH = h * 0.30;
|
||||
final causes = [causeA, causeB];
|
||||
final results = [resultA, resultB];
|
||||
final choices = [chosenA, chosenB];
|
||||
|
||||
// Headers.
|
||||
_header(canvas, 'السبب', Offset(left + boxW / 2, rowH / 2),
|
||||
AppColors.saqelCyan);
|
||||
_header(canvas, 'النتيجة', Offset(rightX + boxW / 2, rowH / 2),
|
||||
const Color(0xFF0071E3));
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
final y = rowH * (i + 1) + 24;
|
||||
// Cause box.
|
||||
_box(canvas, Rect.fromLTWH(left, y - 34, boxW, 68), causes[i],
|
||||
Colors.white, AppColors.saqelCyan);
|
||||
final from = Offset(left + boxW, y);
|
||||
if (choices[i] != null) {
|
||||
final j = choices[i]!;
|
||||
final rj = results[j];
|
||||
_box(
|
||||
canvas,
|
||||
Rect.fromLTWH(rightX, y - 26, boxW, 52),
|
||||
rj,
|
||||
i == j ? const Color(0xFF30D158) : const Color(0xFFFF375F),
|
||||
const Color(0xFF0071E3));
|
||||
canvas.drawLine(
|
||||
from,
|
||||
Offset(rightX, y),
|
||||
Paint()
|
||||
..color =
|
||||
(i == j ? const Color(0xFF30D158) : const Color(0xFFFF375F))
|
||||
.withValues(alpha: 0.8)
|
||||
..strokeWidth = 2.4,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _header(Canvas canvas, String label, Offset center, Color color) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: label,
|
||||
style: TextStyle(
|
||||
color: color, fontSize: 13, fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
void _box(Canvas canvas, Rect rect, String text, Color accent, Color border) {
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, const Radius.circular(12)),
|
||||
Paint()..color = accent.withValues(alpha: 0.08),
|
||||
);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, const Radius.circular(12)),
|
||||
Paint()
|
||||
..color = border.withValues(alpha: 0.55)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.3,
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 10.5, height: 1.5)),
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 3,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: rect.width - 16);
|
||||
tp.paint(canvas,
|
||||
Offset(rect.center.dx - tp.width / 2, rect.center.dy - tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CauseEffectPainter oldDelegate) =>
|
||||
oldDelegate.chosenA != chosenA || oldDelegate.chosenB != chosenB;
|
||||
}
|
||||
+735
@@ -0,0 +1,735 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — FLUENT + COMPREHENSIVE READING (APOLOGY IN QUR'ANIC STORIES)
|
||||
/// Lesson: أقرأُ بطلاقةٍ وفهمٍ
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_01/lesson_03.md (pages 13-18)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// DIVERGENCE NOTE (kept honest): the spec front-matter title says
|
||||
/// «سينية البحتري في الاعتذار», yet the extracted corpus pages 13-18 cover —
|
||||
/// and this lab models ONLY — what is actually extractable and verifiable:
|
||||
/// - تعريف القراءة الصامتة (العين والعقل دون تحريك اللسان أو الشفة ولا همس),
|
||||
/// - ثقافة الاعتذار في قصص قرآنية (يوسف/الأعراف/الكهف) كما وردت في المصدر,
|
||||
/// - اعتذار موسى للعبد الصالح (الكهف 73): النسيان وطلب عدم المؤاخذة وآداب
|
||||
/// طلب العلم، والآية ﴿لا تثريب عليكم﴾ كما وردت في قائمة المفردات.
|
||||
/// No poetry/verse text beyond what the source prints is invented. The title/
|
||||
/// corpus gap and the OCR table of «شكل الاعتذار» stay flagged for academic
|
||||
/// review (identity.statusLabel). No demo mastery/income. identity is lesson 03.
|
||||
/// ============================================================================
|
||||
|
||||
const List<(String, String, String)> _surahApology = [
|
||||
(
|
||||
'سورة يوسف',
|
||||
'الإقرارُ بالخطأ وطلبُ المغفرة (﴿لا تثريبَ عليكم﴾)',
|
||||
'الإنكارُ وإلقاءُ اللومِ على الآخرين'
|
||||
),
|
||||
(
|
||||
'سورة الأعراف',
|
||||
'إسفٌ شديدٌ والمسارعةُ إلى الرشادِ بعدَ ذكرِ العجل',
|
||||
'الاعتذارُ بالنسيانِ فقط'
|
||||
),
|
||||
(
|
||||
'سورة الكهف',
|
||||
'الاعترافُ بالنسيانِ وآدابُ التعلّمِ والخُلُقُ الرفيع',
|
||||
'تركُ التعلّمِ من العبدِ الصالح'
|
||||
),
|
||||
];
|
||||
|
||||
const List<(String, String)> _mosesParts = [
|
||||
('أ', 'الاعترافُ بالنسيان: «نَسِيتُ»'),
|
||||
('ب', 'طلبُ عدمِ المؤاخذة: «لا تُؤاخِذني»'),
|
||||
('ج', 'المتابعةُ بأدبٍ في رحلةِ طلبِ العلم'),
|
||||
];
|
||||
const List<String> _mosesShort = [
|
||||
'الاعتراف',
|
||||
'طلبُ عدمِ المؤاخذة',
|
||||
'الاستمرار'
|
||||
];
|
||||
|
||||
class ArabicQuranicApologyReadingLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicQuranicApologyReadingLabView(
|
||||
{super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicQuranicApologyReadingLabView> createState() =>
|
||||
_ArabicQuranicApologyReadingLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicQuranicApologyReadingLabViewState
|
||||
extends State<ArabicQuranicApologyReadingLabView> {
|
||||
static const List<String> _canonicalMoses = ['أ', 'ب', 'ج'];
|
||||
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — ثقافة الاعتذار في قصص القرآن.
|
||||
final List<int?> _surah = List<int?>.filled(3, null);
|
||||
|
||||
// Activity 2 — القراءة الصامتة: سلوكيات صحيحة (2) وخطأ (2).
|
||||
bool _eyeTrack = false; // أتابع بعينيّ
|
||||
bool _think = false; // أفكّر بالمعنى بلا تحريك شفة
|
||||
bool _lipsMove = false; // خطأ: تحريك الشفاه/اللسان همسًا
|
||||
bool _voiceOut = false; // خطأ: النطق الصوتي
|
||||
|
||||
// Activity 3 — اعتذار موسى للعبد الصالح.
|
||||
final List<String> _placedMoses = [];
|
||||
int _mosesWrong = 0;
|
||||
int _flashMoses = -1;
|
||||
bool _mosesSolved = false;
|
||||
|
||||
bool get _surahSolved => _surah.every((s) => s == 0);
|
||||
|
||||
bool get _silentDone => _eyeTrack && _think && !_lipsMove && !_voiceOut;
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _surahSolved;
|
||||
if (_activity == 1) return _silentDone;
|
||||
return _mosesSolved;
|
||||
}
|
||||
|
||||
void _tapMoses(String letter) {
|
||||
if (_mosesSolved || _placedMoses.contains(letter)) return;
|
||||
setState(() {
|
||||
_placedMoses.add(letter);
|
||||
if (_placedMoses.length == 3) {
|
||||
var correct = true;
|
||||
for (var i = 0; i < _canonicalMoses.length; i++) {
|
||||
if (_placedMoses[i] != _canonicalMoses[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (correct) {
|
||||
_mosesSolved = true;
|
||||
saqelTick();
|
||||
} else {
|
||||
_mosesWrong++;
|
||||
_flashMoses = _canonicalMoses.indexOf(_placedMoses[0]);
|
||||
_placedMoses.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetMoses() {
|
||||
setState(() {
|
||||
_placedMoses.clear();
|
||||
_mosesWrong = 0;
|
||||
_flashMoses = -1;
|
||||
_mosesSolved = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _SurahApologyPainter(surah: _surah),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _SilentReadingPainter(
|
||||
eyeTrack: _eyeTrack,
|
||||
think: _think,
|
||||
lipsMove: _lipsMove,
|
||||
voiceOut: _voiceOut,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _MosesApologyPainter(
|
||||
placed: List.of(_placedMoses),
|
||||
canonical: _canonicalMoses,
|
||||
solved: _mosesSolved,
|
||||
flash: _flashMoses,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['قصصُ الاعتذار', 'القراءةُ الصامتة', 'اعتذارُ موسى'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'عرضَت الآياتُ جانبًا من ثقافةِ الاعتذارِ بين أيدينا. اربط كلَّ سورةٍ بما برزَ منها في القصصِ القرآني.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _surahApology.length; i++) ...[
|
||||
Text(
|
||||
_surahApology[i].$1,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF94A3B8),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: [_surahApology[i].$2, _surahApology[i].$3],
|
||||
values: const [0, 1],
|
||||
current: _surah[i] ?? -1,
|
||||
accent: const Color(0xFF0071E3),
|
||||
onSelected: (v) => setState(() {
|
||||
_surah[i] = v;
|
||||
if (_surahSolved) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (_surahSolved)
|
||||
const LabPill(
|
||||
'صحيح: لكلِّ قصةٍ وجهٌ من وجوهِ الاعتذارِ — أقِرّ واستغفِر وتعلّم.',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'القراءةُ الصامتةُ هي قراءةُ العينِ والعقلِ دونَ تحريكِ اللسانِ أو الشفةِ ولا همسٍ ولا صوت. فعِّلْ السلوكَ الصحيحَ وأوقفِ السلوكَ الخاطئَ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabToggle(
|
||||
label: 'أتابعُ الكلماتِ بعينيِّ وأعي معناها',
|
||||
value: _eyeTrack,
|
||||
onChanged: (v) => setState(() => _eyeTrack = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أفكّرُ في المعنى بذهني من غيرِ تحريكِ شفةٍ أو لسان',
|
||||
value: _think,
|
||||
onChanged: (v) => setState(() => _think = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أُحرّكُ شفتيّ بالكلماتِ همسًا (سلوكٌ خاطئ)',
|
||||
value: _lipsMove,
|
||||
onChanged: (v) => setState(() => _lipsMove = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'أُنطِقُ الحروفَ صوتًا مسموعًا (سلوكٌ خاطئ)',
|
||||
value: _voiceOut,
|
||||
onChanged: (v) => setState(() => _voiceOut = v),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_silentDone)
|
||||
const LabPill(
|
||||
'قراءةٌ صامتةٌ صحيحة: العينُ والعقلُ بلا همسٍ ولا صوت ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill('أكملْ: العينُ تتابع، والعقلُ يفكّر، بلا شفةٍ ولا صوت.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else {
|
||||
final nextLetter =
|
||||
_placedMoses.length < 3 ? _canonicalMoses[_placedMoses.length] : null;
|
||||
c.addAll([
|
||||
Text(
|
||||
_mosesSolved
|
||||
? 'أحسنتَ: اعتذارُ موسى عليه السلام صدقٌ بالنسيانِ ثمّ لا تؤاخذني ثمّ الاستمراربأدب.'
|
||||
: 'رتِّب مراتبَ اعتذارِ موسى للعبدِ الصالح. اضغط المرتبة «${nextLetter ?? 'التالي'}» الحالية.',
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _mosesParts.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: _placedMoses.contains(_mosesParts[i].$1)
|
||||
? Colors.white.withValues(alpha: 0.06)
|
||||
: (_flashMoses == i
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.02)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _mosesSolved ? null : () => _tapMoses(_mosesParts[i].$1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _placedMoses.contains(_mosesParts[i].$1)
|
||||
? AppColors.saqelCyan.withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: _placedMoses.contains(_mosesParts[i].$1)
|
||||
? AppColors.saqelCyan
|
||||
: Colors.black.withValues(alpha: 0.35),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_mosesParts[i].$1,
|
||||
style: TextStyle(
|
||||
color: _placedMoses.contains(_mosesParts[i].$1)
|
||||
? Colors.black
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_mosesParts[i].$2,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_mosesWrong >= 2 && !_mosesSolved)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'خطوة صغيرة: يبدأ الاعتذارُ بالاعترافِ ثمّ بطلبِ عدمِ المؤاخذةِ ثمّ بالاستمرار.',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _resetMoses,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('صفّر الترتيب'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question:
|
||||
'علامَ يدلُّ قولُ موسى عليه السلام للعبدِ الصالح (الكهف 73)؟',
|
||||
options: const [
|
||||
'على الاعترافِ بالنسيانِ وطلبِ عدمِ المؤاخذة — خُلُقٌ رفيعٌ في الاعتذار',
|
||||
'على أنّ موسى يتركُ رحلةَ طلبِ العلم',
|
||||
'على لومِ المعلمِ على شدّتِه',
|
||||
'على أنّ النسيانَ لا يُغتفَرُ أبدًا',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicQuranicApologyLabIdentity,
|
||||
titleAr: 'مختبر القراءة الفاهمة',
|
||||
subtitleAr: 'أقرأُ بطلاقةٍ وفهم — ثقافةُ الاعتذارِ في قصصٍ قرآنية',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 13-18. عناوين المواصفة تذكر «سينية البحتري» بينما صفحات الكوربوس المستخرجة تعرض القراءة الصامتة والاعتذار القرآني — التفاوت مسجّل ويحتاج مراجعة أكاديمية قبل أي نشر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — منارات قصص الاعتذار.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _SurahApologyPainter extends CustomPainter {
|
||||
final List<int?> surah;
|
||||
|
||||
const _SurahApologyPainter({required this.surah});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
|
||||
// Ground.
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.1, h * 0.78),
|
||||
Offset(w * 0.9, h * 0.78),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.15)
|
||||
..strokeWidth = 2);
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
final x = w * (0.16 + 0.34 * i);
|
||||
final correct = surah[i] == 0;
|
||||
final wrong = surah[i] == 1;
|
||||
// Pedestal.
|
||||
canvas.drawLine(
|
||||
Offset(x, h * 0.78),
|
||||
Offset(x, h * 0.52),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.20)
|
||||
..strokeWidth = 2);
|
||||
// Cap.
|
||||
canvas.drawCircle(
|
||||
Offset(x, h * 0.50),
|
||||
16,
|
||||
Paint()
|
||||
..color = correct
|
||||
? const Color(0xFF30D158)
|
||||
: (wrong ? const Color(0xFF102035) : const Color(0xFF102035)));
|
||||
canvas.drawCircle(
|
||||
Offset(x, h * 0.50),
|
||||
16,
|
||||
Paint()
|
||||
..color = (correct || wrong)
|
||||
? (wrong
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.9)
|
||||
: AppColors.saqelCyan.withValues(alpha: 0.9))
|
||||
: Colors.white.withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = wrong ? 2.5 : 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, h * 0.50),
|
||||
'${i + 1}',
|
||||
const TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.w900, fontSize: 12));
|
||||
|
||||
final stateText = switch (surah[i]) {
|
||||
0 => 'الإقرارُ وطلبُ المغفرة',
|
||||
1 => 'راجِعِ الوجهَ الصحيح',
|
||||
_ => 'لم يُجَبْ بعد',
|
||||
};
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, h * 0.31),
|
||||
stateText,
|
||||
TextStyle(
|
||||
color: correct
|
||||
? const Color(0xFF30D158)
|
||||
: wrong
|
||||
? const Color(0xFFFF375F)
|
||||
: Colors.white54,
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.28);
|
||||
}
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.12),
|
||||
'القصصُ القرآنيُّ مدرسةُ الاعتذارِ: أَحسنوا الإقرارَ وسارعوا إلى المغفرة.',
|
||||
const TextStyle(color: Colors.white70, fontSize: 11),
|
||||
maxWidth: w * 0.84);
|
||||
|
||||
if (surah.every((s) => s == 0)) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.88),
|
||||
'المناراتُ الثلاثُ أُضيئت: الاعتذارُ بإجلالٍ في كتابِ الله ✓',
|
||||
const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w800),
|
||||
maxWidth: w * 0.86);
|
||||
}
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _SurahApologyPainter old) =>
|
||||
!listEquals(old.surah, surah);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — العين والعقل بدل الشفة والصوت.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _SilentReadingPainter extends CustomPainter {
|
||||
final bool eyeTrack;
|
||||
final bool think;
|
||||
final bool lipsMove;
|
||||
final bool voiceOut;
|
||||
|
||||
const _SilentReadingPainter({
|
||||
required this.eyeTrack,
|
||||
required this.think,
|
||||
required this.lipsMove,
|
||||
required this.voiceOut,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final bookY = h * 0.62;
|
||||
|
||||
// Open book.
|
||||
final bookPath = Path()
|
||||
..moveTo(cx - w * 0.34, bookY)
|
||||
..quadraticBezierTo(cx, bookY - h * 0.10, cx + w * 0.34, bookY)
|
||||
..quadraticBezierTo(cx, bookY + h * 0.14, cx - w * 0.34, bookY)
|
||||
..close();
|
||||
canvas.drawPath(bookPath,
|
||||
Paint()..color = const Color(0xFF102035).withValues(alpha: 0.9));
|
||||
canvas.drawPath(
|
||||
bookPath,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.35)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5);
|
||||
canvas.drawLine(
|
||||
Offset(cx, bookY - h * 0.02),
|
||||
Offset(cx, bookY + h * 0.11),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.30)
|
||||
..strokeWidth = 1);
|
||||
|
||||
// The eye (or gaze) above the book = silent reading.
|
||||
final eyeOn = eyeTrack && think && !lipsMove && !voiceOut;
|
||||
if (eyeOn) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.20),
|
||||
'قراءةٌ صامتةٌ ✓',
|
||||
const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800));
|
||||
} else {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.20),
|
||||
'أُكمِلُ: عينٌ وعقلٌ بلا شفةٍ ولا صوت',
|
||||
const TextStyle(color: Colors.white54, fontSize: 10.5));
|
||||
}
|
||||
|
||||
// Face/lips zone.
|
||||
final lipsX = cx - w * 0.30;
|
||||
final lipsColor = lipsMove ? const Color(0xFFFF375F) : Colors.white24;
|
||||
canvas.drawCircle(Offset(lipsX, h * 0.40), 14, Paint()..color = lipsColor);
|
||||
canvas.drawLine(
|
||||
Offset(lipsX - 6, h * 0.40),
|
||||
Offset(lipsX + 6, h * 0.40),
|
||||
Paint()
|
||||
..color = Colors.white70
|
||||
..strokeWidth = 2
|
||||
..strokeCap = StrokeCap.round);
|
||||
_centerText(canvas, Offset(lipsX, h * 0.40 - 24), 'شفةٌ / لسان',
|
||||
const TextStyle(color: Colors.white54, fontSize: 9));
|
||||
|
||||
// Voice zone.
|
||||
final voiceX = cx + w * 0.30;
|
||||
final voiceColor = voiceOut ? const Color(0xFFFF375F) : Colors.white24;
|
||||
canvas.drawCircle(
|
||||
Offset(voiceX, h * 0.40), 14, Paint()..color = voiceColor);
|
||||
if (voiceOut) {
|
||||
canvas.drawLine(
|
||||
Offset(voiceX - 5, h * 0.45),
|
||||
Offset(voiceX + 5, h * 0.35),
|
||||
Paint()
|
||||
..color = const Color(0xFFFF375F)
|
||||
..strokeWidth = 2);
|
||||
} else {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(voiceX, h * 0.40),
|
||||
'✕',
|
||||
const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w900));
|
||||
}
|
||||
_centerText(canvas, Offset(voiceX, h * 0.40 - 24), 'صوتٌ مسموعٌ',
|
||||
const TextStyle(color: Colors.white54, fontSize: 9));
|
||||
|
||||
final done = eyeTrack && think && !lipsMove && !voiceOut;
|
||||
if (done) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.88),
|
||||
'العينُ والعقلُ يعملان، والشفةُ والصوتُ ساكنان.',
|
||||
const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _SilentReadingPainter old) =>
|
||||
old.eyeTrack != eyeTrack ||
|
||||
old.think != think ||
|
||||
old.lipsMove != lipsMove ||
|
||||
old.voiceOut != voiceOut;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — قوس مراتب اعتذار موسى.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _MosesApologyPainter extends CustomPainter {
|
||||
final List<String> placed;
|
||||
final List<String> canonical;
|
||||
final bool solved;
|
||||
final int flash;
|
||||
|
||||
const _MosesApologyPainter({
|
||||
required this.placed,
|
||||
required this.canonical,
|
||||
required this.solved,
|
||||
required this.flash,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final baseY = h * 0.74;
|
||||
final span = w * 0.26;
|
||||
|
||||
final arc = Path()
|
||||
..moveTo(cx - span, baseY)
|
||||
..quadraticBezierTo(cx, baseY - h * 0.36, cx + span, baseY);
|
||||
canvas.drawPath(
|
||||
arc,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.18)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2);
|
||||
|
||||
for (var i = 0; i < canonical.length; i++) {
|
||||
final t = i / (canonical.length - 1);
|
||||
final x = cx + (2 * t - 1) * span;
|
||||
final y = baseY - h * 0.36 * (1 - (1 - (2 * t - 1).abs()) * 1.0);
|
||||
final isPlaced = placed.contains(canonical[i]) || (solved && i < 3);
|
||||
final nodeColor = isPlaced
|
||||
? (flash == i ? const Color(0xFFFF375F) : AppColors.saqelCyan)
|
||||
: const Color(0xFF102035);
|
||||
canvas.drawCircle(Offset(x, y), 18, Paint()..color = nodeColor);
|
||||
canvas.drawCircle(
|
||||
Offset(x, y),
|
||||
18,
|
||||
Paint()
|
||||
..color = (isPlaced || (i == placed.length && !solved))
|
||||
? flashColor(flash, i, isPlaced)
|
||||
: Colors.white.withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = i == placed.length && !solved ? 2.5 : 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, y),
|
||||
canonical[i],
|
||||
const TextStyle(
|
||||
color: Colors.black, fontWeight: FontWeight.w900, fontSize: 13));
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, y + 30),
|
||||
isPlaced ? '${_mosesShort[i]} ✓' : _mosesShort[i],
|
||||
const TextStyle(color: Color(0xFF94A3B8), fontSize: 10, height: 1.2),
|
||||
maxWidth: w * 0.24);
|
||||
}
|
||||
|
||||
if (solved) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.12),
|
||||
'اعترفَ موسى ثمّ قال «لا تُؤاخِذني»، فاستمرّ بأدبٍ في رحلةِ العلم (الكهف 73).',
|
||||
const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
} else if (flash >= 0) {
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.10),
|
||||
'التسلسلُ غيرُ صحيح — تبدأُ بالاعترافِ ثمّ طلبِ عدمِ المؤاخذةِ ثمّ الاستمرار.',
|
||||
const TextStyle(
|
||||
color: Color(0xFFFF375F),
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w600),
|
||||
maxWidth: w * 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
Color flashColor(int flash, int i, bool isPlaced) {
|
||||
if (!isPlaced && i == flash) return const Color(0xFFFF375F);
|
||||
return AppColors.saqelCyan.withValues(alpha: 0.9);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _MosesApologyPainter old) =>
|
||||
old.placed != placed || old.solved != solved || old.flash != flash;
|
||||
}
|
||||
|
||||
bool listEquals<T>(List<T?> a, List<T?> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
+637
@@ -0,0 +1,637 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC UNIT 2 — LISTENING ATTENTION (قصة الضيف)
|
||||
/// Lesson: الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_02/lesson_01.md (pages 34-37)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Built ONLY on extractable facts of the story of the guest who seized the
|
||||
/// narrator's house:
|
||||
/// - آداب الاستماع: «أنتبه وأركّز من بدء الاستماع إلى نهايته ضمن زمان محدد»،
|
||||
/// وإضاءة «حسنُ الاستماعِ أساسُ الانتفاعِ» (page 34).
|
||||
/// - تصحيح العبارات √/× (page 34) وحديث البيت عن اليوم السابع نقطة التحوّل
|
||||
/// التي قسمت إقامة الضيف إلى قسمين (page 35).
|
||||
/// - أسباب قوة القصة وصفات الشخصيات (page 36): تزوير الأوراق، حسن النية،
|
||||
/// قلة حيلة الراوي، قوة الضيف والتهديد بالسلاح.
|
||||
/// - نهاية القصة (page 36): بدأت والراوي في بيته وانتهت وهو مطرود خارجُهُ.
|
||||
/// FIDELITY NOTE: page 37 belongs to الدرس الثاني (أتحدث بطلاقة) yet the spec
|
||||
/// binds pages 34-37; this lab covers only the listening extractables of
|
||||
/// pages 34-36. Flagged for the academic reviewer before any release.
|
||||
/// ============================================================================
|
||||
|
||||
// عبارات √/×: الأولى أربع صحيحة والآخرة خاطئة (الهدف في التبديل: 4 ON + 1 OFF).
|
||||
const List<(String, bool)> _checkStatements = [
|
||||
('القصّةُ التي سمِعْتُها فيها أحداثٌ يمكنُ أن تحدثَ في الواقع.', true),
|
||||
('نوعُ الحوارِ في «قلتُ: لا بدَّ من أن يحزمَ أمرُهُ» حوارٌ داخليّ.', true),
|
||||
('حسنُ الاستماعِ أساسُ الانتفاعِ.', true),
|
||||
(
|
||||
'يكونُ اليومُ السابعُ نقطةَ التحوّلِ التي قسَمت إقامةَ الضيفِ إلى قسمينِ.',
|
||||
true
|
||||
),
|
||||
(
|
||||
'بدأتِ القصّةُ والراوي مَطرودٌ خارجَ بيتِه، وانتهت ورجعَ الضيفُ إلى بلادِه.',
|
||||
false
|
||||
),
|
||||
];
|
||||
|
||||
// صفاتُ وأفعالُ الشخصيات: الضيف (0) أو الراوي (1) — من أسباب القوة (صفحة 36).
|
||||
const List<(String, int)> _characterRows = [
|
||||
('تزويرُ الأوراقِ الخاصةِ بالإيجارِ.', 0),
|
||||
('حُسنُ النيّّةِ في استقبالِ الآخرِ.', 1),
|
||||
('قِلّةُ حيلةِ راوي القصّةِ.', 1),
|
||||
('قوّةُ الضيفِ والتهديدُ بالسلاحِ.', 0),
|
||||
];
|
||||
|
||||
// مراحل القصة الزمنية في ترتيبها الصحيح.
|
||||
const List<(String, String)> _storyParts = [
|
||||
('أ', 'استقبلَ الراوي الضيفَ بحُسنِ نيّةٍ داخلَ بيتِه.'),
|
||||
('ب', 'تصرّفَ الضيفُ كأنّه في بيتِه، وكانَ اليومُ السابعُ نقطةَ التحوّلِ.'),
|
||||
('ج', 'تزويرُ أوراقِ الإيجارِ وضعفُ سلطةِ المحكمةِ.'),
|
||||
('د', 'خرجَ الراوي مطرودًا من بيتِه، وانتهتِ القصّةُ وهو خارجُهُ.'),
|
||||
];
|
||||
const List<String> _storyCanonical = ['أ', 'ب', 'ج', 'د'];
|
||||
const List<int> _storyDisplay = [3, 1, 0, 2]; // د، ب، أ، ج — شفرة.
|
||||
|
||||
class ArabicUnit2ListeningLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicUnit2ListeningLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicUnit2ListeningLabView> createState() =>
|
||||
_ArabicUnit2ListeningLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicUnit2ListeningLabViewState
|
||||
extends State<ArabicUnit2ListeningLabView> {
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — تصحيح العبارات.
|
||||
final List<bool> _checks = List.filled(_checkStatements.length, false);
|
||||
bool _checkTouched = false;
|
||||
|
||||
// Activity 2 — صفات الشخصيات.
|
||||
final List<int?> _characters = List<int?>.filled(4, null);
|
||||
|
||||
// Activity 3 — ترتيب مراحل القصة.
|
||||
final List<String> _placed = [];
|
||||
int _wrongCount = 0;
|
||||
int _flash = -1;
|
||||
bool _solved = false;
|
||||
|
||||
bool get _checksDone {
|
||||
for (var i = 0; i < _checkStatements.length; i++) {
|
||||
if (_checks[i] != _checkStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _charactersDone {
|
||||
for (var i = 0; i < _characters.length; i++) {
|
||||
if (_characters[i] != _characterRows[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _checksDone && _checkTouched;
|
||||
if (_activity == 1) return _charactersDone;
|
||||
return _solved;
|
||||
}
|
||||
|
||||
void _tapPart(String letter) {
|
||||
if (_solved || _placed.contains(letter)) return;
|
||||
setState(() {
|
||||
_placed.add(letter);
|
||||
if (_placed.length == 4) {
|
||||
var correct = true;
|
||||
for (var i = 0; i < _storyCanonical.length; i++) {
|
||||
if (_placed[i] != _storyCanonical[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (correct) {
|
||||
_solved = true;
|
||||
saqelTick();
|
||||
} else {
|
||||
_wrongCount++;
|
||||
_flash = _storyCanonical.indexOf(_placed[0]);
|
||||
_placed.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetParts() {
|
||||
setState(() {
|
||||
_placed.clear();
|
||||
_wrongCount = 0;
|
||||
_flash = -1;
|
||||
_solved = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _ChecklistPainter(checks: List.of(_checks)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _CharactersPainter(characters: List.of(_characters)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _StoryChainPainter(
|
||||
placed: List.of(_placed),
|
||||
canonical: _storyCanonical,
|
||||
solved: _solved,
|
||||
flash: _flash,
|
||||
displayOrder: _storyDisplay,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['صحِّحِ العبارات', 'ميّزِ الشخصيّات', 'رتِّبِ الحكاية'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'ضعْ علامةَ الصوابِ إزاءَ العباراتِ الصحيحةِ، واتركِ الخاطئةَ مُعطّلةً.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _checkStatements.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: LabToggle(
|
||||
label: (i == _checkStatements.length - 1 ? '( ) ' : '( √ ) ') +
|
||||
_checkStatements[i].$1,
|
||||
value: _checks[i],
|
||||
onChanged: (v) => setState(() {
|
||||
_checks[i] = v;
|
||||
_checkTouched = true;
|
||||
if (_checksDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (_checksDone)
|
||||
const LabPill('عباراتُك صحيحة: أربعةُ صوابٍ وواحدةٍ خاطئة ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الصواب: أحداثٌ تُحدّث في الواقع، حوارٌ داخليّ، أساس الانتفاع، واليومُ السابعُ نقطةُ التحوّل. والخاطئة: بدايةُ القصّةِ بالطردِ.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'مِنْ أسبابِ قوّةِ القصّةِ: ميّزِ صفةَ كلّ سببٍ: هي للضيفِ أم للراوي؟',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _characterRows.length; i++) ...[
|
||||
Text(
|
||||
_characterRows[i].$1,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: const ['الضيفُ', 'الراوي'],
|
||||
values: const [0, 1],
|
||||
current: _characters[i] ?? -1,
|
||||
accent: _characters[i] == _characterRows[i].$2
|
||||
? const Color(0xFF30D158)
|
||||
: (i == 0 ? const Color(0xFF0071E3) : const Color(0xFFFF9F0A)),
|
||||
onSelected: (v) => setState(() {
|
||||
_characters[i] = v;
|
||||
if (_charactersDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
if (_charactersDone)
|
||||
const LabPill(
|
||||
'ميّزتَ الشخصيّات: تزويرٌ وتهديدٌ للضيف، ونيّةٌ وحيلةٌ للراوي ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'رتِّبْ مراحلَ القصّةِ زمنيًّا من حسنِ الاستقبالِ إلى خروجِ الراوي مطرودًا.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (final idx in _storyDisplay)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: _placed.contains(_storyParts[idx].$1)
|
||||
? Colors.white.withValues(alpha: 0.06)
|
||||
: (_flash == idx
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.02)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _solved ? null : () => _tapPart(_storyParts[idx].$1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _placed.contains(_storyParts[idx].$1)
|
||||
? const Color(0xFF00F5D4).withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: _placed.contains(_storyParts[idx].$1)
|
||||
? const Color(0xFF00F5D4)
|
||||
: Colors.black.withValues(alpha: 0.35),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_storyParts[idx].$1,
|
||||
style: TextStyle(
|
||||
color: _placed.contains(_storyParts[idx].$1)
|
||||
? Colors.black
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_storyParts[idx].$2,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_wrongCount >= 2 && !_solved)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'خطوة صغيرة: الاستقبالُ بحسنِ النيّةِ ثمّ تصرُّفاتُ الضيفِ ونقطةُ اليومِ السابعِ ثمّ تزويرُ الأوراقِ ثمّ الخروجُ مطرودًا.',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _resetParts,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('صفّر الترتيب'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question: 'لماذا يُعدُّ اليومُ السابعُ نقطةَ تحوّلٍ في القصّةِ؟',
|
||||
options: const [
|
||||
'لأنّه قسّم إقامةَ الضيفِ إلى قسمينِ؛ فتغيّرَ منحى الأحداثِ بعدَه في القصّةِ',
|
||||
'لأنّه آخرُ أيّامِ الأسبوعِ عددًا فقط',
|
||||
'لأنّه اليومُ الوحيدُ الذي لم يظهرْ فيه الضيفُ',
|
||||
'لأنّه لا علاقةَ له بأحداثِ القصّةِ',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicUnit2ListeningLabIdentity,
|
||||
titleAr: 'مختبر الاستماع الواعي (قصة الضيف)',
|
||||
subtitleAr: 'أستمعُ بانتباهٍ وتركيزٍ — وللضيفِ حكايةٌ',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 34-37. يُغطي المختبر قابلَ الإثباتِ من صفحات 34-36 (آداب الاستماع، تصحيح العبارات، نقطة التحوّل، أسباب القوة، النهاية)؛ صفحة 37 تعود فعليًا للدرس الثاني (أتحدث بطلاقة) رغم ورودها في نطاق البطاقة. قرار المراجع الأكاديمي مطلوب قبل النشر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — ورقة تصحيح العبارات.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _ChecklistPainter extends CustomPainter {
|
||||
final List<bool> checks;
|
||||
|
||||
const _ChecklistPainter({required this.checks});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _allCorrect();
|
||||
final t = Offset(w / 2, h * 0.12);
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
t,
|
||||
done
|
||||
? 'كلُّ العباراتِ موقّعةٌ على صوابها ✓'
|
||||
: 'سجّلْ ملاحظاتِ الاستماعِ √ و ×',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
// Margin note card.
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.12, h * 0.20, w * 0.76, h * 0.56),
|
||||
const Radius.circular(10));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.20)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
|
||||
for (var i = 0; i < checks.length; i++) {
|
||||
final y = h * 0.20 + 20 + i * (h * 0.56 - 40) / checks.length;
|
||||
final on = checks[i];
|
||||
final target = _checkStatements[i].$2;
|
||||
final wrongOn = on && !target;
|
||||
final color = on
|
||||
? (wrongOn ? const Color(0xFFFF375F) : const Color(0xFF30D158))
|
||||
: const Color(0xFF102035);
|
||||
final slot = Rect.fromCenter(
|
||||
center: Offset(w / 2, y),
|
||||
width: w * 0.66,
|
||||
height: (h * 0.56 - 40) / checks.length - 8);
|
||||
canvas.drawRRect(RRect.fromRectAndRadius(slot, const Radius.circular(7)),
|
||||
Paint()..color = color.withValues(alpha: on ? 0.18 : 0.0));
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(slot.center.dx, slot.center.dy),
|
||||
on ? '✓' : '…',
|
||||
TextStyle(color: color, fontSize: 13, fontWeight: FontWeight.w900),
|
||||
maxWidth: 24);
|
||||
}
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.86),
|
||||
'الإنضاعُ: الاستماعُ واعٍ من البدايةِ إلى النهايةِ ضمنَ زمانٍ محدّد.',
|
||||
const TextStyle(color: Colors.white54, fontSize: 10.5),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
bool _allCorrect() {
|
||||
for (var i = 0; i < checks.length; i++) {
|
||||
if (checks[i] != _checkStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 3,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _ChecklistPainter old) =>
|
||||
!listEqualsBool(old.checks, checks);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — شخصيتا القصة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _CharactersPainter extends CustomPainter {
|
||||
final List<int?> characters;
|
||||
|
||||
const _CharactersPainter({required this.characters});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final guest = characters.where((c) => c == 0).length;
|
||||
final narrator = characters.where((c) => c == 1).length;
|
||||
final done = _allCorrect();
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.13),
|
||||
done
|
||||
? 'تميّزَتْ شخصيّتا القصّةِ ✓'
|
||||
: 'من يفعلُ ماذا؟ الضيفُ أم الراوي؟',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
_person(canvas, Offset(w * 0.30, h * 0.44), 'الضيفُ',
|
||||
const Color(0xFFFF375F), guest, 'التخطيطُ، والتزويرُ، والتهديدُ');
|
||||
_person(canvas, Offset(w * 0.70, h * 0.44), 'الراوي',
|
||||
const Color(0xFF30D158), narrator, 'حُسنُ النيّةِ، وقِلّةُ الحيلةِ');
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.84),
|
||||
'أسبابُ قوّةِ القصّةِ: قوةُ الضيفِ والتهديدُ، تزويرُ الأوراقِ، ضعفُ سلطةِ المحكمةِ، وقِلّةُ حيلةِ الراوي.',
|
||||
const TextStyle(color: Colors.white54, fontSize: 10.5),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
bool _allCorrect() {
|
||||
for (var i = 0; i < characters.length; i++) {
|
||||
if (characters[i] != _characterRows[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _person(Canvas canvas, Offset c, String name, Color color, int count,
|
||||
String traits) {
|
||||
final r = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: c, width: 150, height: 96),
|
||||
const Radius.circular(14));
|
||||
canvas.drawRRect(r, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
r,
|
||||
Paint()
|
||||
..color = color.withValues(alpha: 0.55)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4);
|
||||
canvas.drawCircle(Offset(c.dx, c.dy - 22), 11, Paint()..color = color);
|
||||
_centerText(canvas, Offset(c.dx, c.dy + 6), name,
|
||||
TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.w800),
|
||||
maxWidth: 140);
|
||||
_centerText(canvas, Offset(c.dx, c.dy + 30), '$count/2',
|
||||
const TextStyle(color: Colors.white, fontSize: 11),
|
||||
maxWidth: 50);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 3,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _CharactersPainter old) =>
|
||||
!listEqualsNullable(old.characters, characters);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — سلسلة زمنية لمشاهد القصة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _StoryChainPainter extends CustomPainter {
|
||||
final List<String> placed;
|
||||
final List<String> canonical;
|
||||
final bool solved;
|
||||
final int flash;
|
||||
final List<int> displayOrder;
|
||||
|
||||
const _StoryChainPainter({
|
||||
required this.placed,
|
||||
required this.canonical,
|
||||
required this.solved,
|
||||
required this.flash,
|
||||
required this.displayOrder,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final cx = w / 2;
|
||||
final y = h * 0.60;
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.10, y),
|
||||
Offset(w * 0.90, y),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.20)
|
||||
..strokeWidth = 2);
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
final idx = displayOrder[i];
|
||||
final letter = _storyParts[idx].$1;
|
||||
final x = w * (0.12 + 0.24 * i);
|
||||
final isPlaced = placed.contains(letter);
|
||||
final color = isPlaced
|
||||
? (flash == idx ? const Color(0xFFFF375F) : const Color(0xFF00F5D4))
|
||||
: const Color(0xFF102035);
|
||||
canvas.drawCircle(Offset(x, y), 15, Paint()..color = color);
|
||||
canvas.drawCircle(
|
||||
Offset(x, y),
|
||||
15,
|
||||
Paint()
|
||||
..color = isPlaced
|
||||
? Colors.white.withValues(alpha: 0.7)
|
||||
: Colors.white.withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(x, y),
|
||||
letter,
|
||||
const TextStyle(
|
||||
color: Colors.black, fontWeight: FontWeight.w900, fontSize: 12));
|
||||
}
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(cx, h * 0.16),
|
||||
solved
|
||||
? 'حكايةُ الضيفِ مكتملة: استقبالٌ ← تحوّلٌ ← تزويرٌ ← طردٌ ✓'
|
||||
: 'مشاهدُ الحكايةِ تتسلسلُ زمنيًّا — اخترْها بالترتيبِ الصحيحِ',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: maxWidth ?? 200);
|
||||
tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _StoryChainPainter old) =>
|
||||
old.placed != placed || old.solved != solved || old.flash != flash;
|
||||
}
|
||||
|
||||
bool listEqualsBool(List<bool> a, List<bool> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool listEqualsNullable<T>(List<T?> a, List<T?> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,668 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — READING RESISTANCE POETRY FLUENTLY (أقرأُ بطلاقةٍ وفهمٍ)
|
||||
/// Lesson: الدرس الثالث — أقرأُ بطلاقةٍ وفهمٍ (إلى الصامدين غرب النهر)
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_02/lesson_03.md (pages 39-45)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Built ONLY on extractable facts from the source pages:
|
||||
/// - علاماتُ (أستعد) والنشاط (1.3) أضيف إلى معجمي + مفردات المعجم
|
||||
/// (الأندادُ، الكابي، بيادرُكم، سفرٌ، الطَّلُّ...).
|
||||
/// - القصيدة «إلى الصامدين غرب النهر» لخالد محادين (1941-2015) من الكرك،
|
||||
/// الأغنيةُ الأولى من «صلواتٌ للفجرِ الطالعِ» (1969).
|
||||
/// - جوّ النص: شُغِلَت القضيةُ الفلسطينيةُ الشاعرَ منذ صغره؛ بعد حربِ حزيرانَ
|
||||
/// (1967) نظم قصائدَ عديدةً مجّد فيها المقاومةَ؛ انهى القصيدةَ متفائلًا
|
||||
/// (صفحة 42).
|
||||
/// - تعريف شعر التفعيلة ومثال تنوّع قوافيه (صفحة 42): «أسطرٌ شعريةٌ موزونةٌ
|
||||
/// بوزنٍ واحدٍ، مختلفَةِ الطِّوَلِ... يُنوّعُ القوافيَ وفقَ رغبتهِ ورؤيتِهِ
|
||||
/// الفنّيةِ».
|
||||
/// - أنشطة (2.3) الأفهم وأحلّل و(3.3) أتذوّق وأنقد مكثّفة؛ بعض عباراتها
|
||||
/// OCR-متداخل ولم تُخترع تفاصيل ناقصة (نصّ الموازنة مع عبد الرزاق
|
||||
/// البرغوثي وطلب الرجوع لديوان فدوى طوقان «لن أبكي» لا يُنمذجان سوى
|
||||
/// بفتحِ مسارِ بحثٍ يُسجَّل للمراجعة الأكاديمية).
|
||||
/// ============================================================================
|
||||
|
||||
// المفردات الأربع المختارة من معجم الدرس في (1.3) أضيفُ إلى معجمي.
|
||||
// كل صف: الكلمة + معناها الصحيح + خيارات مخلوطة + موضع الصواب.
|
||||
const List<(String, String, List<String>, int)> _lexRows = [
|
||||
(
|
||||
'الأندادُ',
|
||||
'نوعٌ منَ الطِّيبِ يُخلَطُ فيهِ المسكُ والكافورُ',
|
||||
[
|
||||
'موضعٌ يُداسُ فيهِ القمحُ حتى يخرجَ سُنبُلُهُ',
|
||||
'نوعٌ منَ الطِّيبِ يُخلَطُ فيهِ المسكُ والكافورُ',
|
||||
'كتابٌ',
|
||||
],
|
||||
1,
|
||||
),
|
||||
(
|
||||
'الكابي',
|
||||
'العاثرُ العاجزُ الذي يُدعى إلى الخيرِ فلا يُجيبُ',
|
||||
[
|
||||
'العاثرُ العاجزُ الذي يُدعى إلى الخيرِ فلا يُجيبُ',
|
||||
'نوعٌ منَ الطِّيبِ يُخلَطُ فيهِ المسكُ والكافورُ',
|
||||
'كتابٌ',
|
||||
],
|
||||
0,
|
||||
),
|
||||
(
|
||||
'بيادرُكم',
|
||||
'موضعٌ يُداسُ فيهِ القمحُ حتى يخرجَ سُنبُلُهُ',
|
||||
[
|
||||
'العاثرُ العاجزُ الذي لا يُجيبُ الداعيَ',
|
||||
'موضعٌ يُداسُ فيهِ القمحُ حتى يخرجَ سُنبُلُهُ',
|
||||
'نوعٌ منَ الطِّيبِ يُخلَطُ فيهِ المسكُ والكافورُ',
|
||||
],
|
||||
1,
|
||||
),
|
||||
(
|
||||
'السَّفَرُ',
|
||||
'الكتابُ',
|
||||
[
|
||||
'موضعٌ يُداسُ فيهِ القمحُ حتى يخرجَ سُنبُلُهُ',
|
||||
'الكتابُ',
|
||||
'العاثرُ العاجزُ الذي لا يُجيبُ الداعيَ',
|
||||
],
|
||||
1,
|
||||
),
|
||||
];
|
||||
|
||||
// مراحل سير القصيدة من «جوّ النص» (صفحة 42) مرتبةً زمنيًا.
|
||||
const List<(String, String)> _poemJourney = [
|
||||
('أ', 'يَبكي الشاعرُ ضياعَ فلسطينَ في افتتاحِ قصيدتِهِ.'),
|
||||
('ب', 'يَبعثُ رسائلَ إلى أهلِها الصامدينَ غربَ النهرِ لمؤازرتِهِم.'),
|
||||
('ج', 'يُبيّنُ تعاطفَهُ معهم وعمقِ ارتباطِ الأردنِّ وفلسطينَ.'),
|
||||
('د', 'يُنهي الشاعرُ القصيدةَ وهو متفائلٌ.'),
|
||||
];
|
||||
const List<String> _journeyCanonical = ['أ', 'ب', 'ج', 'د'];
|
||||
const List<int> _journeyDisplay = [2, 0, 3, 1]; // ج، أ، د، ب — شُفرة.
|
||||
|
||||
// خصائص شعر التفعيلة من تعريف القصيدة (صفحة 42).
|
||||
const List<(String, bool)> _prosodyStatements = [
|
||||
('تنتمي هذه القصيدةُ إلى شعرِ التفعيلةِ، وهو شعرٌ حديثٌ.', true),
|
||||
('يتكوّنُ شعرُ التفعيلةِ من أسطرٍ شعريةٍ موزونةٍ مختلفةِ الطِّوَلِ.', true),
|
||||
('يلتزمُ الشاعرُ في جميعِ أسطرِهِ بقافيةٍ واحدةٍ ثابتةٍ.', false),
|
||||
('يُنوّعُ الشاعرُ القوافيَ وفقَ رغبتِهِ ورؤيتِهِ الفنّيةِ.', true),
|
||||
('مثالٌ على تنوُّعِ القوافي: البابُ - أحبابي - الكابي - الغابُ.', true),
|
||||
(
|
||||
'يُلزَمُ الشاعرُ في شعرِ التفعيلةِ بتساوي الأسطرِ طولًا في كلِّ القصيدةِ.',
|
||||
false
|
||||
),
|
||||
];
|
||||
|
||||
class ArabicUnit2PoetryLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicUnit2PoetryLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicUnit2PoetryLabView> createState() =>
|
||||
_ArabicUnit2PoetryLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicUnit2PoetryLabViewState extends State<ArabicUnit2PoetryLabView> {
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — مفردات معجم القصيدة.
|
||||
final List<int?> _lex = List<int?>.filled(_lexRows.length, null);
|
||||
bool _lexTouched = false;
|
||||
|
||||
// Activity 2 — مسار جوّ القصيدة.
|
||||
final List<String> _placed = [];
|
||||
int _wrongCount = 0;
|
||||
int _flash = -1;
|
||||
bool _solved = false;
|
||||
|
||||
// Activity 3 — خصائص شعر التفعيلة.
|
||||
final List<bool> _prosody = List.filled(_prosodyStatements.length, false);
|
||||
|
||||
bool get _lexDone {
|
||||
for (var i = 0; i < _lexRows.length; i++) {
|
||||
if (_lex[i] != _lexRows[i].$4) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _prosodyDone {
|
||||
for (var i = 0; i < _prosodyStatements.length; i++) {
|
||||
if (_prosody[i] != _prosodyStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _lexDone && _lexTouched;
|
||||
if (_activity == 1) return _solved;
|
||||
return _prosodyDone;
|
||||
}
|
||||
|
||||
void _tapPart(String letter) {
|
||||
if (_solved || _placed.contains(letter)) return;
|
||||
setState(() {
|
||||
_placed.add(letter);
|
||||
if (_placed.length == 4) {
|
||||
var correct = true;
|
||||
for (var i = 0; i < _journeyCanonical.length; i++) {
|
||||
if (_placed[i] != _journeyCanonical[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (correct) {
|
||||
_solved = true;
|
||||
saqelTick();
|
||||
} else {
|
||||
_wrongCount++;
|
||||
_flash = _journeyCanonical.indexOf(_placed[0]);
|
||||
_placed.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetParts() {
|
||||
setState(() {
|
||||
_placed.clear();
|
||||
_wrongCount = 0;
|
||||
_flash = -1;
|
||||
_solved = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _LexiconPainter(lex: List.of(_lex)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _JourneyPainter(
|
||||
placed: List.of(_placed),
|
||||
canonical: _journeyCanonical,
|
||||
solved: _solved,
|
||||
flash: _flash,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _ProsodyPainter(checks: List.of(_prosody)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['معجمُ القصيدةِ', 'جوُّ القصيدةِ', 'رَشِيقُ التفعيلةِ'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أضِفْ إلى معجمِكِ: اقرنْ كلَّ كلمةٍ من القصيدةِ بمعناها الدقيقِ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _lexRows.length; i++) ...[
|
||||
Text(
|
||||
_lexRows[i].$1,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 13, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: const ['أ', 'ب', 'ج'],
|
||||
values: const [0, 1, 2],
|
||||
current: _lex[i] ?? -1,
|
||||
accent: _lex[i] == _lexRows[i].$4
|
||||
? const Color(0xFF30D158)
|
||||
: (i.isEven
|
||||
? const Color(0xFF0071E3)
|
||||
: const Color(0xFFFF9F0A)),
|
||||
onSelected: (v) => setState(() {
|
||||
_lex[i] = v;
|
||||
_lexTouched = true;
|
||||
if (_lexDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (_lex[i] != null)
|
||||
Text(
|
||||
_lexRows[i].$3[_lex[i]!],
|
||||
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 11),
|
||||
),
|
||||
],
|
||||
if (_lexDone)
|
||||
const LabPill(
|
||||
'معجمُ القصيدةِ محفوظٌ: الأندادُ طيبٌ، والكابي عاجزٌ، والبيادرُ لدياسِ القمحِ، والسفرُ كتابٌ ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'رتِّبْ سيرَ القصيدةِ كما وردَ في «جوِّ النصِّ»: من بكاءِ الضياعِ إلى الخاتمةِ المتفائِلَةِ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (final idx in _journeyDisplay)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: _placed.contains(_poemJourney[idx].$1)
|
||||
? Colors.white.withValues(alpha: 0.06)
|
||||
: (_flash == idx
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.02)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _solved ? null : () => _tapPart(_poemJourney[idx].$1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _placed.contains(_poemJourney[idx].$1)
|
||||
? const Color(0xFF00F5D4).withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: _placed.contains(_poemJourney[idx].$1)
|
||||
? const Color(0xFF00F5D4)
|
||||
: Colors.black.withValues(alpha: 0.35),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_poemJourney[idx].$1,
|
||||
style: TextStyle(
|
||||
color: _placed.contains(_poemJourney[idx].$1)
|
||||
? Colors.black
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_poemJourney[idx].$2,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_wrongCount >= 2 && !_solved)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'خطوة صغيرة: البدايةُ بكاءُ الضياعِ، ثمّ بعثُ الرسائلِ، ثمّ بيانُ الارتباطِ، ثمّ الخاتمةُ المتفائلةُ.',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _resetParts,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('إعادةُ ترتيبِ المسارِ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'علِّقْ على خصائصِ شعرِ التفعيلةِ في هذه القصيدةِ: صوابٌ أو خطأٌ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _prosodyStatements.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: LabToggle(
|
||||
label: (i == 2 || i == 5 ? '( ) ' : '( √ ) ') +
|
||||
_prosodyStatements[i].$1,
|
||||
value: _prosody[i],
|
||||
onChanged: (v) => setState(() {
|
||||
_prosody[i] = v;
|
||||
if (_prosodyDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (_prosodyDone)
|
||||
const LabPill(
|
||||
'رَشِيقُ التفعيلةِ واضحٌ: أسطرُها موزونةٌ متباينةُ الطولِ، قوافيها متنوّعةٌ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الصواب: شعرٌ حديثٌ موزونٌ بأسطرٍ مختلفةِ الطولِ، والقوافي تُنوّعُ (البابُ - أحبابي - الكابي - الغابُ). الخطأ: قافيةٌ واحدةٌ ثابتةٌ وتَساوي الأسطرِ.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question:
|
||||
'كيفَ أنهى الشاعرُ القصيدةَ، وما الدليلُ على ذلكَ في «جوِّ النصِّ»؟',
|
||||
options: const [
|
||||
'متفائلًا؛ فالنصُّ يذكرُ أنّ الشاعرَ ينهي القصيدةَ وهو متفائلٌ',
|
||||
'باكيًا في كلِّ قصيدتِهِ دونَ أملٍ',
|
||||
'صامتًا لا يختمُ النصَّ بشيءٍ',
|
||||
'غاضبًا فقط من دونِ مؤازرةٍ',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicUnit2PoetryLabIdentity,
|
||||
titleAr: 'مختبر قراءةِ شعرِ المقاومة (إلى الصامدين غرب النهر)',
|
||||
subtitleAr: 'أقرأُ بطلاقةٍ وفهمٍ — لخالدِ محادينَ',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 39-45. بُني المختبر على معجمِ النشاط (1.3)، وجوِّ النصِّ (يبكي ضياعَ فلسطينَ ثمّ يؤازرُ ثمّ ينهي متفائلًا)، وتعاريفِ شعرِ التفعيلةِ بصفحة 42. أنشطةُ التحليل (2.3)/(3.3) مكثّفةٌ وبعضُ نصوصِها OCR-متداخل؛ نصُّ الموازنةِ مع عبدِ الرزاقِ البرغوثيِّ ووصفُ صفحةِ 45 وطلبُ الرجوعِ لديوانِ فدوى طوقانَ («لن أبكي») لم تُنمذَجْ بنصوصٍ مخترعةٍ — يُكملُ المراجعُ الأكاديميّ ما نقصَ أو تصحيحُهُ قبلَ النشر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — قاموس المقاومة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _LexiconPainter extends CustomPainter {
|
||||
final List<int?> lex;
|
||||
|
||||
const _LexiconPainter({required this.lex});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
done
|
||||
? 'الكلماتُ تُخدَمُ بِمعناها ✓'
|
||||
: 'اقرنْ كلَّ لفظٍ بمعناهُ في المعجمِ',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
for (var i = 0; i < _lexRows.length; i++) {
|
||||
final y = h * 0.22 + i * (h * 0.17);
|
||||
final choice = lex[i];
|
||||
final correct = choice == _lexRows[i].$4;
|
||||
|
||||
// بطاقة اللفظ.
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.10, y, w * 0.80, h * 0.10),
|
||||
const Radius.circular(9));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = choice == null
|
||||
? Colors.white12
|
||||
: (correct
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.6)
|
||||
: const Color(0xFFFF375F).withValues(alpha: 0.45))
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: _lexRows[i].$1,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF00F5D4),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(canvas,
|
||||
Offset(w * 0.40 - tp.width / 2, y + h * 0.05 - tp.height / 2));
|
||||
|
||||
if (choice != null) {
|
||||
final m = TextPainter(
|
||||
text: TextSpan(
|
||||
text: _lexRows[i].$3[choice],
|
||||
style: TextStyle(
|
||||
color:
|
||||
correct ? const Color(0xFFE2E8F0) : const Color(0xFFFF375F),
|
||||
fontSize: 10),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 1,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: w * 0.66);
|
||||
m.paint(canvas, Offset(w * 0.24, y + h * 0.135 - m.height / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _lexRows.length; i++) {
|
||||
if (lex[i] != _lexRows[i].$4) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_LexiconPainter old) => old.lex != lex;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — مسار جوّ القصيدة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _JourneyPainter extends CustomPainter {
|
||||
final List<String> placed;
|
||||
final List<String> canonical;
|
||||
final bool solved;
|
||||
final int flash;
|
||||
|
||||
const _JourneyPainter({
|
||||
required this.placed,
|
||||
required this.canonical,
|
||||
required this.solved,
|
||||
required this.flash,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
solved
|
||||
? 'جاءَ سيرُ القصيدةِ متناسقًا ✓'
|
||||
: 'سِرْ في مسارِ جوِّ القصيدةِ',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
final path = Path()
|
||||
..moveTo(w * 0.16, h * 0.24)
|
||||
..lineTo(w * 0.84, h * 0.24)
|
||||
..lineTo(w * 0.84, h * 0.40)
|
||||
..lineTo(w * 0.16, h * 0.40)
|
||||
..lineTo(w * 0.16, h * 0.56)
|
||||
..lineTo(w * 0.84, h * 0.56)
|
||||
..lineTo(w * 0.84, h * 0.72)
|
||||
..lineTo(w * 0.50, h * 0.72);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4).withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3);
|
||||
|
||||
final nodes = [
|
||||
Offset(w * 0.50, h * 0.24),
|
||||
Offset(w * 0.16, h * 0.40),
|
||||
Offset(w * 0.84, h * 0.56),
|
||||
Offset(w * 0.50, h * 0.72),
|
||||
];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
final letter = canonical[i];
|
||||
final on = placed.contains(letter);
|
||||
final circle = Path()
|
||||
..addOval(Rect.fromCircle(center: nodes[i], radius: 18));
|
||||
canvas.drawPath(
|
||||
circle,
|
||||
Paint()
|
||||
..color = on ? const Color(0xFF00F5D4) : const Color(0xFF102035));
|
||||
canvas.drawPath(
|
||||
circle,
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5);
|
||||
final lt = TextPainter(
|
||||
text: TextSpan(
|
||||
text: letter,
|
||||
style: TextStyle(
|
||||
color: on ? Colors.black : Colors.white70,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w900),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
lt.paint(canvas,
|
||||
Offset(nodes[i].dx - lt.width / 2, nodes[i].dy - lt.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_JourneyPainter old) =>
|
||||
old.placed != placed || old.solved != solved || old.flash != flash;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — رَشيقُ التفعيلة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _ProsodyPainter extends CustomPainter {
|
||||
final List<bool> checks;
|
||||
|
||||
const _ProsodyPainter({required this.checks});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
done
|
||||
? 'خصائصُ التفعيلةِ مثبَّتةٌ ✓'
|
||||
: 'أسطرٌ موزونةٌ متباينةُ الطولِ، وقوافٍ متنوّعة',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
// أعمدة تمثّل الأسطر المتباينة الطول.
|
||||
const bars = [0.14, 0.30, 0.22, 0.40];
|
||||
final baseY = h * 0.72;
|
||||
for (var i = 0; i < bars.length; i++) {
|
||||
final bw = w * 0.12;
|
||||
final bh = h * bars[i];
|
||||
final rect =
|
||||
Rect.fromLTWH(w * 0.16 + i * (bw + w * 0.08), baseY - bh, bw, bh);
|
||||
canvas.drawRRect(RRect.fromRectAndRadius(rect, const Radius.circular(6)),
|
||||
Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, const Radius.circular(6)),
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4).withValues(alpha: 0.6)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5);
|
||||
}
|
||||
|
||||
// رُقَع القافية المتعددة.
|
||||
const rhymes = ['البابُ', 'أحبابي', 'الكابي', 'الغابُ'];
|
||||
final rhymeY = h * 0.86;
|
||||
for (var i = 0; i < rhymes.length; i++) {
|
||||
final rt = TextPainter(
|
||||
text: TextSpan(
|
||||
text: rhymes[i],
|
||||
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 10),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
rt.paint(
|
||||
canvas,
|
||||
Offset(
|
||||
w * 0.18 + i * (w * 0.17) + (w * 0.06 - rt.width) / 2, rhymeY));
|
||||
}
|
||||
|
||||
canvas.drawCircle(Offset(w * 0.90, h * 0.18), done ? 10 : 8,
|
||||
Paint()..color = const Color(0xFF30D158));
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _prosodyStatements.length; i++) {
|
||||
if (checks[i] != _prosodyStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_ProsodyPainter old) => old.checks != checks;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// مساعدات الرسم المشتركة.
|
||||
// ---------------------------------------------------------------------------
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 6,
|
||||
)..layout(maxWidth: maxWidth ?? double.infinity);
|
||||
tp.paint(canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
|
||||
}
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — SPEAKING FLUENTLY: THE ORAL PRESENTATION (أتحدثُ بطلاقةٍ)
|
||||
/// Lesson: الدرس الثاني — أتحدثُ بطلاقةٍ (العرضُ التقديميّ)
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_02/lesson_02.md (page 38)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Built ONLY on extractable facts from the page (heavily OCR-damaged):
|
||||
/// - The task (نشاط 3.2): «أعبّرُ شفهيًّا» + «أصمّمُ عرضًا تقديميًّا يعبّرُ عن
|
||||
/// المظاهرِ الحقيقيةِ للانتماءِ إلى وطنِنا، موظّفًا مهاراتِ التواصلِ
|
||||
/// البصريِّ، وأتحدّثُ بطلاقةٍ بسرعةٍ مناسبةٍ، وأعرضُهُ أمامَ صفِّي،
|
||||
/// بانسيابٍ ضمنَ زمنٍ محددٍ».
|
||||
/// - قيم الوطنية في النص: الوطنيةُ ليست أقوالًا ولا شعاراتٍ برّاقةً هدفُها
|
||||
/// الكسبُ والمنفعةُ الشّخصيةُ؛ حبُّ الوطنِ تُترجمهُ سلوكاتُنا القويمةُ
|
||||
/// وأفعالُنا؛ الولاءُ والانتماءُ والتكافلُ والتعاونُ والتراحمُ؛
|
||||
/// «ليكونوا كالبنيانِ المرصوصِ يشدُّ بعضُهم بعضًا في السرّاءِ والضرّاءِ».
|
||||
/// - رسالةُ جلالةِ الملكِ الحسينِ الثاني إلى أبناءِ الوطنِ بمناسبةِ عيدِ
|
||||
/// ميلادِه السّتين: «لنْ أنسى ما حييتُ كلماتِ والدي... أنّ الإخلاصَ
|
||||
/// والوفاءَ لهذا الوطنِ الحبيبِ (الأردنّ) ليظلَّ عزيزًا قويًّا، والحمى
|
||||
/// شرفٌ وواجبٌ».
|
||||
/// The OCR text interleaves this letter with the values fragment; the quoted
|
||||
///「والدي الحسين」attribution is OCR-ambiguous and left to the academic
|
||||
/// reviewer. Nothing was invented; ambiguous attribution is not named.
|
||||
/// ============================================================================
|
||||
|
||||
// عبارات صواب وخطأ حول الوطنية: مستخلصة حرفياً من النص.
|
||||
const List<(String, bool)> _valueStatements = [
|
||||
(
|
||||
'الوطنيةُ ليستْ أقوالًا ولا شعاراتٍ برّاقةً هدفُها الكسبُ والمنفعةُ الشّخصيةُ.',
|
||||
true
|
||||
),
|
||||
('حبُّ الوطنِ والحفاظُ عليه تُترجمهُ سلوكاتُنا القويمةُ وأفعالُنا.', true),
|
||||
(
|
||||
'أبناءُ الوطنِ كالبنيانِ المرصوصِ يشدُّ بعضُهم بعضًا في السرّاءِ والضرّاءِ.',
|
||||
true
|
||||
),
|
||||
('التكافلُ والتعاونُ والتراحمُ منْ مظاهرِ الانتماءِ إلى الوطنِ.', true),
|
||||
('الشعاراتُ البرّاقةُ وحدَها تُحفَظُ الوطنَ وتُترجمُهُ.', false),
|
||||
('الحمى شرفٌ وواجبٌ.', true),
|
||||
];
|
||||
|
||||
// عناصر إعداد العرض التقديمي: مطلوب (0) أو ليس مطلوبًا (1) بحسب نص النشاط.
|
||||
const List<(String, int)> _presentRows = [
|
||||
('التعبيرُ عن المظاهرِ الحقيقيةِ للانتماءِ إلى الوطنِ', 0),
|
||||
('توظيفُ مهاراتِ التواصلِ البصريِّ في العرضِ', 0),
|
||||
('التحدّثُ بطلاقةٍ بسرعةٍ مناسبةٍ', 0),
|
||||
('قراءةُ العرضِ حرفيًّا منَ الورقةِ بلا تلقائيّةٍ', 1),
|
||||
('عرضُهُ أمامَ الصفِّ ضمنَ زمنٍ محددٍ بانسيابٍ', 0),
|
||||
];
|
||||
|
||||
// كل صف: اقتباس من النص + خيارات (مخلوطة الترتيب) + موقع الصواب.
|
||||
const List<(String, List<String>, int)> _quoteRows = [
|
||||
(
|
||||
'«الوطنيةُ ليستْ أقوالًا ولا شعاراتٍ برّاقةً هدفُها الكسبُ.»',
|
||||
[
|
||||
'نفيُ الوطنيةِ الكلاميةِ والشّعاريةِ',
|
||||
'معانٍ محفورةٌ في الذاكرةِ لا يُزيلُها الزمنُ',
|
||||
'تماسكُ أبناءِ الوطنِ في السرّاءِ والضرّاءِ',
|
||||
'الدفاعُ عنِ الوطنِ شرفٌ وفرضُ واجبٍ',
|
||||
],
|
||||
0,
|
||||
),
|
||||
(
|
||||
'«ليكونوا كالبنيانِ المرصوصِ يشدُّ بعضُهم بعضًا.»',
|
||||
[
|
||||
'نفيُ الوطنيةِ الكلاميةِ والشّعاريةِ',
|
||||
'تماسكُ أبناءِ الوطنِ في السرّاءِ والضرّاءِ',
|
||||
'معانٍ محفورةٌ في الذاكرةِ لا يُزيلُها الزمنُ',
|
||||
'الدفاعُ عنِ الوطنِ شرفٌ وفرضُ واجبٍ',
|
||||
],
|
||||
1,
|
||||
),
|
||||
(
|
||||
'«والحمى شرفٌ وواجبٌ.»',
|
||||
[
|
||||
'تماسكُ أبناءِ الوطنِ في السرّاءِ والضرّاءِ',
|
||||
'معانٍ محفورةٌ في الذاكرةِ لا يُزيلُها الزمنُ',
|
||||
'الدفاعُ عنِ الوطنِ شرفٌ وفرضُ واجبٍ',
|
||||
'نفيُ الوطنيةِ الكلاميةِ والشّعاريةِ',
|
||||
],
|
||||
2,
|
||||
),
|
||||
(
|
||||
'«لنْ أنسى ما حييتُ كلماتِ والدي.»',
|
||||
[
|
||||
'الدفاعُ عنِ الوطنِ شرفٌ وفرضُ واجبٍ',
|
||||
'معانٍ محفورةٌ في الذاكرةِ لا يُزيلُها الزمنُ',
|
||||
'نفيُ الوطنيةِ الكلاميةِ والشّعاريةِ',
|
||||
'تماسكُ أبناءِ الوطنِ في السرّاءِ والضرّاءِ',
|
||||
],
|
||||
1,
|
||||
),
|
||||
];
|
||||
|
||||
class ArabicUnit2SpeakingLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicUnit2SpeakingLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicUnit2SpeakingLabView> createState() =>
|
||||
_ArabicUnit2SpeakingLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicUnit2SpeakingLabViewState
|
||||
extends State<ArabicUnit2SpeakingLabView> {
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — عبارات الوطنية.
|
||||
final List<bool> _checks = List.filled(_valueStatements.length, false);
|
||||
bool _checkTouched = false;
|
||||
|
||||
// Activity 2 — عناصر العرض التقديمي.
|
||||
final List<int?> _present = List<int?>.filled(_presentRows.length, null);
|
||||
|
||||
// Activity 3 — القول والمضمون.
|
||||
final List<int?> _matchings = List<int?>.filled(_quoteRows.length, null);
|
||||
|
||||
bool get _checksDone {
|
||||
for (var i = 0; i < _valueStatements.length; i++) {
|
||||
if (_checks[i] != _valueStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _presentDone {
|
||||
for (var i = 0; i < _presentRows.length; i++) {
|
||||
if (_present[i] != _presentRows[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _matchingsDone {
|
||||
for (var i = 0; i < _quoteRows.length; i++) {
|
||||
if (_matchings[i] != _quoteRows[i].$3) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _checksDone && _checkTouched;
|
||||
if (_activity == 1) return _presentDone;
|
||||
return _matchingsDone;
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _ValuesSheetPainter(checks: List.of(_checks)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _PresentationPainter(rows: List.of(_present)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _QuoteBoardPainter(matchings: List.of(_matchings)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['قيمُ الوطنيةِ', 'عرضُ التحدّثِ', 'القولُ والمضمونُ'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'عُلّقْ على العباراتِ الصحيحةِ وحدَها إزاءَ قيمِ الوطنيةِ ومظاهرِ الانتماءِ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _valueStatements.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: LabToggle(
|
||||
label: (i == 4 ? '( ) ' : '( √ ) ') + _valueStatements[i].$1,
|
||||
value: _checks[i],
|
||||
onChanged: (v) => setState(() {
|
||||
_checks[i] = v;
|
||||
_checkTouched = true;
|
||||
if (_checksDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (_checksDone)
|
||||
const LabPill(
|
||||
'قيمُ الوطنيةِ معطوبةٌ الآن: خمسُ صوابٍ وواحدةٌ تُنافي النصَّ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الصواب: الوطنيةُ فعلاً لا شعارَ؛ الحبُّ بالسلوكِ؛ البنيانُ المرصوصُ؛ انتماءٌ وتكافلٌ؛ والحمى شرفٌ وواجبٌ. والخاطئةُ: الشعاراتُ تُحفظُ الوطنَ وحدَها.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'بوصفِكَ مُعِدَّ العرضِ التقديميّ: حدّدْ ما يطلبُهُ النشاطُ وما لا يطلبُهُ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _presentRows.length; i++) ...[
|
||||
Text(
|
||||
_presentRows[i].$1,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: const ['مطلوبٌ', 'ليسَ مطلوبًا'],
|
||||
values: const [0, 1],
|
||||
current: _present[i] ?? -1,
|
||||
accent: _present[i] == _presentRows[i].$2
|
||||
? const Color(0xFF30D158)
|
||||
: (i.isEven
|
||||
? const Color(0xFF0071E3)
|
||||
: const Color(0xFFFF9F0A)),
|
||||
onSelected: (v) => setState(() {
|
||||
_present[i] = v;
|
||||
if (_presentDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
if (_presentDone)
|
||||
const LabPill(
|
||||
'خطّةُ العرضِ سليمةٌ: تعبيرٌ عن الانتماءِ + تواصلٌ بصريٌّ + طلاقةٌ بسرعةٍ مناسبةٍ + عرضٌ أمامَ الصفِّ بانسيابٍ ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'اقرنْ كلَّ قولٍ من النصِّ بمضمونِهِ الدالّ عليه.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
for (var i = 0; i < _quoteRows.length; i++) ...[
|
||||
Text(
|
||||
_quoteRows[i].$1,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<int>(
|
||||
labels: const ['أ', 'ب', 'ج', 'د'],
|
||||
values: const [0, 1, 2, 3],
|
||||
current: _matchings[i] ?? -1,
|
||||
accent: _matchings[i] == _quoteRows[i].$3
|
||||
? const Color(0xFF30D158)
|
||||
: (i.isEven
|
||||
? const Color(0xFF0071E3)
|
||||
: const Color(0xFFFF9F0A)),
|
||||
onSelected: (v) => setState(() {
|
||||
_matchings[i] = v;
|
||||
if (_matchingsDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (_matchings[i] != null)
|
||||
Text(
|
||||
_quoteRows[i].$2[_matchings[i]!],
|
||||
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 11),
|
||||
),
|
||||
],
|
||||
if (_matchingsDone)
|
||||
const LabPill(
|
||||
'كلُّ قولٍ مُسنَدٌ إلى مضمونِهِ: الوطنيةُ فعليّةٌ، والتماسكُ بالبنيانِ، والحمى شرفٌ وواجبٌ، والسِّتّونَ مَطيّةُ الذكرِ ✓',
|
||||
color: Color(0xFF30D158)),
|
||||
if (_discoveryDone) ...[
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question:
|
||||
'ما دلالةُ عبارةِ (والحمى شرفٌ وواجبٌ) في رسالةِ عيدِ الميلادِ؟',
|
||||
options: const [
|
||||
'الدفاعُ عنِ الوطنِ موضعُ شرفٍ وفخرٍ وهو التزامٌ لا يَسَعُ التخلّيَ عنه',
|
||||
'الدفاعُ عملٌ ثانويٌّ اختياريٌّ لا يلزمُ أحدًا',
|
||||
'الحمى أثرٌ تاريخيٌّ لا صلةَ لَهُ بصيانةِ الوطنِ',
|
||||
'الواجبُ مقصورٌ على الكلامِ دونَ العملِ',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicUnit2SpeakingLabIdentity,
|
||||
titleAr: 'مختبر العرض التقديمي (أتحدثُ بطلاقةٍ)',
|
||||
subtitleAr: 'أعبّرُ بطلاقةٍ عنِ الوطنِ — عرضٌ تقديميّ',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحة 38 (نصُّ OCR متداخل يحتاج مراجعة بشرية). بُني المختبر على نصوصِ النشاطِ (أصمّمُ عرضًا تقديميًّا... موظّفًا مهاراتِ التواصلِ البصريِّ... بطلاقةٍ بسرعةٍ مناسبةٍ... ضمنَ زمنٍ محددٍ) وقيمِ الوطنيةِ ورسالةِ الملكِ الحسينِ الثاني بعيدِ ميلادِه الستّين. نسبةُ قولِ (والدي الحسين) في النصِّ مُلتبسةً بالـOCR ولم تُسمَّ في المختبر؛ يحدِّدُها المراجعُ الأكاديميّ.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — دفتر قيمة الوطن.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _ValuesSheetPainter extends CustomPainter {
|
||||
final List<bool> checks;
|
||||
|
||||
const _ValuesSheetPainter({required this.checks});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
done
|
||||
? 'قيمُ الوطنيةِ مطابقةٌ للنصِّ ✓'
|
||||
: 'سجّلْ عندَ كلِّ عبارةٍ: √ أو (ترك)',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.12, h * 0.18, w * 0.76, h * 0.62),
|
||||
const Radius.circular(10));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
|
||||
for (var i = 0; i < checks.length; i++) {
|
||||
final y = h * 0.24 + i * (h * 0.50) / checks.length;
|
||||
final on = checks[i];
|
||||
final target = _valueStatements[i].$2;
|
||||
final wrongOn = on && !target;
|
||||
final color = on
|
||||
? (wrongOn ? const Color(0xFFFF375F) : const Color(0xFF30D158))
|
||||
: const Color(0xFF102035);
|
||||
|
||||
canvas.drawCircle(Offset(w * 0.22, y), 9, Paint()..color = color);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: on ? (wrongOn ? '✕' : '✓') : '·',
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(canvas, Offset(w * 0.22 - tp.width / 2, y - tp.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _valueStatements.length; i++) {
|
||||
if (checks[i] != _valueStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_ValuesSheetPainter old) => old.checks != checks;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — شاشة العرض التقديمي.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _PresentationPainter extends CustomPainter {
|
||||
final List<int?> rows;
|
||||
|
||||
const _PresentationPainter({required this.rows});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.09),
|
||||
done
|
||||
? 'خطّةُ العرضِ مكتملةٌ ✓'
|
||||
: 'أصمّمُ عرضًا تقديميًّا عنِ الانتماءِ',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.88);
|
||||
|
||||
// شريحة العرض.
|
||||
final slide = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.10, h * 0.17, w * 0.80, h * 0.34),
|
||||
const Radius.circular(8));
|
||||
canvas.drawRRect(slide, Paint()..color = const Color(0xFF102035));
|
||||
canvas.drawRRect(
|
||||
slide,
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.10, h * 0.17, w * 0.80, h * 0.055),
|
||||
const Radius.circular(8)),
|
||||
Paint()..color = const Color(0xFF00F5D4).withValues(alpha: 0.18));
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w * 0.50, h * 0.197),
|
||||
'عنوانُ الشريحةِ: حبُّ الوطنِ فعلٌ لا شعارٌ',
|
||||
const TextStyle(
|
||||
color: Color(0xFFE2E8F0),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.70);
|
||||
|
||||
// سطر التسليم المطلوب في النشاط.
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
final y = h * 0.26 + i * (h * 0.055);
|
||||
final choice = rows[i];
|
||||
final correct = choice == _presentRows[i].$2;
|
||||
final color = choice == null
|
||||
? const Color(0xFF1E293B)
|
||||
: (correct ? const Color(0xFF30D158) : const Color(0xFFFF375F));
|
||||
canvas.drawCircle(Offset(w * 0.17, y), 7, Paint()..color = color);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: _briefPresent(i),
|
||||
style: TextStyle(
|
||||
color: choice == null ? const Color(0xFF94A3B8) : Colors.white,
|
||||
fontSize: 10.5),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 1,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: w * 0.58);
|
||||
tp.paint(canvas, Offset(w * 0.24, y - tp.height / 2));
|
||||
}
|
||||
|
||||
// شريط تعليق أسفل الشريحة.
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.82),
|
||||
'«وأتحدّثُ بطلاقةٍ بسرعةٍ مناسبةٍ، وأعرضُهُ أمامَ صفِّي بانسيابٍ ضمنَ زمنٍ محددٍ»',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF00F5D4) : const Color(0xFF94A3B8),
|
||||
fontSize: 11,
|
||||
height: 1.5),
|
||||
maxWidth: w * 0.86);
|
||||
}
|
||||
|
||||
String _briefPresent(int i) {
|
||||
const b = {
|
||||
0: 'التعبيرُ عن المظاهرِ الحقيقيةِ للانتماءِ',
|
||||
1: 'توظيفُ مهاراتِ التواصلِ البصريِّ',
|
||||
2: 'التحدّثُ بطلاقةٍ بسرعةٍ مناسبةٍ',
|
||||
3: 'قراءةُ العرضِ حرفيًّا بلا تلقائيّةٍ',
|
||||
4: 'العرضُ أمامَ الصفِّ ضمنَ زمنٍ محددٍ',
|
||||
};
|
||||
return b[i]!;
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _presentRows.length; i++) {
|
||||
if (rows[i] != _presentRows[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_PresentationPainter old) => old.rows != rows;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — لوحة القولِ والمضمونِ.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _QuoteBoardPainter extends CustomPainter {
|
||||
final List<int?> matchings;
|
||||
|
||||
const _QuoteBoardPainter({required this.matchings});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
done
|
||||
? 'الاقتباساتُ مُسنَدةٌ إلى مضمونِها ✓'
|
||||
: 'اقرنْ كلَّ قولٍ بمضمونِهِ',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
for (var i = 0; i < _quoteRows.length; i++) {
|
||||
final y = h * 0.21 + i * (h * 0.17);
|
||||
final choice = matchings[i];
|
||||
final correct = choice == _quoteRows[i].$3;
|
||||
|
||||
// بطاقة الاقتباس.
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.10, y, w * 0.80, h * 0.11),
|
||||
const Radius.circular(9));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = choice == null
|
||||
? Colors.white12
|
||||
: (correct
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.7)
|
||||
: const Color(0xFFFF375F).withValues(alpha: 0.5))
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: _quoteRows[i].$1,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 10.5),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 2,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: w * 0.70);
|
||||
tp.paint(canvas, Offset(w * 0.16, y + h * 0.055 - tp.height / 2));
|
||||
|
||||
// شارة المضمون عند إجابته.
|
||||
if (choice != null) {
|
||||
final m = TextPainter(
|
||||
text: TextSpan(
|
||||
text: _quoteRows[i].$2[choice],
|
||||
style: TextStyle(
|
||||
color:
|
||||
correct ? const Color(0xFF00F5D4) : const Color(0xFFFF375F),
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
maxLines: 1,
|
||||
ellipsis: '…',
|
||||
)..layout(maxWidth: w * 0.40);
|
||||
m.paint(canvas, Offset(w * 0.14, y + h * 0.145 - m.height / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _quoteRows.length; i++) {
|
||||
if (matchings[i] != _quoteRows[i].$3) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_QuoteBoardPainter old) => old.matchings != matchings;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// مساعدات الرسم المشتركة.
|
||||
// ---------------------------------------------------------------------------
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 6,
|
||||
)..layout(maxWidth: maxWidth ?? double.infinity);
|
||||
tp.paint(canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
|
||||
}
|
||||
+673
@@ -0,0 +1,673 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — BUILD MY LANGUAGE (1): THE VOCATIVE STYLE (أسلوبُ النّداءِ)
|
||||
/// Lesson: الدرس الخامس — أبني لغتي (1): أسلوبُ النّداءِ
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_02/lesson_05.md (pages 50-55)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Built ONLY on extractable facts from the source pages:
|
||||
/// - مفهوم النداء (ص50-51): خطابٌ بين طرفين (المنادي المتكلّم والمنادى
|
||||
/// المستمع) لإثارة الانتباه والاستدعاء وتوجيه طلبٍ ما؛ أركانه: حرفُ النداء
|
||||
/// والمنادى.
|
||||
/// - حروف النداء (ص50-51): للقريب (يا، أَي)، وللبعيد (أيا، أَ، أَيْ)؛
|
||||
/// أكثرها استعمالاً (يا).
|
||||
/// - أنواع المنادى (ص52-53): المفردُ العلمُ مبنيٌّ على الضّمِّ في محلِّ نصبٍ
|
||||
/// (يا مريمُ)، النّكرةُ المقصودةُ مبنيّةٌ على الضّمِّ (يا نارُ، يا طفلُ)،
|
||||
/// المضافُ منصوبٌ بالفتحة (يا أعدلَ الناسِ)، الشّبيهُ بالمضافِ منصوبٌ
|
||||
/// (يا وارثًا علمًا)، والنّكرةُ غيرُ المقصودةِ منصوبةٌ (يا قاضيًا احكم
|
||||
/// بالعدلِ لأنّ الخطاب موجه إلى القضاة جميعًا).
|
||||
/// - نداءُ المعرفِ بـ(ال) (ص53): تُستعمَل (أيّها) مع المذكّر و(أيّتها) مع
|
||||
/// المؤنّث (يا أيّها الناسُ، يا أيّتها النفسُ المطمئنّةُ).
|
||||
/// - حذفُ حرفِ النداءِ (ص53، 55): يُقدَّرُ بـ(يا) إذا فُهِمَ النّداءُ
|
||||
/// بدونه؛ (للّهُمَّ) عُوِّضَ حرفُها بميمٍ مشدّدة، وحديث «السلامُ عليكم
|
||||
/// دارَ قومٍ مؤمنينَ» نصٌّ مقدَّرُ ندائِهِ (يا دارَ...).
|
||||
/// ============================================================================
|
||||
|
||||
// مكوّنات أسلوب النداء (مفهوم، أركان، حروف، حذف).
|
||||
const List<(String, bool)> _vocativeStatements = [
|
||||
(
|
||||
'أسلوبُ النّداءِ خطابٌ يُوجَّهُ إلى المنادى بغرضِ التنبيهِ والاستدعاءِ.',
|
||||
true
|
||||
),
|
||||
('من أركانِ النداءِ: حرفُ النداءِ والمنادى.', true),
|
||||
('المنادي هو المتكلِّمُ، والمنادى هو المستمِعُ.', true),
|
||||
('من حروفِ النداءِ للقريبِ: (يا) و(أَي).', true),
|
||||
('من حروفِ النداءِ للبعيدِ: (أيا) و(أَ) و(أَيْ).', true),
|
||||
('أكثرُ أحرفِ النداءِ استعمالًا حرفُ (يا).', true),
|
||||
(
|
||||
'حرفُ النداءِ يُقدَّرُ بحرفِ (يا) عندَ حذفِهِ إذا فُهِمَ النداءُ بدونِهِ.',
|
||||
true
|
||||
),
|
||||
('من أركانِ النداءِ: المفعولُ والعِدّةُ.', false),
|
||||
];
|
||||
|
||||
// أنواع المنادى بأمثلة الدرس (ص52-53).
|
||||
const List<(String, String, List<String>, int)> _typesRows = [
|
||||
(
|
||||
'يا مريمُ (عَلَمٌ مفردٌ)',
|
||||
'مفردٌ عَلَمٌ مبنيٌّ على الضّمِّ في محلِّ نصبٍ',
|
||||
[
|
||||
'مفردٌ عَلَمٌ مبنيٌّ على الضّمِّ',
|
||||
'مضافٌ منصوبٌ بالفتحةِ',
|
||||
'نكرةٌ غيرُ مقصودةٍ'
|
||||
],
|
||||
0
|
||||
),
|
||||
(
|
||||
'يا طفلُ (مقصودٌ بعينِهِ)',
|
||||
'نكرةٌ مقصودةٌ مبنيّةٌ على الضّمِّ',
|
||||
['مضافٌ منصوبٌ', 'نكرةٌ مقصودةٌ مبنيّةٌ على الضّمِّ', 'شبيهٌ بالمضافِ'],
|
||||
1
|
||||
),
|
||||
(
|
||||
'يا أعدلَ الناسِ',
|
||||
'منادى مضافٌ منصوبٌ بالفتحةِ',
|
||||
[
|
||||
'منادى مضافٌ منصوبٌ بالفتحةِ',
|
||||
'مفردٌ مبنيٌّ على الضّمِّ',
|
||||
'نكرةٌ غيرُ مقصودةٍ'
|
||||
],
|
||||
0
|
||||
),
|
||||
(
|
||||
'يا وارثًا عِلْمًا',
|
||||
'شبيهٌ بالمضافِ منصوبٌ بالفتحةِ',
|
||||
['نكرةٌ مقصودةٌ مبنيّةٌ', 'مضافٌ منصوبٌ', 'شبيهٌ بالمضافِ منصوبٌ بالفتحةِ'],
|
||||
2
|
||||
),
|
||||
(
|
||||
'يا قاضيًا، احكمْ بالعدلِ',
|
||||
'نكرةٌ غيرُ مقصودةٍ منصوبةٌ بالفتحةِ',
|
||||
[
|
||||
'نكرةٌ غيرُ مقصودةٍ منصوبةٌ بالفتحةِ',
|
||||
'مفردٌ عَلَمٌ مبنيٌّ',
|
||||
'مضافٌ منصوبٌ'
|
||||
],
|
||||
0
|
||||
),
|
||||
];
|
||||
|
||||
// توظيف: نداءُ المعرفِ بـ(ال) وحذفُ الأداة (ص53، 55).
|
||||
const List<(String, bool)> _situationalStatements = [
|
||||
(
|
||||
'لنداءِ الاسمِ المقترنِ بـ(ال) نستعملُ (أيّها) معَ المذكَّرِ، مثلَ: (يا أيّها الناسُ).',
|
||||
true
|
||||
),
|
||||
(
|
||||
'معَ المؤنَّثِ نستعملُ (أيّتها)، مثلَ: (يا أيّتُها النفسُ المطمئنّةُ).',
|
||||
true
|
||||
),
|
||||
(
|
||||
'(للّهُمَّ) أصلهُ (يا اللهُ)، حُذفَ حرفُ النداءِ وعُوِّضَ عنه بميمٍ مشدَّدةٍ.',
|
||||
true
|
||||
),
|
||||
(
|
||||
'في حديثِ (السلامُ عليكُم دارَ قومٍ مؤمنينَ) يُقدَّرُ حرفُ النداءِ المحذوفُ بـ(يا)، أصلهُ (يا دارَ قومٍ).',
|
||||
true
|
||||
),
|
||||
('حرفُ النداءِ لا يجوزُ حذفُهُ منَ أسلوبِ النداءِ في أيِّ حالٍ.', false),
|
||||
('أكثرُ أحرفِ النداءِ استعمالًا في العربيةِ حرفُ (أيّها).', false),
|
||||
];
|
||||
|
||||
class ArabicUnit2VocativeLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicUnit2VocativeLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicUnit2VocativeLabView> createState() =>
|
||||
_ArabicUnit2VocativeLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicUnit2VocativeLabViewState
|
||||
extends State<ArabicUnit2VocativeLabView> {
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — مكوّنات النداء.
|
||||
final List<bool> _vocative = List.filled(_vocativeStatements.length, false);
|
||||
bool _vocativeTouched = false;
|
||||
|
||||
// Activity 2 — أنواع المنادى.
|
||||
final List<int> _selected = List.filled(_typesRows.length, -1);
|
||||
bool _typesTouched = false;
|
||||
|
||||
// Activity 3 — التوظيف.
|
||||
final List<bool> _situational =
|
||||
List.filled(_situationalStatements.length, false);
|
||||
|
||||
bool get _vocativeDone {
|
||||
for (var i = 0; i < _vocativeStatements.length; i++) {
|
||||
if (_vocative[i] != _vocativeStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _typesDone {
|
||||
if (!_typesTouched) return false;
|
||||
for (var i = 0; i < _typesRows.length; i++) {
|
||||
if (_selected[i] != _typesRows[i].$4) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _situationalDone {
|
||||
for (var i = 0; i < _situationalStatements.length; i++) {
|
||||
if (_situational[i] != _situationalStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _vocativeDone && _vocativeTouched;
|
||||
if (_activity == 1) return _typesDone;
|
||||
return _situationalDone;
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _VocativeMegaphonePainter(checks: List.of(_vocative)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _TypesBalancePainter(
|
||||
selected: List.of(_selected), rows: _typesRows.length),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _SituationalSheetPainter(checks: List.of(_situational)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const ['مكوّناتُ النداءِ', 'أنواعُ المنادى', 'توظيفُ النداءِ'],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أُعيِّنُ (صوابًا أو خطأً) على مكوّناتِ أسلوبِ النداءِ وحروفِهِ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _vocativeStatements.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: LabToggle(
|
||||
label: (i == 7 ? '( ) ' : '( √ ) ') + _vocativeStatements[i].$1,
|
||||
value: _vocative[i],
|
||||
onChanged: (v) => setState(() {
|
||||
_vocative[i] = v;
|
||||
_vocativeTouched = true;
|
||||
if (_vocativeDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (_vocativeDone)
|
||||
const LabPill(
|
||||
'مكوّناتُ النداءِ مثبّتةٌ: خطابٌ للتنبيهِ والاستدعاءِ، أركانُهُ حرفُ النداءِ والمنادى، وأكثرُ حروفِهِ (يا) ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الصواب: التنبيهُ والاستدعاءُ؛ الأركانُ (حرفُ النداءِ + المنادى)؛ للقريبِ (يا، أَي) وللبعيدِ (أيا، أَ، أَيْ)؛ وأكثرُها استعمالًا (يا). الخطأُ: المفعولُ والعِدّةُ.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'لِكُلِّ مثالٍ من أمثلةِ أنواعِ المنادى أخترْ وصفَهُ الصحيحَ (أ، ب، ج).',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var r = 0; r < _typesRows.length; r++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_typesRows[r].$1,
|
||||
style:
|
||||
const TextStyle(color: Color(0xFF00F5D4), fontSize: 12.5),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
for (var o = 0; o < 3; o++)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
child: Material(
|
||||
color: _selected[r] == o
|
||||
? const Color(0xFF30D158)
|
||||
.withValues(alpha: 0.12)
|
||||
: Colors.white.withValues(alpha: 0.02),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () => setState(() {
|
||||
_selected[r] = o;
|
||||
_typesTouched = true;
|
||||
if (_typesDone) saqelTick();
|
||||
}),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _selected[r] == o
|
||||
? const Color(0xFF30D158)
|
||||
: Colors.white.withValues(alpha: 0.12),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
['أ', 'ب', 'ج'][o],
|
||||
style: TextStyle(
|
||||
color: _selected[r] == o
|
||||
? const Color(0xFF30D158)
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_typesRows[r].$3[o],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_typesDone)
|
||||
const LabPill(
|
||||
'أنواعُ المنادى مضبوطةٌ: المفردُ والنكرةُ المقصودةُ مبنيّانِ على الضّمِّ، والمضافُ والشبيهُ بالمضافِ والنكرةُ غيرُ المقصودةِ منصوبةٌ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'راجع: مريمُ مفردٌ عَلَمٌ مبنيٌّ؛ (يا طفلُ) نكرةٌ مقصودةٌ مبنيةٌ؛ المضافُ (أعدلَ الناسِ) منصوبٌ؛ (وارثًا) شبيهٌ بالمضافِ؛ والقضاةُ المطلوبون كلُّهُمْ نكرةٌ غيرُ مقصودةٍ.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أُعيِّنُ (صوابًا أو خطأً) على نداءِ المعرفِ بـ(ال) وحذفِ حرفِ النداءِ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _situationalStatements.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: LabToggle(
|
||||
label:
|
||||
(i >= 4 ? '( ) ' : '( √ ) ') + _situationalStatements[i].$1,
|
||||
value: _situational[i],
|
||||
onChanged: (v) => setState(() {
|
||||
_situational[i] = v;
|
||||
if (_situationalDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (_situationalDone)
|
||||
const LabPill(
|
||||
'توظيفُ النداءِ سليمٌ: المعرفُ بـ(ال) يُنادى بـ(أيّها/أيّتها)، ويُقدَّرُ حرفُ النداءِ المحذوفُ بـ(يا) ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الصواب: (أيّها) للمذكّرِ و(أيّتها) للمؤنَّثِ، (اللّهُمَّ) ميمُها عوضُ حرفِ النداءِ، وحديثُ (السلامُ عليكم دارَ... ) مقدَّرُهُ (يا دارَ). الخطأُ: ثوابتُ الحذفِ و(أيّها).',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question: 'لماذا بُنيَ المنادى (مريمُ) على الضّمِّ في محلِّ نصبٍ؟',
|
||||
options: const [
|
||||
'لأنّهُ مفردٌ عَلَمٌ ليسَ مضافًا ولا شبيهًا بالمضافِ',
|
||||
'لأنّهُ مفردٌ مضافٌ إلى كلمةٍ بعدَهُ',
|
||||
'لأنّهُ نكرةٌ غيرُ مقصودةٍ',
|
||||
'لأنّ المنادَياتِ لا تُعرَبُ أبدًا',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicUnit2VocativeLabIdentity,
|
||||
titleAr: 'مختبر أسلوبِ النداءِ',
|
||||
subtitleAr: 'أبني لغتي (1) — حروفُ النداءِ وأنواعُ المنادى',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 50-55. بُني على مفهومِ النداءِ ورکنيهِ وحروفِهِ (ص50-51)، وأنواعِ المنادى الخمسةِ بأمثلةِ الدرسِ وإعرابِها (ص52-53)، ونداءِ المعرفِ بـ(ال) بأداةِ (أيّها/أيّتها) وحذفِ حرفِ النداءِ المقدَّرِ بـ(يا) (ص53، 55) ومنها (للّهُمَّ). أنشطةُ الإعرابِ المفصَّلِ والبيتِ الذي اجتمعتْ فيه ثلاثةُ أنواعٍ (ص54) ونَصُّ أستزيد (اللهم ارحمنا) مواضعُ حُصرتْ لـOCR متداخلٍ وبحاجةٍ لمراجعةٍ أكاديميةٍ قبلَ النشر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — بوق/منارة النداء وحروفه.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _VocativeMegaphonePainter extends CustomPainter {
|
||||
final List<bool> checks;
|
||||
|
||||
const _VocativeMegaphonePainter({required this.checks});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.09),
|
||||
done ? 'المناديُ والمنادى جاهزانِ ✓' : 'نداءٌ: منَ المنادي إلى المنادى',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
// مستقبل النداء (موجات صوتية).
|
||||
final origin = Offset(w * 0.62, h * 0.34);
|
||||
canvas.drawCircle(origin, 24, Paint()..color = const Color(0xFF102035));
|
||||
canvas.drawCircle(
|
||||
origin,
|
||||
24,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4).withValues(alpha: 0.6)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2);
|
||||
_centerText(canvas, origin, 'المناديُ',
|
||||
const TextStyle(color: Colors.white, fontSize: 9.5),
|
||||
maxWidth: 40);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(center: origin, radius: 34 + i * 14),
|
||||
-0.25,
|
||||
0.5,
|
||||
false,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4).withValues(alpha: 0.35 - i * 0.08)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2);
|
||||
}
|
||||
final sink = Offset(w * 0.38, h * 0.36);
|
||||
canvas.drawCircle(sink, 22, Paint()..color = const Color(0xFF102035));
|
||||
canvas.drawCircle(
|
||||
sink,
|
||||
22,
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5);
|
||||
_centerText(canvas, sink, 'المنادى',
|
||||
const TextStyle(color: Colors.white, fontSize: 9.5),
|
||||
maxWidth: 38);
|
||||
canvas.drawLine(
|
||||
origin,
|
||||
sink,
|
||||
Paint()
|
||||
..color = const Color(0xFF0071E3).withValues(alpha: 0.6)
|
||||
..strokeWidth = 2);
|
||||
|
||||
// شرائح حروف النداء.
|
||||
const letters = ['(يا)', '(أَي)', '(أيا)', '(أَ)', '(أَيْ)'];
|
||||
for (var i = 0; i < letters.length; i++) {
|
||||
final chip = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * (0.15 + i * 0.14), h * 0.62, w * 0.12, 28),
|
||||
const Radius.circular(14));
|
||||
final lit = i + 3 < checks.length && checks[i + 3];
|
||||
canvas.drawRRect(chip, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
chip,
|
||||
Paint()
|
||||
..color = lit ? const Color(0xFF00F5D4) : Colors.white12
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(chip.left + w * 0.06, chip.center.dy),
|
||||
letters[i],
|
||||
TextStyle(
|
||||
color: lit ? const Color(0xFF00F5D4) : Colors.white60,
|
||||
fontSize: 10.5),
|
||||
maxWidth: w * 0.11);
|
||||
}
|
||||
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.88),
|
||||
checks[6]
|
||||
? 'الحذفُ مقدَّرٌ بـ(يا): (اللّهُمَّ)، (السلامُ عليكم دارَ...) · المعرفُ بـ(ال) يُنادى بأيّها/أيّتها'
|
||||
: 'للقريبِ (يا، أَي) • للبعيدِ (أيا، أَ، أَيْ) • أكثرُها استعمالًا (يا)',
|
||||
TextStyle(
|
||||
color:
|
||||
checks[6] ? const Color(0xFF30D158) : const Color(0xFF94A3B8),
|
||||
fontSize: 10),
|
||||
maxWidth: w * 0.84);
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _vocativeStatements.length; i++) {
|
||||
if (checks[i] != _vocativeStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_VocativeMegaphonePainter old) => old.checks != checks;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — ميزان أنواع المنادى.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _TypesBalancePainter extends CustomPainter {
|
||||
final List<int> selected;
|
||||
final int rows;
|
||||
|
||||
const _TypesBalancePainter({required this.selected, required this.rows});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final solved = _isSolved();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
solved
|
||||
? 'ميزانُ أنواعِ المنادى مضبوطٌ ✓'
|
||||
: 'أنواعُ المنادى على الميزانِ',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
// قبان: ذراعان يمثلان مبنيّ/منصوباً.
|
||||
final pivot = Offset(w / 2, h * 0.34);
|
||||
canvas.drawLine(Offset(w * 0.16, h * 0.34), Offset(w * 0.84, h * 0.34),
|
||||
Paint()..color = const Color(0xFF0071E3).withValues(alpha: 0.5));
|
||||
canvas.drawCircle(pivot, 7, Paint()..color = const Color(0xFF00F5D4));
|
||||
canvas.drawLine(
|
||||
pivot,
|
||||
Offset(w / 2, h * 0.92),
|
||||
Paint()
|
||||
..color = Colors.white12
|
||||
..strokeWidth = 3);
|
||||
|
||||
const left = ['يا مريمُ', 'يا طفلُ'];
|
||||
const right = ['يا أعدلَ الناسِ', 'يا وارثًا علمًا', 'يا قاضيًا'];
|
||||
for (var i = 0; i < left.length; i++) {
|
||||
final pos = Offset(w * 0.28, h * 0.50 + i * 0.13);
|
||||
final on = i < selected.length && selected[i] == _typesRows[i].$4;
|
||||
final chip = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: pos, width: w * 0.42, height: 30),
|
||||
const Radius.circular(15));
|
||||
canvas.drawRRect(chip, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
chip,
|
||||
Paint()
|
||||
..color = on ? const Color(0xFF30D158) : Colors.white12
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
pos,
|
||||
'${left[i]} — مبنيٌّ', TextStyle(
|
||||
color: on ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 10),
|
||||
maxWidth: w * 0.36);
|
||||
}
|
||||
for (var i = 0; i < right.length; i++) {
|
||||
final pos = Offset(w * 0.72, h * 0.50 + i * 0.13);
|
||||
final rowIdx = 2 + i;
|
||||
final on =
|
||||
rowIdx < selected.length && selected[rowIdx] == _typesRows[rowIdx].$4;
|
||||
final chip = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: pos, width: w * 0.42, height: 30),
|
||||
const Radius.circular(15));
|
||||
canvas.drawRRect(chip, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
chip,
|
||||
Paint()
|
||||
..color = on ? const Color(0xFF00F5D4) : Colors.white12
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
pos,
|
||||
'${right[i]} — منصوبٌ',
|
||||
TextStyle(
|
||||
color: on ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 10),
|
||||
maxWidth: w * 0.36);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSolved() {
|
||||
for (var i = 0; i < rows; i++) {
|
||||
if (i >= selected.length || selected[i] != _typesRows[i].$4) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_TypesBalancePainter old) => old.selected != selected;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — ورقة توظيف النداء.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _SituationalSheetPainter extends CustomPainter {
|
||||
final List<bool> checks;
|
||||
|
||||
const _SituationalSheetPainter({required this.checks});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
done ? 'توظيفُ النداءِ سليمٌ ✓' : 'نداءُ المعرفِ بـ(ال) وحذفُ الأداةِ',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.12, h * 0.18, w * 0.76, h * 0.62),
|
||||
const Radius.circular(10));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.1)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
|
||||
for (var i = 0; i < checks.length; i++) {
|
||||
final y = h * 0.24 + i * (h * 0.50) / checks.length;
|
||||
final on = checks[i];
|
||||
final target = _situationalStatements[i].$2;
|
||||
final wrongOn = on && !target;
|
||||
final color = on
|
||||
? (wrongOn ? const Color(0xFFFF375F) : const Color(0xFF30D158))
|
||||
: const Color(0xFF102035);
|
||||
canvas.drawCircle(Offset(w * 0.22, y), 9, Paint()..color = color);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: on ? (wrongOn ? '✕' : '✓') : '·',
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(canvas, Offset(w * 0.22 - tp.width / 2, y - tp.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _situationalStatements.length; i++) {
|
||||
if (checks[i] != _situationalStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_SituationalSheetPainter old) => old.checks != checks;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// مساعدات الرسم المشتركة.
|
||||
// ---------------------------------------------------------------------------
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 6,
|
||||
)..layout(maxWidth: maxWidth ?? double.infinity);
|
||||
tp.paint(canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
|
||||
}
|
||||
@@ -0,0 +1,661 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ARABIC — WRITING CONTENT: ANALYSING A POETIC TEXT (أكتبُ محتوى)
|
||||
/// Lesson: الدرس الرابع — أكتبُ محتوى (تحليل النص الشعري)
|
||||
/// Source: grade_10/arabic_10/semester_1/unit_02/lesson_04.md (pages 46-49)
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Built ONLY on extractable facts from the source pages:
|
||||
/// - عناصر العمل الأدبي (ص46): الأفكار، العواطف، الخيال، اللغة، موسيقا الشعر.
|
||||
/// - غاية التحليل: قراءةُ النصِّ الشعريِّ لكشف أفكاره، وبيان إيحاءات ألفاظه
|
||||
/// وتراكيبه، وكشف مواطن الضعف والقوة، وإبراز جمالياته (ص46).
|
||||
/// - معايير التحليل (ص48): ذكر الديوان، توضيح المناسبة، إظهار الأفكار
|
||||
/// الرئيسة والفرعية، بيان العاطفة والتصوير الفني، تحديد دقة الألفاظ
|
||||
/// والتوظيف والأساليب وإيحاءاتها، توظيف أدوات الربط (حروف العطف، أمّا
|
||||
/// التفصيلية، الضمائر)، ووضع الألفاظ المقتسبة من القصيدة والتواريخ بين
|
||||
/// قوسين.
|
||||
/// - إبداء الرأي في النص: الأفكار مناسبة/غير مناسبة ومترابطة/غير مترابطة،
|
||||
/// الألفاظ معبّرة/غير معبّرة، والصور الفنية خادمة/غير خادمة وإبداعية/
|
||||
/// مألوفة (ص48).
|
||||
/// - تحليل مقطع عبد الكريم الكرمي (1909-1980) «أغنيات بالدي» المنشور على
|
||||
/// Goodreads (ص47-48): صورة تعلّق الشاعر بأرضه كأنها أمّه (كنى عن الخضوع
|
||||
/// بلفظ «تزحف»، تخيّل التراب زهرًا)، وخيام اللاجئين الباكية.
|
||||
/// - نشاط (2.4) يطلب تحليل «أردن يا بلدي» لحبيب الزيودي من ديوان
|
||||
/// «غيم على العالوك» (ص49) في حدود 200-250 كلمة — يُكتب خارج المختبر.
|
||||
/// ============================================================================
|
||||
|
||||
// عِبارات عناصرِ الكتابةِ وأدواتِها.
|
||||
const List<(String, bool)> _craftStatements = [
|
||||
('من عناصرِ العملِ الأدبيِّ: الأفكارُ.', true),
|
||||
('من عناصرِ العملِ الأدبيِّ: العواطفُ.', true),
|
||||
('من عناصرِ العملِ الأدبيِّ: الخيالُ.', true),
|
||||
('من عناصرِ العملِ الأدبيِّ: اللغةُ.', true),
|
||||
('من عناصرِ العملِ الأدبيِّ: موسيقا الشعرِ.', true),
|
||||
('من عناصرِ العملِ الأدبيِّ: الورقُ والحبرُ.', false),
|
||||
('تحليلُ النصِّ يكشفُ أفكارَهُ ويُبرزُ جماليّاتِهِ.', true),
|
||||
(
|
||||
'من أدواتِ الربطِ المناسبةِ في الكتابةِ: حروفُ العطفِ و(أمّا) التفصيليّةُ والضمائرُ.',
|
||||
true
|
||||
),
|
||||
];
|
||||
|
||||
// معايير تحليل المقطع الشعري مرتبةً كما وردت في الكُتيّب.
|
||||
const List<(String, String)> _criterionSteps = [
|
||||
('أ', 'ذِكرُ الديوانِ الذي أُخذَ منه النصُّ، وتوضيحُ مناسبةِ النصِّ.'),
|
||||
(
|
||||
'ب',
|
||||
'إظهارُ الأفكارِ الرئيسةِ والفرعيّةِ، وبيانُ العاطفةِ والتصويرِ الفنّيِّ.'
|
||||
),
|
||||
('ج', 'تحديدُ دقّةِ الألفاظِ والتوظيفِ والأساليبِ اللغويّةِ وإيحاءاتِها.'),
|
||||
(
|
||||
'د',
|
||||
'توظيفُ أدواتِ الربطِ المناسبةِ، ووضعُ الألفاظِ المقتَبَسةِ والتواريخِ بينَ قوسينِ.'
|
||||
),
|
||||
];
|
||||
const List<String> _criterionCanonical = ['أ', 'ب', 'ج', 'د'];
|
||||
const List<int> _criterionDisplay = [3, 1, 0, 2]; // د، ب، أ، ج — شُفرة.
|
||||
|
||||
// تحليلُ مقطعِ الكرميِّ: لقطاتٌ من التحليلِ (ص47-48).
|
||||
const List<(String, bool)> _analysisStatements = [
|
||||
(
|
||||
'في البيتينِ الأولِ والثاني صورةٌ تمثّلُ شدّةَ تعلّقِ الشاعرِ بأرضِهِ كأنّها أُمُّهُ.',
|
||||
true
|
||||
),
|
||||
('كنّى الشاعرُ عن الخضوعِ بلفظِ (تزحفُ).', true),
|
||||
(
|
||||
'تخيّلَ الشاعرُ الترابَ زهرًا له عطرٌ، والهوى شجرةً عاشَ تحتها الآباءُ والأجدادُ.',
|
||||
true
|
||||
),
|
||||
('الشاعرُ مخلصٌ في حبِّ أهلِهِ لا يُبالي بلومِ اللائمينَ.', true),
|
||||
(
|
||||
'خيامُ اللاجئينَ باكيّةٌ ممزّقةٌ تضربُها الرياحُ، وقد حرموا نعمةَ الاستقرارِ.',
|
||||
true
|
||||
),
|
||||
('اعتمدَ الشاعرُ في تصويرِهِ على الكلامِ المجرّدِ من غيرِ أيِ صورةٍ.', false),
|
||||
];
|
||||
|
||||
class ArabicUnit2WritingLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const ArabicUnit2WritingLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ArabicUnit2WritingLabView> createState() =>
|
||||
_ArabicUnit2WritingLabViewState();
|
||||
}
|
||||
|
||||
class _ArabicUnit2WritingLabViewState extends State<ArabicUnit2WritingLabView> {
|
||||
int _activity = 0;
|
||||
|
||||
// Activity 1 — عناصر الكتابة.
|
||||
final List<bool> _craft = List.filled(_craftStatements.length, false);
|
||||
bool _craftTouched = false;
|
||||
|
||||
// Activity 2 — معايير التحليل.
|
||||
final List<String> _placed = [];
|
||||
int _wrongCount = 0;
|
||||
int _flash = -1;
|
||||
bool _solved = false;
|
||||
|
||||
// Activity 3 — تحليل مقطع الكرمي.
|
||||
final List<bool> _analysis = List.filled(_analysisStatements.length, false);
|
||||
|
||||
bool get _craftDone {
|
||||
for (var i = 0; i < _craftStatements.length; i++) {
|
||||
if (_craft[i] != _craftStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _analysisDone {
|
||||
for (var i = 0; i < _analysisStatements.length; i++) {
|
||||
if (_analysis[i] != _analysisStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _discoveryDone {
|
||||
if (_activity == 0) return _craftDone && _craftTouched;
|
||||
if (_activity == 1) return _solved;
|
||||
return _analysisDone;
|
||||
}
|
||||
|
||||
void _tapPart(String letter) {
|
||||
if (_solved || _placed.contains(letter)) return;
|
||||
setState(() {
|
||||
_placed.add(letter);
|
||||
if (_placed.length == 4) {
|
||||
var correct = true;
|
||||
for (var i = 0; i < _criterionCanonical.length; i++) {
|
||||
if (_placed[i] != _criterionCanonical[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (correct) {
|
||||
_solved = true;
|
||||
saqelTick();
|
||||
} else {
|
||||
_wrongCount++;
|
||||
_flash = _criterionCanonical.indexOf(_placed[0]);
|
||||
_placed.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _resetParts() {
|
||||
setState(() {
|
||||
_placed.clear();
|
||||
_wrongCount = 0;
|
||||
_flash = -1;
|
||||
_solved = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildCanvas() {
|
||||
switch (_activity) {
|
||||
case 0:
|
||||
return CustomPaint(
|
||||
painter: _CraftSheetPainter(checks: List.of(_craft)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
case 1:
|
||||
return CustomPaint(
|
||||
painter: _CriteriaPathPainter(
|
||||
placed: List.of(_placed),
|
||||
canonical: _criterionCanonical,
|
||||
solved: _solved,
|
||||
flash: _flash,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
default:
|
||||
return CustomPaint(
|
||||
painter: _AnalysisSheetPainter(checks: List.of(_analysis)),
|
||||
size: Size.infinite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildControls() {
|
||||
final c = <Widget>[
|
||||
LabSegments<int>(
|
||||
labels: const [
|
||||
'عناصرُ الكتابةِ',
|
||||
'معاييرُ التحليلِ',
|
||||
'تحليلُ الكرميِّ'
|
||||
],
|
||||
values: const [0, 1, 2],
|
||||
current: _activity,
|
||||
onSelected: (v) => setState(() {
|
||||
_activity = v;
|
||||
saqelTick();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
];
|
||||
|
||||
if (_activity == 0) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'أُعيِّنُ عناصرَ العملِ الأدبيِّ الصحيحةَ، وأدواتَ الربطِ المناسبةَ في كتابةِ التحليلِ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _craftStatements.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: LabToggle(
|
||||
label: (i == 5 ? '( ) ' : '( √ ) ') + _craftStatements[i].$1,
|
||||
value: _craft[i],
|
||||
onChanged: (v) => setState(() {
|
||||
_craft[i] = v;
|
||||
_craftTouched = true;
|
||||
if (_craftDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (_craftDone)
|
||||
const LabPill(
|
||||
'عناصرُ الكتابةِ مثبّتةٌ: الأفكارُ والعواطفُ والخيالُ واللغةُ وموسيقا الشعرِ، معَ أدواتِ الربطِ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الصواب: العناصرُ الخمسةُ (الأفكار، العواطف، الخيال، اللغة، موسيقا الشعر) + أدواتِ الربطِ (حروفُ العطفِ وأمّا والضمائرُ). الخطأُ: الورقُ والحبرُ.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
} else if (_activity == 1) {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'رتِّبْ معاييرَ تحليلِ المقطعِ الشعريِّ كما وردَ في كتُيِّبِ الكتابةِ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (final idx in _criterionDisplay)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: _placed.contains(_criterionSteps[idx].$1)
|
||||
? Colors.white.withValues(alpha: 0.06)
|
||||
: (_flash == idx
|
||||
? const Color(0xFFFF375F).withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.02)),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _solved ? null : () => _tapPart(_criterionSteps[idx].$1),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: _placed.contains(_criterionSteps[idx].$1)
|
||||
? const Color(0xFF00F5D4).withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: _placed.contains(_criterionSteps[idx].$1)
|
||||
? const Color(0xFF00F5D4)
|
||||
: Colors.black.withValues(alpha: 0.35),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_criterionSteps[idx].$1,
|
||||
style: TextStyle(
|
||||
color: _placed.contains(_criterionSteps[idx].$1)
|
||||
? Colors.black
|
||||
: Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_criterionSteps[idx].$2,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_wrongCount >= 2 && !_solved)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
'خطوة صغيرة: الديوانُ والمناسبةُ، ثمّ الأفكارُ والعاطفةُ والتصويرُ، ثمّ الألفاظُ والأساليبُ، ثمّ أدواتُ الربطِ والبيانُ بينَ القوسينِ.',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _resetParts,
|
||||
icon: const Icon(Icons.refresh, size: 16),
|
||||
label: const Text('إعادةُ ترتيبِ المعاييرِ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
),
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
c.addAll([
|
||||
const Text(
|
||||
'علِّقْ على لقطاتِ تحليلِ مقطعِ عبدِ الكريمِ الكرميِّ: صوابٌ أو خطأٌ.',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
for (var i = 0; i < _analysisStatements.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: LabToggle(
|
||||
label: (i == 5 ? '( ) ' : '( √ ) ') + _analysisStatements[i].$1,
|
||||
value: _analysis[i],
|
||||
onChanged: (v) => setState(() {
|
||||
_analysis[i] = v;
|
||||
if (_analysisDone) saqelTick();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (_analysisDone)
|
||||
const LabPill(
|
||||
'تحليلُ الكرميِّ مطابقٌ: الأرضُ كالأمِّ، والخضوعُ بالزحفِ، والترابُ زهرٌ، فلا غرابةَ في الحبِّ وبكاءِ الخيامِ ✓',
|
||||
color: Color(0xFF30D158))
|
||||
else
|
||||
const LabPill(
|
||||
'الصواب: الأرضُ أُمٌّ يزحفُ إليها، والخضوعُ (تزحفُ)، والترابُ زهرٌ، والوفاءُ لا يُبالي باللومِ، والخيامُ بكّاءةٌ. الخطأُ: تصويرٌ بلا صورٍ.',
|
||||
color: Color(0xFFFF9F0A)),
|
||||
]);
|
||||
}
|
||||
|
||||
if (_discoveryDone) {
|
||||
c.addAll([
|
||||
const SizedBox(height: 12),
|
||||
LabCheckpointButton(
|
||||
question:
|
||||
'لماذا توضعُ الألفاظُ المقتَبَسةُ منَ القصيدةِ والتواريخُ بينَ قوسينِ عندَ التحليلِ؟',
|
||||
options: const [
|
||||
'لتوثيقِ المصدرِ المشروعِ والتعبيرِ عنِ الاستشهادِ الصادقِ بالدليلِ',
|
||||
'للتجميلِ الفنّيِّ فقط دونَ قيمةٍ توثيقيّةٍ',
|
||||
'لإخفاءِ اسمِ الشاعرِ والديوانِ عنِ القارئِ',
|
||||
'قاعدةٌ اختياريّةٌ لا صلةَ لها بالكتابةِ الصحيحةِ',
|
||||
],
|
||||
correctIdx: 0,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
identity: kArabicUnit2WritingLabIdentity,
|
||||
titleAr: 'مختبر كتابةِ تحليلِ النصِّ الشعريِّ',
|
||||
subtitleAr: 'أكتبُ محتوى — عناصرُ العملِ الأدبيِّ ومعاييرُ التحليلِ',
|
||||
canvas: _buildCanvas(),
|
||||
controls: _buildControls(),
|
||||
footerNote:
|
||||
'مصدر: صفحات 46-49. بُني على عناصرِ العملِ الأدبيِّ (ص46)، ومعاييرِ التحليلِ السبعِ (ص48)، وضوابطِ إبداءِ الرأي (ص48)، ولقطاتِ تحليلِ مقطعِ الكرميِّ (ص47-48). نشاطُ (2.4) تحليلُ «أردن يا بلدي» لحبيب الزيودي من ديوان «غيم على العالوك» بحدود 200-250 كلمة مهمةُ كتابةٍ حرةٍ تُنجزُ في المسودةِ وليس داخلَ المختبر. بعضُ نصوصِ OCR (مناسبة الكرمي وغيره) متداخلٌ ويحتاجُ المراجعةَ الأكاديميةَ قبلَ النشر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 1 — ورقة عناصر الكتابة.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _CraftSheetPainter extends CustomPainter {
|
||||
final List<bool> checks;
|
||||
|
||||
const _CraftSheetPainter({required this.checks});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
done
|
||||
? 'عناصرُ العملِ الأدبيِّ مثبّتةٌ ✓'
|
||||
: 'عناصرُ العملِ الأدبيِّ (ورقةُ الكاتبِ)',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
// خريطة العناصر: دائرة مركزية + ذراعان.
|
||||
const elements = [
|
||||
'الأفكارُ',
|
||||
'العواطفُ',
|
||||
'الخيالُ',
|
||||
'اللغةُ',
|
||||
'موسيقا الشعرِ'
|
||||
];
|
||||
final center = Offset(w / 2, h * 0.42);
|
||||
canvas.drawCircle(center, 26, Paint()..color = const Color(0xFF102035));
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
26,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4).withValues(alpha: 0.6)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2);
|
||||
_centerText(
|
||||
canvas,
|
||||
center,
|
||||
'العملُ الأدبيُّ',
|
||||
const TextStyle(
|
||||
color: Colors.white, fontSize: 10, fontWeight: FontWeight.w800),
|
||||
maxWidth: 44);
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
final angle = -1.35 + i * 0.54;
|
||||
final pos = Offset(
|
||||
w / 2 + 90 * math.cos(angle) * (w / 900).clamp(0.7, 1.4),
|
||||
h * 0.42 + 70 * math.sin(angle) * (h / 900).clamp(0.7, 1.4));
|
||||
final chip = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: pos, width: 92, height: 30),
|
||||
const Radius.circular(15));
|
||||
final on = checks[i];
|
||||
canvas.drawRRect(chip, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
chip,
|
||||
Paint()
|
||||
..color = on ? const Color(0xFF30D158) : Colors.white12
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
_centerText(
|
||||
canvas,
|
||||
pos,
|
||||
elements[i],
|
||||
TextStyle(
|
||||
color: on ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: 80);
|
||||
}
|
||||
|
||||
// شريط أدوات الربط أسفل.
|
||||
final bar = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.12, h * 0.80, w * 0.76, h * 0.09),
|
||||
const Radius.circular(8));
|
||||
canvas.drawRRect(bar, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
bar,
|
||||
Paint()
|
||||
..color = Colors.white12
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.845),
|
||||
'أدواتُ الربطِ: حروفُ العطفِ • (أمّا) التفصيليّةُ • الضمائرُ',
|
||||
TextStyle(
|
||||
color:
|
||||
checks[7] ? const Color(0xFF00F5D4) : const Color(0xFF94A3B8),
|
||||
fontSize: 10.5),
|
||||
maxWidth: w * 0.66);
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _craftStatements.length; i++) {
|
||||
if (checks[i] != _craftStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CraftSheetPainter old) => old.checks != checks;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 2 — مسار معايير التحليل.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _CriteriaPathPainter extends CustomPainter {
|
||||
final List<String> placed;
|
||||
final List<String> canonical;
|
||||
final bool solved;
|
||||
final int flash;
|
||||
|
||||
const _CriteriaPathPainter({
|
||||
required this.placed,
|
||||
required this.canonical,
|
||||
required this.solved,
|
||||
required this.flash,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
solved
|
||||
? 'المعاييرُ مرتبةً في مسارِ الكتابةِ ✓'
|
||||
: 'مسارُ معاييرِ تحليلِ المقطعِ',
|
||||
TextStyle(
|
||||
color: solved ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
final path = Path()
|
||||
..moveTo(w * 0.16, h * 0.24)
|
||||
..lineTo(w * 0.84, h * 0.24)
|
||||
..lineTo(w * 0.84, h * 0.40)
|
||||
..lineTo(w * 0.16, h * 0.40)
|
||||
..lineTo(w * 0.16, h * 0.56)
|
||||
..lineTo(w * 0.84, h * 0.56)
|
||||
..lineTo(w * 0.84, h * 0.72)
|
||||
..lineTo(w * 0.50, h * 0.72);
|
||||
canvas.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = const Color(0xFF0071E3).withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3);
|
||||
|
||||
final nodes = [
|
||||
Offset(w * 0.50, h * 0.24),
|
||||
Offset(w * 0.16, h * 0.40),
|
||||
Offset(w * 0.84, h * 0.56),
|
||||
Offset(w * 0.50, h * 0.72),
|
||||
];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
final letter = canonical[i];
|
||||
final on = placed.contains(letter);
|
||||
canvas.drawCircle(
|
||||
nodes[i],
|
||||
18,
|
||||
Paint()
|
||||
..color = on ? const Color(0xFF00F5D4) : const Color(0xFF102035));
|
||||
canvas.drawCircle(
|
||||
nodes[i],
|
||||
18,
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5);
|
||||
final lt = TextPainter(
|
||||
text: TextSpan(
|
||||
text: letter,
|
||||
style: TextStyle(
|
||||
color: on ? Colors.black : Colors.white70,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w900),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
lt.paint(canvas,
|
||||
Offset(nodes[i].dx - lt.width / 2, nodes[i].dy - lt.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CriteriaPathPainter old) =>
|
||||
old.placed != placed || old.solved != solved || old.flash != flash;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Canvas 3 — دفتر تحليل الكرمي.
|
||||
// ---------------------------------------------------------------------------
|
||||
class _AnalysisSheetPainter extends CustomPainter {
|
||||
final List<bool> checks;
|
||||
|
||||
const _AnalysisSheetPainter({required this.checks});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final done = _isDone();
|
||||
_centerText(
|
||||
canvas,
|
||||
Offset(w / 2, h * 0.10),
|
||||
done
|
||||
? 'قراءتُكَ في مقطعِ الكرميِّ سديدةٌ ✓'
|
||||
: 'لقطاتٌ من تحليلِ الكرميِّ',
|
||||
TextStyle(
|
||||
color: done ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxWidth: w * 0.86);
|
||||
|
||||
final card = RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(w * 0.12, h * 0.18, w * 0.76, h * 0.62),
|
||||
const Radius.circular(10));
|
||||
canvas.drawRRect(card, Paint()..color = const Color(0xFF0B1728));
|
||||
canvas.drawRRect(
|
||||
card,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1);
|
||||
|
||||
for (var i = 0; i < checks.length; i++) {
|
||||
final y = h * 0.24 + i * (h * 0.50) / checks.length;
|
||||
final on = checks[i];
|
||||
final target = _analysisStatements[i].$2;
|
||||
final wrongOn = on && !target;
|
||||
final color = on
|
||||
? (wrongOn ? const Color(0xFFFF375F) : const Color(0xFF30D158))
|
||||
: const Color(0xFF102035);
|
||||
canvas.drawCircle(Offset(w * 0.22, y), 9, Paint()..color = color);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: on ? (wrongOn ? '✕' : '✓') : '·',
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(canvas, Offset(w * 0.22 - tp.width / 2, y - tp.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDone() {
|
||||
for (var i = 0; i < _analysisStatements.length; i++) {
|
||||
if (checks[i] != _analysisStatements[i].$2) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_AnalysisSheetPainter old) => old.checks != checks;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// مساعدات الرسم المشتركة.
|
||||
// ---------------------------------------------------------------------------
|
||||
void _centerText(Canvas canvas, Offset center, String text, TextStyle style,
|
||||
{double? maxWidth}) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 6,
|
||||
)..layout(maxWidth: maxWidth ?? double.infinity);
|
||||
tp.paint(canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
|
||||
}
|
||||
@@ -0,0 +1,943 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// BIOLOGY — GRADE 10 VIRTUAL LABS
|
||||
/// 1. BiologyDarwinFinchesLabView — مناقير داروين + توزيع البذور + دورة 20s
|
||||
/// 2. BiologyBacteriophageLabView — آكل البكتيريا + تحلل/اندماج stepper
|
||||
/// 3. BiologyDichotomousKeyLabView — مفتاح ثنائي لكائنات أردنية
|
||||
/// 4. BiologyMicroscopeLabView — مجهر ضوئي 40x-1000x + تركيز + إضاءة
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) DARWIN FINCHES
|
||||
// ---------------------------------------------------------------------------
|
||||
class BiologyDarwinFinchesLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const BiologyDarwinFinchesLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<BiologyDarwinFinchesLabView> createState() =>
|
||||
_BiologyDarwinFinchesLabViewState();
|
||||
}
|
||||
|
||||
class _BiologyDarwinFinchesLabViewState
|
||||
extends State<BiologyDarwinFinchesLabView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
int beak = 1; // 0 thin, 1 crusher, 2 spoon
|
||||
double smallSeed = 0.6; // fraction of small seeds
|
||||
double progress = 0; // 0..1 feeding cycle
|
||||
bool running = false;
|
||||
late final AnimationController ctl;
|
||||
final math.Random rnd = math.Random(42);
|
||||
List<double> history = [];
|
||||
|
||||
static const beaks = [
|
||||
{'name': 'منقار رفيع (ملقط)', 'small': 0.95, 'large': 0.25},
|
||||
{'name': 'منقار ساحق (كسارة)', 'small': 0.55, 'large': 0.95},
|
||||
{'name': 'منقار ملعقي (مغرفة)', 'small': 0.70, 'large': 0.55},
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ctl =
|
||||
AnimationController(vsync: this, duration: const Duration(seconds: 20))
|
||||
..addListener(() {
|
||||
if (!running) return;
|
||||
setState(() => progress = ctl.value);
|
||||
if (ctl.value >= 1.0) {
|
||||
running = false;
|
||||
history.add(survival);
|
||||
if (history.length > 8) history.removeAt(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
double get survival {
|
||||
final b = beaks[beak];
|
||||
return (smallSeed * (b['small'] as double) +
|
||||
(1 - smallSeed) * (b['large'] as double));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ctl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final eaten = (progress * 40 * survival).toInt();
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'تطور الكائنات — عصافير داروين',
|
||||
subtitleAr: 'اختر المنقار ووزع البذور ثم شغّل دورة تغذية 20 ثانية',
|
||||
identity: kBiologyDarwinFinchesToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'في بيئة تسودها البذور الكبيرة الصلبة، أي منقار يمنح ميزة انتخابية؟',
|
||||
checkpointOptions: const [
|
||||
'المنقار الساحق',
|
||||
'المنقار الرفيع',
|
||||
'لا فرق بينهما',
|
||||
'المنقار الملعقي دائماً'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('البقاء: ${(survival * 100).toStringAsFixed(0)}%',
|
||||
color: const Color(0xFF30D158)),
|
||||
LabPill('بذور مأكولة: $eaten', color: const Color(0xFFFF9F0A)),
|
||||
LabPill('${(progress * 20).toStringAsFixed(0)} / 20 s'),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _FinchPainter(
|
||||
beak: beak, smallSeed: smallSeed, progress: progress, seed: 42),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
const Text('شكل المنقار:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
LabSegments<int>(
|
||||
labels: const ['رفيع', 'ساحق', 'ملعقي'],
|
||||
values: const [0, 1, 2],
|
||||
current: beak,
|
||||
onSelected: (v) => setState(() => beak = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabSlider(
|
||||
label: 'نسبة البذور الصغيرة في البيئة',
|
||||
value: smallSeed,
|
||||
min: 0,
|
||||
max: 1,
|
||||
display: '${(smallSeed * 100).toInt()}% صغيرة',
|
||||
accent: const Color(0xFF30D158),
|
||||
onChanged: (v) => setState(() => smallSeed = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabTransportRow(
|
||||
playing: running,
|
||||
playLabel: 'دورة تغذية 20s',
|
||||
timeLabel: '${(progress * 20).toStringAsFixed(0)}s',
|
||||
onToggle: () => setState(() {
|
||||
if (!running) {
|
||||
if (progress >= 1) {
|
||||
progress = 0;
|
||||
ctl.forward(from: 0);
|
||||
} else {
|
||||
ctl.forward(from: progress);
|
||||
}
|
||||
running = true;
|
||||
} else {
|
||||
running = false;
|
||||
ctl.stop();
|
||||
}
|
||||
}),
|
||||
onReset: () => setState(() {
|
||||
running = false;
|
||||
progress = 0;
|
||||
ctl.reset();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: beaks[beak]['name'] as String,
|
||||
body:
|
||||
'كفاءة على البذور الصغيرة: ${((beaks[beak]['small'] as double) * 100).toInt()}%\nكفاءة على البذور الكبيرة: ${((beaks[beak]['large'] as double) * 100).toInt()}%\nمعدل البقاء المتوقع في هذه البيئة: ${(survival * 100).toStringAsFixed(0)}%',
|
||||
),
|
||||
if (history.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
const Text('سجل الدورات (بقاء %):',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 11.5)),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
height: 46,
|
||||
child: CustomPaint(
|
||||
painter: _MiniBars(values: history), child: Container()),
|
||||
),
|
||||
],
|
||||
],
|
||||
footerNote:
|
||||
'نموذج تعليمي مبسط للانتخاب الطبيعي؛ ليس بيانات حقلية حقيقية.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FinchPainter extends CustomPainter {
|
||||
final int beak;
|
||||
final double smallSeed, progress;
|
||||
final int seed;
|
||||
_FinchPainter(
|
||||
{required this.beak,
|
||||
required this.smallSeed,
|
||||
required this.progress,
|
||||
required this.seed});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
// ground
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF0B1526));
|
||||
c.drawRect(Rect.fromLTWH(0, s.height * 0.62, s.width, s.height * 0.38),
|
||||
Paint()..color = const Color(0xFF1A2B1A));
|
||||
// seeds
|
||||
final rnd = math.Random(seed);
|
||||
for (int i = 0; i < 46; i++) {
|
||||
final small = rnd.nextDouble() < smallSeed;
|
||||
final x = rnd.nextDouble() * s.width;
|
||||
final y = s.height * 0.64 + rnd.nextDouble() * s.height * 0.30;
|
||||
final eaten = (i / 46) < progress * 0.9;
|
||||
c.drawCircle(
|
||||
Offset(x, y),
|
||||
small ? 3.2 : 5.4,
|
||||
Paint()
|
||||
..color = eaten
|
||||
? Colors.white.withValues(alpha: 0.10)
|
||||
: (small ? const Color(0xFFFFD166) : const Color(0xFFB45309)));
|
||||
}
|
||||
// finch body
|
||||
final fx = s.width * 0.5, fy = s.height * 0.34;
|
||||
c.drawCircle(Offset(fx, fy), 26, Paint()..color = const Color(0xFF334155));
|
||||
c.drawCircle(
|
||||
Offset(fx + 8, fy - 16), 15, Paint()..color = const Color(0xFF475569));
|
||||
c.drawCircle(Offset(fx + 12, fy - 18), 3, Paint()..color = Colors.white);
|
||||
// beak shapes
|
||||
final beakPaint = Paint()..color = const Color(0xFFFF9F0A);
|
||||
if (beak == 0) {
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(fx + 22, fy - 20)
|
||||
..lineTo(fx + 52, fy - 12)
|
||||
..lineTo(fx + 22, fy - 6)
|
||||
..close(),
|
||||
beakPaint);
|
||||
} else if (beak == 1) {
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(fx + 20, fy - 26)
|
||||
..quadraticBezierTo(fx + 46, fy - 22, fx + 40, fy - 6)
|
||||
..lineTo(fx + 20, fy - 6)
|
||||
..close(),
|
||||
beakPaint);
|
||||
} else {
|
||||
c.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(fx + 36, fy - 12), width: 30, height: 16),
|
||||
beakPaint);
|
||||
}
|
||||
// pecking animation line
|
||||
if (progress > 0) {
|
||||
final px = fx + 30 + math.sin(progress * 40) * 8;
|
||||
c.drawLine(
|
||||
Offset(fx + 20, fy),
|
||||
Offset(px, s.height * 0.66),
|
||||
Paint()
|
||||
..color = Colors.white30
|
||||
..strokeWidth = 1.4);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _FinchPainter o) =>
|
||||
o.beak != beak || o.smallSeed != smallSeed || o.progress != progress;
|
||||
}
|
||||
|
||||
class _MiniBars extends CustomPainter {
|
||||
final List<double> values;
|
||||
_MiniBars({required this.values});
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
if (values.isEmpty) return;
|
||||
final bw = s.width / values.length;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
final h = values[i] * s.height;
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(i * bw + 3, s.height - h, (i + 1) * bw - 3, s.height,
|
||||
const Radius.circular(4)),
|
||||
Paint()..color = const Color(0xFF30D158));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _MiniBars o) => o.values != values;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) BACTERIOPHAGE + LYTIC/LYSOGENIC STEPPER
|
||||
// ---------------------------------------------------------------------------
|
||||
class BiologyBacteriophageLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const BiologyBacteriophageLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<BiologyBacteriophageLabView> createState() =>
|
||||
_BiologyBacteriophageLabViewState();
|
||||
}
|
||||
|
||||
class _BiologyBacteriophageLabViewState
|
||||
extends State<BiologyBacteriophageLabView> {
|
||||
int step = 0;
|
||||
bool lysogenic = false;
|
||||
|
||||
static const lyticSteps = [
|
||||
{'t': '1 الالتصاق', 'd': 'ألياف الذيل تتعرف على مستقبلات جدار البكتيريا.'},
|
||||
{'t': '2 الحقن', 'd': 'ينكمش الغمد ويُحقن DNA الفيروس داخل الخلية.'},
|
||||
{
|
||||
't': '3 الاستنساخ',
|
||||
'd': 'آلية الخلية تصنع نسخاً من DNA وبروتينات الفيروس.'
|
||||
},
|
||||
{'t': '4 التجميع', 'd': 'تتجمع القفيصات والذيول لتكوين فيروسات جديدة.'},
|
||||
{'t': '5 الانفجار', 'd': 'إنزيم الليزوزيم يمزق الجدار وتخرج ~100 فيروس.'},
|
||||
];
|
||||
static const lysoSteps = [
|
||||
{
|
||||
't': '1 الاندماج',
|
||||
'd': 'يدمج DNA الفيروس في كروموسوم البكتيريا (بروفيروس).'
|
||||
},
|
||||
{
|
||||
't': '2 الانقسام الهادئ',
|
||||
'd': 'تتكاثر البكتيريا حاملة البروفيروس دون أعراض.'
|
||||
},
|
||||
{'t': '3 الاستحثاث', 'd': 'إجهاد (أشعة/كيماويات) يفعّل البروفيروس.'},
|
||||
{'t': '4 التحول للتحلل', 'd': 'يدخل المسار التحللي: استنساخ ثم انفجار.'},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final steps = lysogenic ? lysoSteps : lyticSteps;
|
||||
final cur = steps[step.clamp(0, steps.length - 1)];
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'نمذجة الفيروسات — آكل البكتيريا',
|
||||
subtitleAr: 'نموذج أيزومتري + مسارا التحلل والاندماج خطوة بخطوة',
|
||||
identity: kBiologyBacteriophageToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'الفرق الجوهري بين الدورة الاندماجية والتحللية هو …',
|
||||
checkpointOptions: const [
|
||||
'اندماج DNA دون انفجار فوري',
|
||||
'عدم وجود DNA',
|
||||
'غياب القفيصة',
|
||||
'عدم إصابة البكتيريا'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(lysogenic ? 'اندماجية Lysogenic' : 'تحللية Lytic',
|
||||
color: const Color(0xFF8B5CF6)),
|
||||
LabPill('خطوة ${step + 1}/${steps.length}'),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _PhagePainter(step: step, lysogenic: lysogenic),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<bool>(
|
||||
labels: const ['تحللية', 'اندماجية'],
|
||||
values: const [false, true],
|
||||
current: lysogenic,
|
||||
onSelected: (v) => setState(() {
|
||||
lysogenic = v;
|
||||
step = 0;
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: step > 0 ? () => setState(() => step--) : null,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24)),
|
||||
child: const Text('السابق'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: step < steps.length - 1
|
||||
? () => setState(() => step++)
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.saqelCyan,
|
||||
foregroundColor: Colors.black),
|
||||
child: const Text('التالي',
|
||||
style: TextStyle(fontWeight: FontWeight.w800)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: cur['t']!,
|
||||
body: cur['d']!,
|
||||
accent: const Color(0xFF8B5CF6),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text('أجزاء الفيروس: قفيصة عشرونية + غمد + ألياف ذيل + DNA.',
|
||||
style:
|
||||
TextStyle(color: Color(0xFF94A3B8), fontSize: 11, height: 1.6)),
|
||||
],
|
||||
footerNote: 'رسم تخطيطي تعليمي؛ الأحجام النسبية غير مرسومة بمقياس حقيقي.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PhagePainter extends CustomPainter {
|
||||
final int step;
|
||||
final bool lysogenic;
|
||||
_PhagePainter({required this.step, required this.lysogenic});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF0A0F1E));
|
||||
final cx = s.width * 0.5, cy = s.height * 0.38;
|
||||
// capsid (icosahedron hint: hexagon)
|
||||
final cap = Path();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
final a = i * math.pi / 3 - math.pi / 2;
|
||||
final p = Offset(cx + math.cos(a) * 34, cy + math.sin(a) * 34);
|
||||
if (i == 0) {
|
||||
cap.moveTo(p.dx, p.dy);
|
||||
} else {
|
||||
cap.lineTo(p.dx, p.dy);
|
||||
}
|
||||
}
|
||||
cap.close();
|
||||
c.drawPath(
|
||||
cap, Paint()..color = const Color(0xFF00F5D4).withValues(alpha: 0.25));
|
||||
c.drawPath(
|
||||
cap,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2);
|
||||
// DNA coil inside
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(cx - 16, cy - 12)
|
||||
..quadraticBezierTo(cx, cy - 4, cx + 16, cy - 12)
|
||||
..quadraticBezierTo(cx, cy + 4, cx - 16, cy + 12),
|
||||
Paint()
|
||||
..color = const Color(0xFFFFD166)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2,
|
||||
);
|
||||
// sheath + tail fibers
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(
|
||||
cx - 9, cy + 34, cx + 9, cy + 78, const Radius.circular(5)),
|
||||
Paint()..color = const Color(0xFF64748B));
|
||||
for (int i = -2; i <= 2; i++) {
|
||||
c.drawLine(
|
||||
Offset(cx, cy + 78),
|
||||
Offset(cx + i * 18.0, cy + 108),
|
||||
Paint()
|
||||
..color = const Color(0xFF94A3B8)
|
||||
..strokeWidth = 2);
|
||||
}
|
||||
// bacterium below
|
||||
final by = s.height * 0.82;
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(
|
||||
30, by - 34, s.width - 30, by + 20, const Radius.circular(22)),
|
||||
Paint()..color = const Color(0xFF30D158).withValues(alpha: 0.22));
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(
|
||||
30, by - 34, s.width - 30, by + 20, const Radius.circular(22)),
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.6);
|
||||
// stage illustration: burst particles at last step
|
||||
if (!lysogenic && step == 4) {
|
||||
final rnd = math.Random(9);
|
||||
for (int i = 0; i < 14; i++) {
|
||||
c.drawCircle(
|
||||
Offset(40 + rnd.nextDouble() * (s.width - 80),
|
||||
by - 60 - rnd.nextDouble() * 60),
|
||||
4,
|
||||
Paint()..color = const Color(0xFF00F5D4));
|
||||
}
|
||||
}
|
||||
if (lysogenic && step >= 1) {
|
||||
final tp = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: 'بروفيروس مندمج في كروموسوم البكتيريا',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFBF5AF2),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout(maxWidth: s.width - 60);
|
||||
tp.paint(c, Offset(30, by - 66));
|
||||
}
|
||||
// step dots
|
||||
final n = lysogenic ? 4 : 5;
|
||||
for (int i = 0; i < n; i++) {
|
||||
final dx = 30.0 + i * ((s.width - 60) / (n - 1));
|
||||
c.drawCircle(
|
||||
Offset(dx, s.height - 18),
|
||||
6,
|
||||
Paint()
|
||||
..color = i <= step ? const Color(0xFF8B5CF6) : Colors.white24);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _PhagePainter o) =>
|
||||
o.step != step || o.lysogenic != lysogenic;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3) DICHOTOMOUS KEY — Jordanian wildlife
|
||||
// ---------------------------------------------------------------------------
|
||||
class BiologyDichotomousKeyLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const BiologyDichotomousKeyLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<BiologyDichotomousKeyLabView> createState() =>
|
||||
_BiologyDichotomousKeyLabViewState();
|
||||
}
|
||||
|
||||
class _KeyNode {
|
||||
final String q;
|
||||
final _KeyNode? yes;
|
||||
final _KeyNode? no;
|
||||
final String? result;
|
||||
const _KeyNode({this.q = '', this.yes, this.no, this.result});
|
||||
bool get isLeaf => result != null;
|
||||
}
|
||||
|
||||
class _BiologyDichotomousKeyLabViewState
|
||||
extends State<BiologyDichotomousKeyLabView> {
|
||||
late final _KeyNode root = _KeyNode(
|
||||
q: 'هل الكائن فقاري (له عمود فقري)؟',
|
||||
yes: _KeyNode(
|
||||
q: 'هل يرضع صغاره (ثديي)؟',
|
||||
yes: _KeyNode(
|
||||
q: 'هل يعيش في صحراء الأردن الشرقية بأذنين طويلتين؟',
|
||||
yes: const _KeyNode(
|
||||
result: 'الأرنب البري الأردني — ثديي عشبي ليلي من البادية.'),
|
||||
no: const _KeyNode(
|
||||
result: 'الوبر الصخري — ثديي يعيش في مرتفعات البتراء وعجلون.'),
|
||||
),
|
||||
no: _KeyNode(
|
||||
q: 'هل له ريش ومنقار؟',
|
||||
yes: const _KeyNode(result: 'العقاب الذهبي — طائر جارح في محمية ضانا.'),
|
||||
no: const _KeyNode(
|
||||
result:
|
||||
'الأفعى الفلسطينية — زاحف سام؛ لا تقترب، بلّغ الجهات المختصة.'),
|
||||
),
|
||||
),
|
||||
no: _KeyNode(
|
||||
q: 'هل جسمه مقسم مع هيكل خارجي ومفاصل؟',
|
||||
yes: _KeyNode(
|
||||
q: 'هل له 8 أرجل؟',
|
||||
yes: const _KeyNode(result: 'العقرب الأصفر — مفصلي ليلي في وادي رم.'),
|
||||
no: const _KeyNode(
|
||||
result: 'الجراد الصحراوي — حشرة قد تصل بأسراب موسمية.'),
|
||||
),
|
||||
no: const _KeyNode(
|
||||
result:
|
||||
'البراميسيوم — طليعي مهدب يعيش في المياه العذبة (يُرى بالمجهر).'),
|
||||
),
|
||||
);
|
||||
|
||||
final List<bool> path = [];
|
||||
|
||||
_KeyNode get current {
|
||||
_KeyNode n = root;
|
||||
for (final b in path) {
|
||||
n = b ? n.yes! : n.no!;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cur = current;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'أسس التصنيف — المفتاح الثنائي',
|
||||
subtitleAr: 'أجب بنعم/لا خطوة بخطوة لتحديد الكائن الأردني المجهول',
|
||||
identity: kBiologyDichotomousKeyLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'أول سؤال صحيح في مفتاح ثنائي يجب أن …',
|
||||
checkpointOptions: const [
|
||||
'يقسم الكائنات لمجموعتين واضحتين',
|
||||
'يسأل عن اللون فقط',
|
||||
'يذكر الاسم مباشرة',
|
||||
'يجمع كل الصفات دفعة واحدة'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('الخطوة ${path.length + 1}'),
|
||||
if (cur.isLeaf) const LabPill('تم التحديد ✓', color: Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _KeyTreePainter(depth: path.length, done: cur.isLeaf),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
if (!cur.isLeaf) ...[
|
||||
Text(cur.q,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 14,
|
||||
height: 1.6)),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => setState(() => path.add(true)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF30D158),
|
||||
foregroundColor: Colors.black),
|
||||
child: const Text('نعم',
|
||||
style: TextStyle(fontWeight: FontWeight.w900)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => setState(() => path.add(false)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFFF375F),
|
||||
foregroundColor: Colors.white),
|
||||
child: const Text('لا',
|
||||
style: TextStyle(fontWeight: FontWeight.w900)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
] else ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF30D158).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF30D158).withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Text(cur.result!,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13.5,
|
||||
height: 1.7,
|
||||
fontWeight: FontWeight.w700)),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.refresh, size: 15),
|
||||
label: const Text('كائن مجهول جديد'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24)),
|
||||
onPressed: () => setState(path.clear),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text('مسارك: ${path.map((b) => b ? 'نعم' : 'لا').join(' ← ')}',
|
||||
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 11.5)),
|
||||
],
|
||||
footerNote:
|
||||
'أمثلة أردنية مبسطة للتدريب الصفي؛ التصنيف المرجعي النهائي من الكتاب.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeyTreePainter extends CustomPainter {
|
||||
final int depth;
|
||||
final bool done;
|
||||
_KeyTreePainter({required this.depth, required this.done});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF0A1424));
|
||||
final levels = 5;
|
||||
for (int i = 0; i < levels; i++) {
|
||||
final y = 30.0 + i * ((s.height - 60) / (levels - 1));
|
||||
final active = i <= depth;
|
||||
c.drawCircle(
|
||||
Offset(s.width / 2, y),
|
||||
active ? 12 : 8,
|
||||
Paint()
|
||||
..color = active
|
||||
? (done && i == depth
|
||||
? const Color(0xFF30D158)
|
||||
: const Color(0xFF00F5D4))
|
||||
: Colors.white12);
|
||||
if (i < levels - 1) {
|
||||
c.drawLine(
|
||||
Offset(s.width / 2, y + 12),
|
||||
Offset(s.width / 2, y + (s.height - 60) / (levels - 1) - 8),
|
||||
Paint()
|
||||
..color = i < depth ? const Color(0xFF00F5D4) : Colors.white12
|
||||
..strokeWidth = 2);
|
||||
}
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: 'قرار ${i + 1}',
|
||||
style: TextStyle(
|
||||
color: active ? Colors.white : Colors.white38, fontSize: 10)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(c, Offset(s.width / 2 + 20, y - 8));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _KeyTreePainter o) =>
|
||||
o.depth != depth || o.done != done;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4) COMPOUND MICROSCOPE
|
||||
// ---------------------------------------------------------------------------
|
||||
class BiologyMicroscopeLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const BiologyMicroscopeLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<BiologyMicroscopeLabView> createState() =>
|
||||
_BiologyMicroscopeLabViewState();
|
||||
}
|
||||
|
||||
class _BiologyMicroscopeLabViewState extends State<BiologyMicroscopeLabView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
int objective = 1; // 0:40x 1:100x 2:400x 3:1000x
|
||||
double focus = 0.5;
|
||||
double iris = 0.7;
|
||||
int specimen = 0; // 0 paramecium, 1 amoeba, 2 onion
|
||||
late final AnimationController ctl;
|
||||
double swim = 0;
|
||||
|
||||
static const mags = [40, 100, 400, 1000];
|
||||
static const specs = [
|
||||
'براميسيوم (أهداب)',
|
||||
'أميبا (أقدام كاذبة)',
|
||||
'بشرة بصل (خلايا نباتية)'
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ctl = AnimationController(vsync: this, duration: const Duration(seconds: 3))
|
||||
..addListener(() => setState(() => swim = ctl.value * math.pi * 2));
|
||||
ctl.repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ctl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final blur = ((focus - 0.55).abs() * 8).clamp(0.0, 4.0);
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'المجهر المركب — البكتيريا والطلائعيات والفطريات',
|
||||
subtitleAr: 'بدّل العدسة واضبط التركيز وفتحة الحجاب لترى الحركة الحية',
|
||||
identity: kBiologyMicroscopeToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'التكبير الكلي = تكبير العينية × الشيئية. عينية 10x وشيئية 40x تعطي …',
|
||||
checkpointOptions: const ['400x', '50x', '40x', '1000x'],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('${mags[objective]}x', color: const Color(0xFFFFD166)),
|
||||
LabPill(specs[specimen]),
|
||||
LabPill(blur < 1 ? 'صورة حادة ✓' : 'اضبط التركيز',
|
||||
color:
|
||||
blur < 1 ? const Color(0xFF30D158) : const Color(0xFFFF9F0A)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _MicroscopePainter(
|
||||
specimen: specimen,
|
||||
mag: mags[objective],
|
||||
blur: blur,
|
||||
iris: iris,
|
||||
swim: swim,
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
const Text('العدسة الشيئية:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
LabSegments<int>(
|
||||
labels: const ['4x', '10x', '40x', '100x'],
|
||||
values: const [0, 1, 2, 3],
|
||||
current: objective,
|
||||
onSelected: (v) => setState(() => objective = v),
|
||||
),
|
||||
Text('التكبير الكلي: ${mags[objective]}x (عينية 10x)',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFFD166),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700)),
|
||||
const SizedBox(height: 8),
|
||||
const Text('العينة:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
LabSegments<int>(
|
||||
labels: const ['براميسيوم', 'أميبا', 'بصل'],
|
||||
values: const [0, 1, 2],
|
||||
current: specimen,
|
||||
onSelected: (v) => setState(() => specimen = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabSlider(
|
||||
label: 'مقبض التركيز الدقيق',
|
||||
value: focus,
|
||||
min: 0,
|
||||
max: 1,
|
||||
display: focus.toStringAsFixed(2),
|
||||
onChanged: (v) => setState(() => focus = v)),
|
||||
LabSlider(
|
||||
label: 'فتحة الحجاب (الإضاءة)',
|
||||
value: iris,
|
||||
min: 0.15,
|
||||
max: 1,
|
||||
display: '${(iris * 100).toInt()}%',
|
||||
accent: const Color(0xFFFFD166),
|
||||
onChanged: (v) => setState(() => iris = v)),
|
||||
],
|
||||
footerNote:
|
||||
'التكبير 100x (زيتي) للعرض التعليمي فقط؛ العينة الزيتية الحقيقية تحتاج تحضيراً مخبرياً.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MicroscopePainter extends CustomPainter {
|
||||
final int specimen, mag;
|
||||
final double blur, iris, swim;
|
||||
_MicroscopePainter(
|
||||
{required this.specimen,
|
||||
required this.mag,
|
||||
required this.blur,
|
||||
required this.iris,
|
||||
required this.swim});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
// field
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF06121F));
|
||||
final center = Offset(s.width / 2, s.height / 2);
|
||||
final fieldR = math.min(s.width, s.height) * 0.44;
|
||||
// illumination
|
||||
c.drawCircle(center, fieldR + 8, Paint()..color = Colors.black);
|
||||
c.drawCircle(
|
||||
center,
|
||||
fieldR,
|
||||
Paint()
|
||||
..color = Color.lerp(
|
||||
const Color(0xFF0B1B2E), const Color(0xFFD9F2E6), iris * 0.55)!);
|
||||
// vignette ring (eyepiece)
|
||||
c.drawCircle(
|
||||
center,
|
||||
fieldR,
|
||||
Paint()
|
||||
..color = Colors.black
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 10);
|
||||
c.save();
|
||||
c.clipPath(
|
||||
Path()..addOval(Rect.fromCircle(center: center, radius: fieldR)));
|
||||
final scale = (mag / 100).clamp(0.4, 10.0);
|
||||
if (specimen == 0) {
|
||||
// paramecium with cilia
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final px = center.dx +
|
||||
(i - 1) * 44 * scale.clamp(0.5, 1.4) +
|
||||
math.sin(swim + i) * 10;
|
||||
final py = center.dy + (i % 2) * 30 - 15 + math.cos(swim * 1.3 + i) * 8;
|
||||
_paramecium(c, Offset(px, py), scale, swim + i);
|
||||
}
|
||||
} else if (specimen == 1) {
|
||||
_amoeba(c, center, swim, scale);
|
||||
} else {
|
||||
// onion cells grid
|
||||
final cell = 34.0 * scale.clamp(0.5, 2.0);
|
||||
for (double x = center.dx - fieldR; x < center.dx + fieldR; x += cell) {
|
||||
for (double y = center.dy - fieldR;
|
||||
y < center.dy + fieldR;
|
||||
y += cell * 0.62) {
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(x, y, x + cell - 3, y + cell * 0.62 - 3,
|
||||
const Radius.circular(8)),
|
||||
Paint()
|
||||
..color = const Color(0xFFB989D6).withValues(alpha: 0.35)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.6);
|
||||
c.drawCircle(Offset(x + cell / 2, y + cell * 0.31), 3,
|
||||
Paint()..color = const Color(0xFF6D28D9));
|
||||
}
|
||||
}
|
||||
}
|
||||
// defocus overlay
|
||||
if (blur > 0.4) {
|
||||
c.drawCircle(
|
||||
center,
|
||||
fieldR,
|
||||
Paint()
|
||||
..color =
|
||||
Colors.white.withValues(alpha: (blur * 0.12).clamp(0, 0.4))
|
||||
..maskFilter = MaskFilter.blur(BlurStyle.normal, blur * 4));
|
||||
}
|
||||
c.restore();
|
||||
}
|
||||
|
||||
void _paramecium(Canvas c, Offset p, double scale, double ph) {
|
||||
final r = 20.0 * scale.clamp(0.5, 1.6);
|
||||
c.drawOval(Rect.fromCenter(center: p, width: r * 2.4, height: r * 1.4),
|
||||
Paint()..color = const Color(0xFF30D158).withValues(alpha: 0.75));
|
||||
// cilia strokes
|
||||
for (int k = 0; k < 16; k++) {
|
||||
final a = k / 16 * math.pi * 2;
|
||||
final base = p + Offset(math.cos(a) * r * 1.15, math.sin(a) * r * 0.68);
|
||||
final tip = base +
|
||||
Offset(math.cos(a), math.sin(a)) * (6 + math.sin(ph * 3 + k) * 3);
|
||||
c.drawLine(
|
||||
base,
|
||||
tip,
|
||||
Paint()
|
||||
..color = Colors.white70
|
||||
..strokeWidth = 1.2);
|
||||
}
|
||||
c.drawCircle(p + const Offset(-4, -2), 3.4,
|
||||
Paint()..color = const Color(0xFF064E3B));
|
||||
}
|
||||
|
||||
void _amoeba(Canvas c, Offset p, double ph, double scale) {
|
||||
final r = 30.0 * scale.clamp(0.5, 1.5);
|
||||
final path = Path();
|
||||
for (int k = 0; k <= 40; k++) {
|
||||
final a = k / 40 * math.pi * 2;
|
||||
final wob =
|
||||
1 + 0.22 * math.sin(a * 3 + ph) + 0.12 * math.sin(a * 5 - ph * 1.4);
|
||||
final pt = p + Offset(math.cos(a) * r * wob, math.sin(a) * r * 0.8 * wob);
|
||||
if (k == 0) {
|
||||
path.moveTo(pt.dx, pt.dy);
|
||||
} else {
|
||||
path.lineTo(pt.dx, pt.dy);
|
||||
}
|
||||
}
|
||||
path.close();
|
||||
c.drawPath(
|
||||
path, Paint()..color = const Color(0xFFFF9F0A).withValues(alpha: 0.70));
|
||||
c.drawCircle(p, 6, Paint()..color = const Color(0xFF7C2D12));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _MicroscopePainter o) =>
|
||||
o.swim != swim ||
|
||||
o.specimen != specimen ||
|
||||
o.mag != mag ||
|
||||
o.blur != blur ||
|
||||
o.iris != iris;
|
||||
}
|
||||
@@ -0,0 +1,955 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// CHEMISTRY — GRADE 10 VIRTUAL LABS
|
||||
/// 1. ChemistryBohrAtomLabView — نموذج بور n=1..6 + امتصاص/انبعاث + بالمر
|
||||
/// 2. ChemistryElectronConfigLabView — التوزيع الإلكتروني + قاعدة هوند
|
||||
/// 3. ChemistryFlameTestLabView — اختبار اللهب + مطياف الانبعاث
|
||||
/// 4. ChemistryLewisBondingLabView — لويس + VSEPR
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) BOHR ATOM
|
||||
// ---------------------------------------------------------------------------
|
||||
class ChemistryBohrAtomLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const ChemistryBohrAtomLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ChemistryBohrAtomLabView> createState() =>
|
||||
_ChemistryBohrAtomLabViewState();
|
||||
}
|
||||
|
||||
class _ChemistryBohrAtomLabViewState extends State<ChemistryBohrAtomLabView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
int nElectron = 2; // current level of the electron
|
||||
int nTarget = 2;
|
||||
late final AnimationController ctl;
|
||||
double glow = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ctl = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 900))
|
||||
..addListener(() => setState(() => glow = math.sin(ctl.value * math.pi)));
|
||||
}
|
||||
|
||||
double energy(int n) => -13.6 / (n * n); // eV for hydrogen
|
||||
|
||||
void _jump(int to) {
|
||||
if (to == nElectron) return;
|
||||
setState(() => nTarget = to);
|
||||
ctl.forward(from: 0).then((_) {
|
||||
if (mounted) setState(() => nElectron = to);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ctl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final e1 = energy(nElectron), e2 = energy(nTarget);
|
||||
final dE = (e2 - e1).abs();
|
||||
final lambda = dE > 0.01 ? 1240 / dE : 0; // nm
|
||||
final isEmission = nTarget < nElectron;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'بنية الذرة — نموذج بور والطيف',
|
||||
subtitleAr: 'انقل الإلكترون بين n=1..6 وشاهد الفوتون والخط الطيفي',
|
||||
identity: kChemistryBohrAtomLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'انتقال إلكترون الهيدروجين من n=3 إلى n=2 يصدر خط بالمر الأول. لونه التقريبي؟',
|
||||
checkpointOptions: const [
|
||||
'أحمر 656nm',
|
||||
'أخضر 550nm',
|
||||
'بنفسجي 400nm',
|
||||
'أشعة سينية'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('n = $nElectron → $nTarget'),
|
||||
LabPill('ΔE = ${dE.toStringAsFixed(2)} eV',
|
||||
color: const Color(0xFFFF9F0A)),
|
||||
if (lambda > 0)
|
||||
LabPill('λ ≈ ${lambda.toStringAsFixed(0)} nm',
|
||||
color: const Color(0xFFFF375F)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _BohrPainter(
|
||||
nElectron: nElectron,
|
||||
nTarget: nTarget,
|
||||
progress: ctl.value,
|
||||
emission: isEmission),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
const Text('مستوى الإلكترون الحالي (n)',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
LabSegments<int>(
|
||||
labels: const ['1', '2', '3', '4', '5', '6'],
|
||||
values: const [1, 2, 3, 4, 5, 6],
|
||||
current: nTarget,
|
||||
onSelected: _jump,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabFormulaCard(
|
||||
title: isEmission ? 'انبعاث فوتون ▼' : 'امتصاص فوتون ▲',
|
||||
body:
|
||||
'Eₙ = −13.6/n² eV\nE($nElectron) = ${e1.toStringAsFixed(2)} eV\nE($nTarget) = ${e2.toStringAsFixed(2)} eV\nΔE = ${dE.toStringAsFixed(2)} eV → λ = hc/ΔE ≈ ${lambda.toStringAsFixed(0)} nm',
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text('سلسلة بالمر (العودة إلى n=2 — مرئي):',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
_balmerStrip(),
|
||||
],
|
||||
footerNote: 'قيم الهيدروجين الحقيقية (13.6 eV)؛ الألوان تقريبية للتعليم.',
|
||||
);
|
||||
}
|
||||
|
||||
Widget _balmerStrip() {
|
||||
final lines = [
|
||||
{'nm': '656', 'label': '3→2 أحمر', 'c': const Color(0xFFFF375F)},
|
||||
{'nm': '486', 'label': '4→2 سماوي', 'c': const Color(0xFF00F5D4)},
|
||||
{'nm': '434', 'label': '5→2 أزرق', 'c': const Color(0xFF60A5FA)},
|
||||
{'nm': '410', 'label': '6→2 بنفسجي', 'c': const Color(0xFF8B5CF6)},
|
||||
];
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.45),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.10)),
|
||||
),
|
||||
child: Column(
|
||||
children: lines.map((l) {
|
||||
final c = l['c'] as Color;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: c,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
boxShadow: [BoxShadow(color: c, blurRadius: 10)])),
|
||||
const SizedBox(width: 8),
|
||||
Text('${l['label']} • ${l['nm']} nm',
|
||||
style:
|
||||
const TextStyle(color: Colors.white70, fontSize: 11.5)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BohrPainter extends CustomPainter {
|
||||
final int nElectron, nTarget;
|
||||
final double progress;
|
||||
final bool emission;
|
||||
_BohrPainter(
|
||||
{required this.nElectron,
|
||||
required this.nTarget,
|
||||
required this.progress,
|
||||
required this.emission});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
// starfield
|
||||
final rnd = math.Random(7);
|
||||
for (int i = 0; i < 60; i++) {
|
||||
c.drawCircle(
|
||||
Offset(rnd.nextDouble() * s.width, rnd.nextDouble() * s.height),
|
||||
rnd.nextDouble() * 1.4 + 0.4,
|
||||
Paint()..color = Colors.white.withValues(alpha: 0.25));
|
||||
}
|
||||
final cx = s.width * 0.42, cy = s.height * 0.52;
|
||||
// orbits n=1..6
|
||||
for (int n = 1; n <= 6; n++) {
|
||||
final r = 22.0 + n * 19.0;
|
||||
final active = n == nElectron || n == nTarget;
|
||||
c.drawCircle(
|
||||
Offset(cx, cy),
|
||||
r,
|
||||
Paint()
|
||||
..color = active
|
||||
? const Color(0xFF00F5D4).withValues(alpha: 0.8)
|
||||
: Colors.white.withValues(alpha: 0.14)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = active ? 1.8 : 1);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: 'n=$n',
|
||||
style: TextStyle(
|
||||
color: active ? const Color(0xFF00F5D4) : Colors.white38,
|
||||
fontSize: 9)),
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
tp.paint(c, Offset(cx + r * 0.72, cy - r * 0.72));
|
||||
}
|
||||
// nucleus
|
||||
c.drawCircle(
|
||||
Offset(cx, cy),
|
||||
13,
|
||||
Paint()
|
||||
..shader = const RadialGradient(colors: [
|
||||
Color(0xFFFF9F0A),
|
||||
Color(0xFFB45309)
|
||||
]).createShader(Rect.fromCircle(center: Offset(cx, cy), radius: 13)));
|
||||
final nt = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: '+',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900)),
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
nt.paint(c, Offset(cx - 4, cy - 10));
|
||||
// electron interpolated
|
||||
final r1 = 22.0 + nElectron * 19.0, r2 = 22.0 + nTarget * 19.0;
|
||||
final r = r1 + (r2 - r1) * progress.clamp(0, 1);
|
||||
final ang = progress * math.pi * 2 + 0.6;
|
||||
final ePos = Offset(cx + r * math.cos(ang), cy + r * math.sin(ang) * 0.92);
|
||||
c.drawCircle(
|
||||
ePos,
|
||||
7,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4)
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6));
|
||||
c.drawCircle(ePos, 5, Paint()..color = Colors.white);
|
||||
// photon arrow
|
||||
if (progress > 0.05 && progress < 0.95) {
|
||||
final px = s.width * 0.78;
|
||||
final dir = emission ? 1.0 : -1.0;
|
||||
final py = cy - dir * progress * 70;
|
||||
c.drawCircle(
|
||||
Offset(px, py),
|
||||
6,
|
||||
Paint()
|
||||
..color = const Color(0xFFFFD166)
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5));
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: emission ? 'انبعاث ▼ hν' : 'امتصاص ▲ hν',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFFD166),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl)
|
||||
..layout();
|
||||
tp.paint(c, Offset(px - 66, py - 28));
|
||||
// spectrum line preview right
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(s.width - 34, 20, s.width - 14, s.height - 20,
|
||||
const Radius.circular(8)),
|
||||
Paint()..color = Colors.black);
|
||||
c.drawLine(
|
||||
Offset(s.width - 34, py.clamp(24, s.height - 24)),
|
||||
Offset(s.width - 14, py.clamp(24, s.height - 24)),
|
||||
Paint()
|
||||
..color = const Color(0xFFFF375F)
|
||||
..strokeWidth = 3);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _BohrPainter o) =>
|
||||
o.progress != progress ||
|
||||
o.nElectron != nElectron ||
|
||||
o.nTarget != nTarget;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) ELECTRON CONFIGURATION + HUND
|
||||
// ---------------------------------------------------------------------------
|
||||
class ChemistryElectronConfigLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const ChemistryElectronConfigLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ChemistryElectronConfigLabView> createState() =>
|
||||
_ChemistryElectronConfigLabViewState();
|
||||
}
|
||||
|
||||
class _ChemistryElectronConfigLabViewState
|
||||
extends State<ChemistryElectronConfigLabView> {
|
||||
int z = 8; // oxygen default
|
||||
static const orbitals = ['1s', '2s', '2p', '3s', '3p', '4s', '3d', '4p'];
|
||||
static const caps = [2, 2, 6, 2, 6, 2, 10, 6];
|
||||
|
||||
Map<String, int> config(int atomic) {
|
||||
int left = atomic;
|
||||
final m = <String, int>{};
|
||||
for (int i = 0; i < orbitals.length && left > 0; i++) {
|
||||
final take = math.min(left, caps[i]);
|
||||
m[orbitals[i]] = take;
|
||||
left -= take;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Hund boxes for p subshell: returns list of 3 boxes each 0..2 with spin order up-first.
|
||||
List<int> hundP(int electronsInP) {
|
||||
final boxes = [0, 0, 0];
|
||||
int left = electronsInP;
|
||||
for (int i = 0; i < 3 && left > 0; i++) {
|
||||
boxes[i] = 1;
|
||||
left--;
|
||||
}
|
||||
for (int i = 0; i < 3 && left > 0; i++) {
|
||||
boxes[i] = 2;
|
||||
left--;
|
||||
}
|
||||
return boxes;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cfg = config(z);
|
||||
final pCount = cfg['2p'] ?? 0;
|
||||
final boxes = hundP(pCount > 6 ? 6 : pCount);
|
||||
final noble = z <= 2
|
||||
? 'He'
|
||||
: z <= 10
|
||||
? 'He'
|
||||
: z <= 18
|
||||
? 'Ne'
|
||||
: 'Ar';
|
||||
final valence =
|
||||
(cfg['2s'] ?? 0) + pCount + (cfg['3s'] ?? 0) + (cfg['3p'] ?? 0);
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'التوزيع الإلكتروني وقاعدة هوند',
|
||||
subtitleAr: 'سلّم أوفباو + صناديق الغزل ↑↓ للمدار p',
|
||||
identity: kChemistryElectronConfigLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'حسب قاعدة هوند، التوزيع الصحيح لـ p³ هو …',
|
||||
checkpointOptions: const [
|
||||
'↑ ↑ ↑ (صناديق منفردة)',
|
||||
'↑↓ ↑ —',
|
||||
'↑↓ ↑↓ ↑',
|
||||
'↑ ↑ — مع ازدواج مبكر'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('Z = $z'),
|
||||
LabPill('التكافؤ ≈ $valence', color: const Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _AufbauPainter(cfg: cfg, hundBoxes: boxes),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'العدد الذري Z',
|
||||
value: z.toDouble(),
|
||||
min: 1,
|
||||
max: 30,
|
||||
display: 'Z = $z',
|
||||
onChanged: (v) => setState(() => z = v.toInt())),
|
||||
const SizedBox(height: 6),
|
||||
LabFormulaCard(
|
||||
title: 'التوزيع حسب مبدأ أوفباو',
|
||||
body:
|
||||
'${cfg.entries.map((e) => '${e.key}${_sup(e.value)}').join(' ')}\nالاختصار بالغاز النبيل: [$noble] + بقية المدارات\nإلكترونات التكافؤ (الغلاف الأخير) تحدد السلوك الكيميائي.',
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text('صناديق هوند للمدار 2p (↑ قبل الازدواج ↓):',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: boxes.map((b) {
|
||||
String arrows = b == 0
|
||||
? '·'
|
||||
: b == 1
|
||||
? '↑'
|
||||
: '↑↓';
|
||||
Color col = b == 0
|
||||
? Colors.white24
|
||||
: b == 1
|
||||
? const Color(0xFF00F5D4)
|
||||
: const Color(0xFFFF9F0A);
|
||||
return Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border:
|
||||
Border.all(color: col.withValues(alpha: 0.5), width: 1.5),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(arrows,
|
||||
style: TextStyle(
|
||||
color: col, fontSize: 20, fontWeight: FontWeight.w900)),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'حتى Z=30 (الخارصين)؛ الترتيب 4s قبل 3d حسب طاقة أوفباو للصف العاشر.',
|
||||
);
|
||||
}
|
||||
|
||||
String _sup(int n) {
|
||||
const m = {
|
||||
'0': '⁰',
|
||||
'1': '¹',
|
||||
'2': '²',
|
||||
'3': '³',
|
||||
'4': '⁴',
|
||||
'5': '⁵',
|
||||
'6': '⁶',
|
||||
'7': '⁷',
|
||||
'8': '⁸',
|
||||
'9': '⁹'
|
||||
};
|
||||
return n.toString().split('').map((d) => m[d] ?? d).join();
|
||||
}
|
||||
}
|
||||
|
||||
class _AufbauPainter extends CustomPainter {
|
||||
final Map<String, int> cfg;
|
||||
final List<int> hundBoxes;
|
||||
_AufbauPainter({required this.cfg, required this.hundBoxes});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
const order = ['1s', '2s', '2p', '3s', '3p', '4s', '3d', '4p'];
|
||||
final stepH = s.height / (order.length + 1);
|
||||
for (int i = 0; i < order.length; i++) {
|
||||
final y = stepH * (i + 1);
|
||||
final has = (cfg[order[i]] ?? 0) > 0;
|
||||
// stair step
|
||||
final w = s.width * (0.86 - i * 0.04);
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(s.width * 0.07, y - 13, s.width * 0.07 + w, y + 13,
|
||||
const Radius.circular(8)),
|
||||
Paint()
|
||||
..color = has
|
||||
? const Color(0xFF00F5D4).withValues(alpha: 0.18)
|
||||
: Colors.white.withValues(alpha: 0.05),
|
||||
);
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(s.width * 0.07, y - 13, s.width * 0.07 + w, y + 13,
|
||||
const Radius.circular(8)),
|
||||
Paint()
|
||||
..color = has ? const Color(0xFF00F5D4) : Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2,
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${order[i]} • ${cfg[order[i]] ?? 0}e',
|
||||
style: TextStyle(
|
||||
color: has ? Colors.white : Colors.white38,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
tp.paint(c, Offset(s.width * 0.07 + 10, y - 9));
|
||||
// electron dots
|
||||
final n = cfg[order[i]] ?? 0;
|
||||
for (int e = 0; e < n && e < 10; e++) {
|
||||
c.drawCircle(Offset(s.width * 0.07 + w - 14 - e * 13, y), 4.5,
|
||||
Paint()..color = const Color(0xFFFFD166));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _AufbauPainter o) => o.cfg != cfg;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3) FLAME TEST
|
||||
// ---------------------------------------------------------------------------
|
||||
class ChemistryFlameTestLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const ChemistryFlameTestLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ChemistryFlameTestLabView> createState() =>
|
||||
_ChemistryFlameTestLabViewState();
|
||||
}
|
||||
|
||||
class _ChemistryFlameTestLabViewState extends State<ChemistryFlameTestLabView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
int salt = 1; // 0 Li,1 Na,2 K,3 Cu,4 Ca
|
||||
double gas = 0.7;
|
||||
late final AnimationController ctl;
|
||||
double flick = 0;
|
||||
|
||||
static const salts = [
|
||||
{
|
||||
'name': 'ليثيوم Li⁺',
|
||||
'flame': Color(0xFFFF375F),
|
||||
'nm': 670,
|
||||
'line': 'أحمر قرمزي 670nm'
|
||||
},
|
||||
{
|
||||
'name': 'صوديوم Na⁺',
|
||||
'flame': Color(0xFFFFD60A),
|
||||
'nm': 589,
|
||||
'line': 'أصفر شديد 589nm'
|
||||
},
|
||||
{
|
||||
'name': 'بوتاسيوم K⁺',
|
||||
'flame': Color(0xFFBF5AF2),
|
||||
'nm': 766,
|
||||
'line': 'بنفسجي ليلكي 766nm'
|
||||
},
|
||||
{
|
||||
'name': 'نحاس Cu²⁺',
|
||||
'flame': Color(0xFF30D158),
|
||||
'nm': 515,
|
||||
'line': 'أخضر-أزرق 515nm'
|
||||
},
|
||||
{
|
||||
'name': 'كالسيوم Ca²⁺',
|
||||
'flame': Color(0xFFFF9F0A),
|
||||
'nm': 622,
|
||||
'line': 'برتقالي 622nm'
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ctl = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 700))
|
||||
..addListener(() => setState(() => flick = ctl.value));
|
||||
ctl.repeat(reverse: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ctl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = salts[salt];
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'اختبار اللهب والمطياف',
|
||||
subtitleAr: 'عصا البلاتين في لهب بنزن — لون مميز لكل فلز',
|
||||
identity: kChemistryFlameTestToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'لهب أصفر ذهبي شديد يدل غالباً على …',
|
||||
checkpointOptions: const [
|
||||
'الصوديوم Na⁺',
|
||||
'النحاس Cu²⁺',
|
||||
'البوتاسيوم K⁺',
|
||||
'الليثيوم Li⁺'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(s['name'] as String, color: s['flame'] as Color),
|
||||
LabPill(s['line'] as String, color: const Color(0xFFFFD166)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _FlamePainter(
|
||||
flame: s['flame'] as Color,
|
||||
gas: gas,
|
||||
flick: flick,
|
||||
nm: s['nm'] as int),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
const Text('ملح الفلز على العصا:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
LabSegments<int>(
|
||||
labels: const ['Li', 'Na', 'K', 'Cu', 'Ca'],
|
||||
values: const [0, 1, 2, 3, 4],
|
||||
current: salt,
|
||||
onSelected: (v) => setState(() => salt = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabSlider(
|
||||
label: 'تدفق الغاز (حرارة اللهب)',
|
||||
value: gas,
|
||||
min: 0.2,
|
||||
max: 1.0,
|
||||
display: '${(gas * 100).toInt()}%',
|
||||
accent: const Color(0xFFFF9F0A),
|
||||
onChanged: (v) => setState(() => gas = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabFormulaCard(
|
||||
title: 'التفسير: إثارة ثم انبعاث',
|
||||
body:
|
||||
'حرارة اللهب تثير إلكترونات ${s['name']} ثم تعود لمستواها مصدرة فوتوناً بطول ${s['line']}.\nكل عنصر بصمة طيفية مميزة — أساس التحليل الطيفي.',
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'ألوان تقريبية للشاشة؛ التجربة الحقيقية تتطلب نظارات وإشراف مختبر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FlamePainter extends CustomPainter {
|
||||
final Color flame;
|
||||
final double gas, flick;
|
||||
final int nm;
|
||||
_FlamePainter(
|
||||
{required this.flame,
|
||||
required this.gas,
|
||||
required this.flick,
|
||||
required this.nm});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
final bx = s.width * 0.38, by = s.height * 0.86;
|
||||
// burner
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(
|
||||
bx - 16, by - 70, bx + 16, by, const Radius.circular(6)),
|
||||
Paint()..color = const Color(0xFF334155));
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(bx - 26, by - 8, bx + 26, by, const Radius.circular(6)),
|
||||
Paint()..color = const Color(0xFF1E293B));
|
||||
final h = (60 + gas * 110) * (1 + flick * 0.08);
|
||||
// outer flame
|
||||
final flamePath = Path()
|
||||
..moveTo(bx - 20 * gas, by - 70)
|
||||
..quadraticBezierTo(bx - 26 * gas, by - 70 - h * 0.6, bx, by - 70 - h)
|
||||
..quadraticBezierTo(
|
||||
bx + 26 * gas, by - 70 - h * 0.6, bx + 20 * gas, by - 70)
|
||||
..close();
|
||||
c.drawPath(
|
||||
flamePath,
|
||||
Paint()
|
||||
..color = flame.withValues(alpha: 0.85)
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8));
|
||||
// inner cone
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(bx - 9, by - 70)
|
||||
..lineTo(bx, by - 70 - h * 0.35)
|
||||
..lineTo(bx + 9, by - 70)
|
||||
..close(),
|
||||
Paint()..color = const Color(0xFFDBEAFE).withValues(alpha: 0.9),
|
||||
);
|
||||
// platinum wire + salt bead
|
||||
c.drawLine(
|
||||
Offset(s.width * 0.78, s.height * 0.2),
|
||||
Offset(bx + 8, by - 70 - h * 0.45),
|
||||
Paint()
|
||||
..color = Colors.white70
|
||||
..strokeWidth = 2);
|
||||
c.drawCircle(
|
||||
Offset(bx + 8, by - 70 - h * 0.45),
|
||||
7,
|
||||
Paint()
|
||||
..color = flame
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4));
|
||||
// spectrometer strip (right)
|
||||
final rx = s.width * 0.66, rw = s.width * 0.30;
|
||||
c.drawRRect(RRect.fromLTRBR(rx, 18, rx + rw, 86, const Radius.circular(10)),
|
||||
Paint()..color = Colors.black);
|
||||
final frac = ((nm - 400) / 370).clamp(0.0, 1.0);
|
||||
final lx = rx + 8 + frac * (rw - 16);
|
||||
c.drawLine(
|
||||
Offset(lx, 22),
|
||||
Offset(lx, 82),
|
||||
Paint()
|
||||
..color = flame
|
||||
..strokeWidth = 5
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 2));
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '$nm nm مطياف الانبعاث',
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 10)),
|
||||
textDirection: TextDirection.rtl)
|
||||
..layout();
|
||||
tp.paint(c, Offset(rx + 6, 88));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _FlamePainter o) =>
|
||||
o.flick != flick || o.flame != flame || o.gas != gas;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4) LEWIS + VSEPR
|
||||
// ---------------------------------------------------------------------------
|
||||
class ChemistryLewisBondingLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const ChemistryLewisBondingLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<ChemistryLewisBondingLabView> createState() =>
|
||||
_ChemistryLewisBondingLabViewState();
|
||||
}
|
||||
|
||||
class _ChemistryLewisBondingLabViewState
|
||||
extends State<ChemistryLewisBondingLabView> {
|
||||
int mol = 0; // 0 H2O, 1 CO2, 2 NH3, 3 CH4, 4 O2
|
||||
bool showLonePairs = true;
|
||||
|
||||
static const mols = [
|
||||
{'name': 'ماء H₂O', 'shape': 'منحنٍ Bent • 104.5°', 'pairs': 2, 'bonds': 2},
|
||||
{
|
||||
'name': 'ثاني أكسيد الكربون CO₂',
|
||||
'shape': 'خطي Linear • 180°',
|
||||
'pairs': 0,
|
||||
'bonds': 2
|
||||
},
|
||||
{
|
||||
'name': 'أمونيا NH₃',
|
||||
'shape': 'هرمي Trigonal pyramidal • 107°',
|
||||
'pairs': 1,
|
||||
'bonds': 3
|
||||
},
|
||||
{
|
||||
'name': 'ميثان CH₄',
|
||||
'shape': 'رباعي Tetrahedral • 109.5°',
|
||||
'pairs': 0,
|
||||
'bonds': 4
|
||||
},
|
||||
{
|
||||
'name': 'أكسجين O₂',
|
||||
'shape': 'رابطة مزدوجة • 121 pm',
|
||||
'pairs': 2,
|
||||
'bonds': 1
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final m = mols[mol];
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'الروابط وتراكيب لويس — VSEPR',
|
||||
subtitleAr: 'نقاط التكافؤ والأزواج الحرة تحدد الشكل الفراغي',
|
||||
identity: kChemistryLewisBondingLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'لماذا زاوية H₂O (104.5°) أصغر من زاوية CH₄ (109.5°)؟',
|
||||
checkpointOptions: const [
|
||||
'تنافر الأزواج الحرة أقوى',
|
||||
'الأكسجين أصغر',
|
||||
'الرابطة أطول',
|
||||
'لا يوجد فرق حقيقي'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(m['name'] as String),
|
||||
LabPill(m['shape'] as String, color: const Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _LewisPainter(molIndex: mol, showLonePairs: showLonePairs),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<int>(
|
||||
labels: const ['H₂O', 'CO₂', 'NH₃', 'CH₄', 'O₂'],
|
||||
values: const [0, 1, 2, 3, 4],
|
||||
current: mol,
|
||||
onSelected: (v) => setState(() => mol = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabToggle(
|
||||
label: 'إظهار الأزواج الحرة',
|
||||
hint: 'نقطتان = زوج غير رابط يدفع الروابط',
|
||||
value: showLonePairs,
|
||||
onChanged: (v) => setState(() => showLonePairs = v)),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: 'الشكل حسب VSEPR: ${m['shape']}',
|
||||
body: _explain(mol),
|
||||
),
|
||||
],
|
||||
footerNote: 'أطوال الروابط والزوايا قيم تعليمية مبسطة من الكتاب المدرسي.',
|
||||
);
|
||||
}
|
||||
|
||||
String _explain(int i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
return 'الأكسجين: 6 تكافؤ — رابطتان + زوجان حران. التنافر: حر−حر > حر−رابط > رابط−رابط فيضغط الزاوية إلى 104.5°.';
|
||||
case 1:
|
||||
return 'الكربون مركزي برابطتين مزدوجتين ولا أزواج حرة عليه → تنافر متماثل → خط مستقيم 180°.';
|
||||
case 2:
|
||||
return 'النيتروجين: 3 روابط + زوج حر واحد يدفع الهرم قليلاً إلى 107° (أقل من 109.5°).';
|
||||
case 3:
|
||||
return 'الكربون: 4 روابط متماثلة ولا أزواج حرة → رباعي وجوه منتظم 109.5°.';
|
||||
default:
|
||||
return 'الأكسجين المزدوج O=O: كل ذرة تحمل زوجين حرين؛ رابطة مزدوجة أقصر وأقوى من المفردة.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _LewisPainter extends CustomPainter {
|
||||
final int molIndex;
|
||||
final bool showLonePairs;
|
||||
_LewisPainter({required this.molIndex, required this.showLonePairs});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
final o = Offset(s.width / 2, s.height * 0.52);
|
||||
final dot = Paint()..color = const Color(0xFFFFD166);
|
||||
void lonePair(Offset at, double ang) {
|
||||
if (!showLonePairs) return;
|
||||
for (int k = -1; k <= 1; k += 2) {
|
||||
final a = ang + k * 0.28;
|
||||
c.drawCircle(at + Offset(math.cos(a), math.sin(a)) * 26, 3.6, dot);
|
||||
}
|
||||
}
|
||||
|
||||
void atom(Offset at, String sym, Color col, double r) {
|
||||
c.drawCircle(at, r, Paint()..color = col);
|
||||
c.drawCircle(
|
||||
at,
|
||||
r,
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: sym,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900)),
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
tp.paint(c, at + Offset(-tp.width / 2, -9));
|
||||
}
|
||||
|
||||
void bond(Offset a, Offset b, {bool dbl = false}) {
|
||||
final p = Paint()
|
||||
..color = Colors.white70
|
||||
..strokeWidth = 3
|
||||
..strokeCap = StrokeCap.round;
|
||||
if (!dbl) {
|
||||
c.drawLine(a, b, p);
|
||||
} else {
|
||||
final n = Offset(-(b.dy - a.dy), b.dx - a.dx);
|
||||
final nn = n / n.distance * 4;
|
||||
c.drawLine(a + nn, b + nn, p);
|
||||
c.drawLine(a - nn, b - nn, p);
|
||||
}
|
||||
}
|
||||
|
||||
switch (molIndex) {
|
||||
case 0: // H2O bent
|
||||
atom(o, 'O', const Color(0xFFFF375F), 24);
|
||||
lonePair(o, -math.pi / 2);
|
||||
lonePair(o, math.pi / 2 + 0.2);
|
||||
final h1 = o + const Offset(-64, 40), h2 = o + const Offset(64, 40);
|
||||
bond(o, h1);
|
||||
bond(o, h2);
|
||||
atom(h1, 'H', const Color(0xFF60A5FA), 15);
|
||||
atom(h2, 'H', const Color(0xFF60A5FA), 15);
|
||||
_angleArc(c, o, 104.5);
|
||||
break;
|
||||
case 1: // CO2 linear
|
||||
atom(o, 'C', const Color(0xFF94A3B8), 20);
|
||||
final o1 = o + const Offset(-86, 0), o2 = o + const Offset(86, 0);
|
||||
bond(o, o1, dbl: true);
|
||||
bond(o, o2, dbl: true);
|
||||
atom(o1, 'O', const Color(0xFFFF375F), 19);
|
||||
atom(o2, 'O', const Color(0xFFFF375F), 19);
|
||||
lonePair(o1, math.pi);
|
||||
lonePair(o2, 0);
|
||||
break;
|
||||
case 2: // NH3 pyramidal (pseudo-3D)
|
||||
atom(o, 'N', const Color(0xFF0071E3), 22);
|
||||
lonePair(o, -math.pi / 2);
|
||||
final hs = [
|
||||
o + const Offset(-66, 46),
|
||||
o + const Offset(66, 46),
|
||||
o + const Offset(0, 66)
|
||||
];
|
||||
for (final h in hs) {
|
||||
bond(o, h);
|
||||
atom(h, 'H', const Color(0xFF60A5FA), 14);
|
||||
}
|
||||
// depth ellipse
|
||||
c.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: o + const Offset(0, 52), width: 150, height: 26),
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.10)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
break;
|
||||
case 3: // CH4 tetrahedral projection
|
||||
atom(o, 'C', const Color(0xFF30D158), 20);
|
||||
final vs = [
|
||||
o + const Offset(-64, -44),
|
||||
o + const Offset(64, -44),
|
||||
o + const Offset(-58, 52),
|
||||
o + const Offset(58, 52)
|
||||
];
|
||||
for (int i = 0; i < vs.length; i++) {
|
||||
if (i == 3) {
|
||||
// wedge
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(o.dx, o.dy)
|
||||
..lineTo(vs[i].dx - 8, vs[i].dy)
|
||||
..lineTo(vs[i].dx + 8, vs[i].dy)
|
||||
..close(),
|
||||
Paint()..color = Colors.white70);
|
||||
} else {
|
||||
bond(o, vs[i]);
|
||||
}
|
||||
atom(vs[i], 'H', const Color(0xFF60A5FA), 14);
|
||||
}
|
||||
break;
|
||||
default: // O2
|
||||
final a1 = o + const Offset(-52, 0), a2 = o + const Offset(52, 0);
|
||||
bond(a1, a2, dbl: true);
|
||||
atom(a1, 'O', const Color(0xFFFF375F), 22);
|
||||
atom(a2, 'O', const Color(0xFFFF375F), 22);
|
||||
lonePair(a1, math.pi);
|
||||
lonePair(a2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void _angleArc(Canvas c, Offset o, double deg) {
|
||||
c.drawArc(
|
||||
Rect.fromCircle(center: o, radius: 44),
|
||||
math.pi / 2 - deg * math.pi / 360,
|
||||
deg * math.pi / 180,
|
||||
false,
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..strokeWidth = 1.6
|
||||
..style = PaintingStyle.stroke);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _LewisPainter o) =>
|
||||
o.molIndex != molIndex || o.showLonePairs != showLonePairs;
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// CIVICS LABS — GRADE 10 (التربية الوطنية والمدنية الصف العاشر)
|
||||
/// Lesson 1: التربية الوطنية والمدنية: المفهوم والدلالات وميزان المواطنة الفاعلة
|
||||
/// Direct alignment with Jordan Grade 10 Civics Curriculum (Unit 1, Lesson 1)
|
||||
/// ============================================================================
|
||||
|
||||
class CivicsActiveCitizenshipLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const CivicsActiveCitizenshipLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<CivicsActiveCitizenshipLabView> createState() =>
|
||||
_CivicsActiveCitizenshipLabViewState();
|
||||
}
|
||||
|
||||
class _CivicsActiveCitizenshipLabViewState
|
||||
extends State<CivicsActiveCitizenshipLabView> {
|
||||
double rightsScore = 75.0; // 0 to 100
|
||||
double dutiesScore = 75.0; // 0 to 100
|
||||
double communityScore = 60.0; // 0 to 100
|
||||
int selectedScenarioIndex = 0;
|
||||
|
||||
static const scenarios = [
|
||||
{
|
||||
'title': 'حماية الممتلكات المدرسية والعامة',
|
||||
'desc':
|
||||
'المحافظة على أثاث المدرسة والحدائق العامة واجب وطني يعزز استدامة الموارد للأجيال.',
|
||||
'type': 'واجب وطني',
|
||||
'icon': CupertinoIcons.shield_fill,
|
||||
},
|
||||
{
|
||||
'title': 'المشاركة في الأنشطة التطوعية',
|
||||
'desc':
|
||||
'مبادرات تنظيف الأحياء ومساعدة الفئات المحتاجة تجسيد حي للمواطنة الصالحة والمسؤولية المجتمعية.',
|
||||
'type': 'مواطنة فاعلة',
|
||||
'icon': CupertinoIcons.person_3_fill,
|
||||
},
|
||||
{
|
||||
'title': 'احترام سيادة القانون وقيم الحوار',
|
||||
'desc':
|
||||
'سيادة القانون وحرية التعبير المسؤولة ركيزتان أساسيتان لصون كرامة الفرد وتماسك الدولة.',
|
||||
'type': 'حقوق وقانون',
|
||||
'icon': CupertinoIcons.doc_plaintext,
|
||||
},
|
||||
];
|
||||
|
||||
double get balanceDiff => rightsScore - dutiesScore;
|
||||
|
||||
String get citizenshipStatus {
|
||||
if (balanceDiff.abs() <= 10 && communityScore >= 60) {
|
||||
return 'مواطنة فاعلة ونموذجية (توازن دستوري مثالي) 🌟';
|
||||
} else if (balanceDiff > 15) {
|
||||
return 'خلل: تركيز على المطالبة بالحقوق مع نقص أداء الواجبات ⚠️';
|
||||
} else if (balanceDiff < -15) {
|
||||
return 'خلل: أداء الواجبات دون تمتع كامل بالحقوق الدستورية ⚠️';
|
||||
} else {
|
||||
return 'مواطنة قيد التطوير والمشاركة المجتمعية 📈';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final status = citizenshipStatus;
|
||||
final isBalanced = balanceDiff.abs() <= 10;
|
||||
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'مختبر المواطنة وميزان الحقوق والواجبات',
|
||||
subtitleAr:
|
||||
'محاكاة تفاعلية لتوازن الحقوق والواجبات الدستورية والمواطنة الفاعلة',
|
||||
identity: kCivicsActiveCitizenshipLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'تنشئة المواطن على حب وطنه والانتماء لدولته أرضاً وشعباً ونظاماً وتعريفه بحقوقه وواجباته يُعرف بـ …',
|
||||
checkpointOptions: const [
|
||||
'التربية الوطنية (National Education)',
|
||||
'التربية المدنية (Civic Education)',
|
||||
'التكافل الاقتصادي',
|
||||
'العلاقات الدولية',
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('ميزان الحقوق: ${rightsScore.toInt()}%',
|
||||
color: const Color(0xFF60A5FA)),
|
||||
LabPill('ميزان الواجبات: ${dutiesScore.toInt()}%',
|
||||
color: const Color(0xFF30D158)),
|
||||
LabPill('المشاركة: ${communityScore.toInt()}%',
|
||||
color: const Color(0xFFFFD60A)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _CitizenshipScalePainter(
|
||||
balanceDiff: balanceDiff,
|
||||
isBalanced: isBalanced,
|
||||
communityScore: communityScore,
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'التمتع بالحقوق الدستورية (تعليم، رعاية، أمان)',
|
||||
value: rightsScore,
|
||||
min: 10,
|
||||
max: 100,
|
||||
display: '${rightsScore.toInt()}%',
|
||||
onChanged: (v) => setState(() => rightsScore = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabSlider(
|
||||
label: 'النهوض بالواجبات والمسؤوليات (احترام القانون، الدفاع)',
|
||||
value: dutiesScore,
|
||||
min: 10,
|
||||
max: 100,
|
||||
display: '${dutiesScore.toInt()}%',
|
||||
onChanged: (v) => setState(() => dutiesScore = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabSlider(
|
||||
label: 'مستوى المشاركة الإيجابية والتطوع المجتمعي',
|
||||
value: communityScore,
|
||||
min: 10,
|
||||
max: 100,
|
||||
display: '${communityScore.toInt()}%',
|
||||
onChanged: (v) => setState(() => communityScore = v),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isBalanced
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.12)
|
||||
: const Color(0xFFFF9F0A).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: isBalanced
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.35)
|
||||
: const Color(0xFFFF9F0A).withValues(alpha: 0.35),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('تشخيص حالة المواطنة:',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12)),
|
||||
const SizedBox(height: 4),
|
||||
Text(status,
|
||||
style: TextStyle(
|
||||
color: isBalanced
|
||||
? const Color(0xFF30D158)
|
||||
: const Color(0xFFFF9F0A),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12.5)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text('دراسة حالة ومواقف تطبيقية:',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 13)),
|
||||
const SizedBox(height: 6),
|
||||
...List.generate(scenarios.length, (idx) {
|
||||
final sc = scenarios[idx];
|
||||
final isSel = selectedScenarioIndex == idx;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => selectedScenarioIndex = idx),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSel
|
||||
? AppColors.saqelCyan.withValues(alpha: 0.15)
|
||||
: kSaqelGlass,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isSel ? AppColors.saqelCyan : Colors.white12,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(sc['icon'] as IconData,
|
||||
color: isSel ? AppColors.saqelCyan : Colors.white60,
|
||||
size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
sc['title'] as String,
|
||||
style: TextStyle(
|
||||
color: isSel ? Colors.white : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: isSel ? FontWeight.w700 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
footerNote:
|
||||
'منهاج وزارة التربية والتعليم الأردنية • التربية الوطنية والمدنية الصف العاشر • الوحدة الأولى ص 8-12',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CitizenshipScalePainter extends CustomPainter {
|
||||
final double balanceDiff;
|
||||
final bool isBalanced;
|
||||
final double communityScore;
|
||||
|
||||
_CitizenshipScalePainter({
|
||||
required this.balanceDiff,
|
||||
required this.isBalanced,
|
||||
required this.communityScore,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width;
|
||||
final h = size.height;
|
||||
final cx = w * 0.5;
|
||||
final cy = h * 0.45;
|
||||
|
||||
// Background gradient
|
||||
final bg = Paint()
|
||||
..shader = const RadialGradient(
|
||||
colors: [Color(0xFF0F1E36), Color(0xFF07111F)],
|
||||
radius: 0.9,
|
||||
).createShader(Rect.fromLTWH(0, 0, w, h));
|
||||
canvas.drawRect(Rect.fromLTWH(0, 0, w, h), bg);
|
||||
|
||||
// Scale stand
|
||||
final standPaint = Paint()
|
||||
..color = const Color(0xFF475569)
|
||||
..strokeWidth = 6.0
|
||||
..strokeCap = StrokeCap.round;
|
||||
canvas.drawLine(Offset(cx, cy - 60), Offset(cx, cy + 90), standPaint);
|
||||
// Base
|
||||
canvas.drawLine(Offset(cx - 50, cy + 90), Offset(cx + 50, cy + 90),
|
||||
standPaint..strokeWidth = 8.0);
|
||||
|
||||
// Pivot
|
||||
final pivotPaint = Paint()
|
||||
..color = isBalanced ? const Color(0xFFFFD60A) : const Color(0xFF94A3B8);
|
||||
canvas.drawCircle(Offset(cx, cy - 60), 10, pivotPaint);
|
||||
|
||||
// Beam angle (tilted based on balanceDiff: + rights tilts right, - duties tilts left)
|
||||
final tiltAngle = (balanceDiff / 100.0) * (math.pi / 10.0);
|
||||
final armLen = w * 0.32;
|
||||
|
||||
final leftX = cx - (armLen * math.cos(tiltAngle));
|
||||
final leftY = (cy - 60) - (armLen * math.sin(tiltAngle));
|
||||
final rightX = cx + (armLen * math.cos(tiltAngle));
|
||||
final rightY = (cy - 60) + (armLen * math.sin(tiltAngle));
|
||||
|
||||
final beamPaint = Paint()
|
||||
..color = isBalanced ? AppColors.saqelCyan : const Color(0xFFCBD5E1)
|
||||
..strokeWidth = 4.5;
|
||||
canvas.drawLine(Offset(leftX, leftY), Offset(rightX, rightY), beamPaint);
|
||||
|
||||
// Strings and pans
|
||||
final panPaint = Paint()
|
||||
..color = const Color(0xFF334155)
|
||||
..style = PaintingStyle.fill;
|
||||
final borderPaint = Paint()
|
||||
..color = isBalanced ? AppColors.saqelCyan : Colors.white30
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0;
|
||||
|
||||
// Left Pan: Duties (الواجبات)
|
||||
canvas.drawLine(Offset(leftX, leftY), Offset(leftX - 22, leftY + 55),
|
||||
standPaint..strokeWidth = 1.5);
|
||||
canvas.drawLine(Offset(leftX, leftY), Offset(leftX + 22, leftY + 55),
|
||||
standPaint..strokeWidth = 1.5);
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(leftX, leftY + 55), width: 65, height: 16),
|
||||
panPaint);
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(leftX, leftY + 55), width: 65, height: 16),
|
||||
borderPaint);
|
||||
|
||||
// Right Pan: Rights (الحقوق)
|
||||
canvas.drawLine(Offset(rightX, rightY), Offset(rightX - 22, rightY + 55),
|
||||
standPaint..strokeWidth = 1.5);
|
||||
canvas.drawLine(Offset(rightX, rightY), Offset(rightX + 22, rightY + 55),
|
||||
standPaint..strokeWidth = 1.5);
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(rightX, rightY + 55), width: 65, height: 16),
|
||||
panPaint);
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(rightX, rightY + 55), width: 65, height: 16),
|
||||
borderPaint);
|
||||
|
||||
// Text labels
|
||||
final tpLeft = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: 'الواجبات الوطنية 🛡️',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tpLeft.paint(canvas, Offset(leftX - (tpLeft.width / 2), leftY + 68));
|
||||
|
||||
final tpRight = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: 'الحقوق الدستورية ⚖️',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF60A5FA),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tpRight.paint(canvas, Offset(rightX - (tpRight.width / 2), rightY + 68));
|
||||
|
||||
// Harmony badge in center if balanced
|
||||
if (isBalanced) {
|
||||
final glowPaint = Paint()
|
||||
..color = const Color(0xFFFFD60A).withValues(alpha: 0.25)
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 15);
|
||||
canvas.drawCircle(Offset(cx, cy + 30), 28, glowPaint);
|
||||
|
||||
final badgePaint = Paint()..color = const Color(0xFFFFD60A);
|
||||
canvas.drawCircle(Offset(cx, cy + 30), 18, badgePaint);
|
||||
|
||||
final tpCrown = TextPainter(
|
||||
text: const TextSpan(text: '👑', style: TextStyle(fontSize: 16)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tpCrown.paint(canvas, Offset(cx - (tpCrown.width / 2), cy + 20));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _CitizenshipScalePainter oldDelegate) =>
|
||||
oldDelegate.balanceDiff != balanceDiff ||
|
||||
oldDelegate.isBalanced != isBalanced ||
|
||||
oldDelegate.communityScore != communityScore;
|
||||
}
|
||||
@@ -0,0 +1,657 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// EARTH & ENVIRONMENTAL SCIENCES — GRADE 10
|
||||
/// 1. EarthMohsHardnessLabView — اختبار موهس للخدش
|
||||
/// 2. EarthRockCycleLabView — دورة الصخور (حرارة/ضغط/تجوية/تبريد)
|
||||
/// 3. EarthStratigraphyLabView — التأريخ النسبي والطبقات
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) MOHS
|
||||
// ---------------------------------------------------------------------------
|
||||
class EarthMohsHardnessLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const EarthMohsHardnessLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<EarthMohsHardnessLabView> createState() =>
|
||||
_EarthMohsHardnessLabViewState();
|
||||
}
|
||||
|
||||
class _EarthMohsHardnessLabViewState extends State<EarthMohsHardnessLabView> {
|
||||
int tool =
|
||||
1; // 0 fingernail 2.5, 1 copper 3.5, 2 steel 5.5, 3 glass 5.5? use file 6.5
|
||||
int mineral = 3; // 0 talc1 .. 9 diamond10
|
||||
double scratchX = 0.5;
|
||||
bool scratching = false;
|
||||
|
||||
static const tools = [
|
||||
{'n': 'ظفر 2.5', 'h': 2.5},
|
||||
{'n': 'قطعة نحاس 3.5', 'h': 3.5},
|
||||
{'n': 'مسمار فولاذ 5.5', 'h': 5.5},
|
||||
{'n': 'مبرد 6.5', 'h': 6.5},
|
||||
];
|
||||
static const minerals = [
|
||||
{'n': 'تالك', 'h': 1.0, 'c': Color(0xFFE2E8F0)},
|
||||
{'n': 'جبس', 'h': 2.0, 'c': Color(0xFFFDE68A)},
|
||||
{'n': 'كالسيت', 'h': 3.0, 'c': Color(0xFFFED7AA)},
|
||||
{'n': 'فلوريت', 'h': 4.0, 'c': Color(0xFF86EFAC)},
|
||||
{'n': 'أباتيت', 'h': 5.0, 'c': Color(0xFF60A5FA)},
|
||||
{'n': 'أورثوكليز', 'h': 6.0, 'c': Color(0xFFF9A8D4)},
|
||||
{'n': 'كوارتز', 'h': 7.0, 'c': Color(0xFFE0E7FF)},
|
||||
{'n': 'توباز', 'h': 8.0, 'c': Color(0xFFFFD166)},
|
||||
{'n': 'كورندم', 'h': 9.0, 'c': Color(0xFFF87171)},
|
||||
{'n': 'ألماس', 'h': 10.0, 'c': Color(0xFF00F5D4)},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final th = (tools[tool]['h'] as double);
|
||||
final mh = (minerals[mineral]['h'] as double);
|
||||
final scratches = th > mh;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'المعادن — اختبار قساوة موهس',
|
||||
subtitleAr:
|
||||
'اسحب أداة الخدش فوق المعدن — الخدش يحدث فقط إذا كانت الأداة أصلب',
|
||||
identity: kEarthMohsHardnessToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'معدن خدشه الظفر (2.5) وخدشته قطعة النحاس (3.5). قساوته التقريبية …',
|
||||
checkpointOptions: const [
|
||||
'بين 2.5 و 3.5',
|
||||
'أكبر من 6',
|
||||
'تساوي 1',
|
||||
'تساوي 10'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('الأداة: ${tools[tool]['n']}', color: const Color(0xFFFF9F0A)),
|
||||
LabPill('المعدن: ${minerals[mineral]['n']} (${mh.toInt()})'),
|
||||
LabPill(scratches ? 'يخدش ✓' : 'لا يخدش ✗',
|
||||
color:
|
||||
scratches ? const Color(0xFF30D158) : const Color(0xFFFF375F)),
|
||||
],
|
||||
canvas: GestureDetector(
|
||||
onHorizontalDragUpdate: (d) {
|
||||
final w = MediaQuery.of(context).size.width;
|
||||
setState(() {
|
||||
scratchX = (scratchX + d.delta.dx / w * 2).clamp(0.05, 0.95);
|
||||
scratching = true;
|
||||
});
|
||||
},
|
||||
onHorizontalDragEnd: (_) => setState(() => scratching = false),
|
||||
child: CustomPaint(
|
||||
painter: _MohsPainter(
|
||||
toolIdx: tool,
|
||||
mineralIdx: mineral,
|
||||
scratchX: scratchX,
|
||||
scratches: scratches,
|
||||
scratching: scratching,
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
controls: [
|
||||
const Text('أداة الخدش:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
LabSegments<int>(
|
||||
labels: const ['ظفر', 'نحاس', 'فولاذ', 'مبرد'],
|
||||
values: const [0, 1, 2, 3],
|
||||
current: tool,
|
||||
onSelected: (v) => setState(() => tool = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text('المعدن المجهول:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
height: 44,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: minerals.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 6),
|
||||
itemBuilder: (ctx, i) {
|
||||
final sel = i == mineral;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => mineral = i),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: sel
|
||||
? AppColors.saqelCyan
|
||||
: Colors.white.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: sel ? AppColors.saqelCyan : Colors.white12),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(minerals[i]['n'] as String,
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white70,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 11.5)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: scratches
|
||||
? 'نتيجة: الأداة أصلد — يترك أخدوداً'
|
||||
: 'نتيجة: المعدن أصلد — لا أثر',
|
||||
body:
|
||||
'قساوة الأداة $th مقابل المعدن $mh.\nالقاعدة: يخدش الأصلدُ الألينَ فقط. كرر بأدوات أعلى وأدنى لتضييق المجال.',
|
||||
),
|
||||
],
|
||||
footerNote: 'سلم موهس ترتيبي نسبي (1–10) وليس مقياس صلابة خطياً.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MohsPainter extends CustomPainter {
|
||||
final int toolIdx, mineralIdx;
|
||||
final double scratchX;
|
||||
final bool scratches, scratching;
|
||||
_MohsPainter(
|
||||
{required this.toolIdx,
|
||||
required this.mineralIdx,
|
||||
required this.scratchX,
|
||||
required this.scratches,
|
||||
required this.scratching});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF0B1526));
|
||||
// Mohs rail top
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final x = 14.0 + i * ((s.width - 28) / 10);
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(
|
||||
x, 12, x + (s.width - 28) / 10 - 4, 40, const Radius.circular(6)),
|
||||
Paint()
|
||||
..color = i == mineralIdx
|
||||
? const Color(0xFF00F5D4)
|
||||
: Colors.white.withValues(alpha: 0.08));
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${i + 1}',
|
||||
style: TextStyle(
|
||||
color: i == mineralIdx ? Colors.black : Colors.white60,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
tp.paint(c, Offset(x + 8, 18));
|
||||
}
|
||||
// mineral slab
|
||||
final slab = RRect.fromLTRBR(30, s.height * 0.52, s.width - 30,
|
||||
s.height * 0.82, const Radius.circular(16));
|
||||
c.drawRRect(slab, Paint()..color = const Color(0xFF1E293B));
|
||||
c.drawRRect(
|
||||
slab,
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4);
|
||||
// scratch groove
|
||||
if (scratches) {
|
||||
final gx = 30 + scratchX * (s.width - 60);
|
||||
c.drawLine(
|
||||
Offset(40, s.height * 0.66),
|
||||
Offset(gx, s.height * 0.66),
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..strokeWidth = scratching ? 3.4 : 2.2);
|
||||
// dust
|
||||
final rnd = math.Random(3);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
c.drawCircle(
|
||||
Offset(gx + rnd.nextDouble() * 20 - 10,
|
||||
s.height * 0.66 + rnd.nextDouble() * 14 - 4),
|
||||
1.6,
|
||||
Paint()..color = Colors.white60);
|
||||
}
|
||||
}
|
||||
// tool stylus
|
||||
final gx = 30 + scratchX * (s.width - 60);
|
||||
c.drawLine(
|
||||
Offset(gx + 30, 70),
|
||||
Offset(gx, s.height * 0.64),
|
||||
Paint()
|
||||
..color = const Color(0xFFFF9F0A)
|
||||
..strokeWidth = 6
|
||||
..strokeCap = StrokeCap.round);
|
||||
c.drawCircle(Offset(gx, s.height * 0.64), 7,
|
||||
Paint()..color = const Color(0xFFFFD166));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _MohsPainter o) =>
|
||||
o.scratchX != scratchX ||
|
||||
o.toolIdx != toolIdx ||
|
||||
o.mineralIdx != mineralIdx ||
|
||||
o.scratching != scratching;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) ROCK CYCLE
|
||||
// ---------------------------------------------------------------------------
|
||||
class EarthRockCycleLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const EarthRockCycleLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<EarthRockCycleLabView> createState() => _EarthRockCycleLabViewState();
|
||||
}
|
||||
|
||||
class _EarthRockCycleLabViewState extends State<EarthRockCycleLabView> {
|
||||
int rock = 0; // 0 igneous, 1 sedimentary, 2 metamorphic, 3 magma
|
||||
double heat = 0.4, pressure = 0.3, weathering = 0.3, cooling = 0.5;
|
||||
String? lastEvent;
|
||||
|
||||
void _apply(String kind) {
|
||||
setState(() {
|
||||
lastEvent = kind;
|
||||
if (kind == 'تبريد وتبلور') {
|
||||
if (rock == 3) rock = 0;
|
||||
} else if (kind == 'تجوية وتعرية وترسيب') {
|
||||
if (rock != 3) rock = 1;
|
||||
} else if (kind == 'حرارة وضغط') {
|
||||
if (rock == 0 || rock == 1) rock = 2;
|
||||
} else if (kind == 'انصهار') {
|
||||
rock = 3;
|
||||
}
|
||||
});
|
||||
saqelTick();
|
||||
}
|
||||
|
||||
static const names = [
|
||||
'نارية Igneous',
|
||||
'رسوبية Sedimentary',
|
||||
'متحولة Metamorphic',
|
||||
'صهارة Magma'
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'دورة الصخور في الطبيعة',
|
||||
subtitleAr: 'ادفع الصخر بالحرارة والضغط والتجوية والتبريد عبر المسارات',
|
||||
identity: kEarthRockCycleLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'الصخر الرسوبي يتحول إلى متحول أساساً بفعل …',
|
||||
checkpointOptions: const [
|
||||
'الحرارة والضغط',
|
||||
'التبريد السريع',
|
||||
'الانصهار الكامل',
|
||||
'الذوبان في الماء فقط'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('الحالي: ${names[rock]}', color: const Color(0xFFFF9F0A)),
|
||||
if (lastEvent != null)
|
||||
LabPill('آخر عملية: $lastEvent', color: const Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _RockCyclePainter(current: rock),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'حرارة',
|
||||
value: heat,
|
||||
min: 0,
|
||||
max: 1,
|
||||
display: '${(heat * 100).toInt()}%',
|
||||
accent: const Color(0xFFFF375F),
|
||||
onChanged: (v) => setState(() => heat = v)),
|
||||
LabSlider(
|
||||
label: 'ضغط',
|
||||
value: pressure,
|
||||
min: 0,
|
||||
max: 1,
|
||||
display: '${(pressure * 100).toInt()}%',
|
||||
accent: const Color(0xFF0071E3),
|
||||
onChanged: (v) => setState(() => pressure = v)),
|
||||
LabSlider(
|
||||
label: 'تجوية وتعرية',
|
||||
value: weathering,
|
||||
min: 0,
|
||||
max: 1,
|
||||
display: '${(weathering * 100).toInt()}%',
|
||||
accent: const Color(0xFF30D158),
|
||||
onChanged: (v) => setState(() => weathering = v)),
|
||||
LabSlider(
|
||||
label: 'تبريد',
|
||||
value: cooling,
|
||||
min: 0,
|
||||
max: 1,
|
||||
display: '${(cooling * 100).toInt()}%',
|
||||
accent: const Color(0xFF00F5D4),
|
||||
onChanged: (v) => setState(() => cooling = v)),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_go('انصهار 🫠', heat > 0.65),
|
||||
_go('حرارة وضغط 🔥', heat > 0.45 && pressure > 0.45),
|
||||
_go('تجوية وتعرية وترسيب 🌊', weathering > 0.45),
|
||||
_go('تبريد وتبلور ❄️', cooling > 0.45),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const LabFormulaCard(
|
||||
title: 'مسارات الدورة (الكتاب)',
|
||||
body:
|
||||
'صهارة ← تبريد ← نارية ← تجوية ← رسوبية ← حرارة وضغط ← متحولة ← انصهار ← صهارة.\nكل سهم عملية جيولوجية حقيقية بزمن طويل.',
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'العتبات على المنزلقات للتبسيط الصفي؛ الطبيعة تعمل بتدرج مستمر عبر ملايين السنين.',
|
||||
);
|
||||
}
|
||||
|
||||
Widget _go(String label, bool enabled) {
|
||||
return ElevatedButton(
|
||||
onPressed: enabled
|
||||
? () => _apply(label.split(' ').first +
|
||||
(label.contains('و') ? ' ${label.split(' ')[1]}' : ''))
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: enabled ? AppColors.saqelCyan : Colors.white12,
|
||||
foregroundColor: enabled ? Colors.black : Colors.white38,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
child: Text(label,
|
||||
style: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.w800)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RockCyclePainter extends CustomPainter {
|
||||
final int current;
|
||||
_RockCyclePainter({required this.current});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF0B1526));
|
||||
final cx = s.width / 2, cy = s.height / 2;
|
||||
final r = math.min(s.width, s.height) * 0.30;
|
||||
const labels = ['نارية', 'رسوبية', 'متحولة', 'صهارة'];
|
||||
const cols = [
|
||||
Color(0xFFFF375F),
|
||||
Color(0xFFFFD166),
|
||||
Color(0xFF8B5CF6),
|
||||
Color(0xFFFF9F0A)
|
||||
];
|
||||
// cycle ring
|
||||
c.drawCircle(
|
||||
Offset(cx, cy),
|
||||
r,
|
||||
Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.10)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 10);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final a = -math.pi / 2 + i * math.pi / 2;
|
||||
final p = Offset(cx + r * math.cos(a), cy + r * math.sin(a));
|
||||
final sel = i == current;
|
||||
c.drawCircle(p, sel ? 34 : 27,
|
||||
Paint()..color = cols[i].withValues(alpha: sel ? 0.95 : 0.45));
|
||||
c.drawCircle(
|
||||
p,
|
||||
sel ? 34 : 27,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = sel ? 2.4 : 1.2);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: labels[i],
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w900)),
|
||||
textDirection: TextDirection.rtl)
|
||||
..layout();
|
||||
tp.paint(c, p + Offset(-tp.width / 2, -8));
|
||||
// arrow to next
|
||||
final a2 = a + math.pi / 2;
|
||||
final mid = Offset(cx + r * math.cos(a + math.pi / 4),
|
||||
cy + r * math.sin(a + math.pi / 4));
|
||||
final tp2 = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: '➜', style: TextStyle(color: Colors.white60, fontSize: 16)),
|
||||
textDirection: TextDirection.ltr)
|
||||
..layout();
|
||||
tp2.paint(c, mid + const Offset(-6, -10));
|
||||
a2.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _RockCyclePainter o) => o.current != current;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3) STRATIGRAPHY
|
||||
// ---------------------------------------------------------------------------
|
||||
class EarthStratigraphyLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const EarthStratigraphyLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<EarthStratigraphyLabView> createState() =>
|
||||
_EarthStratigraphyLabViewState();
|
||||
}
|
||||
|
||||
class _EarthStratigraphyLabViewState extends State<EarthStratigraphyLabView> {
|
||||
bool fault = true;
|
||||
bool dike = true;
|
||||
bool unconformity = true;
|
||||
int highlight = 0; // 0 none, 1 superposition, 2 cross-cutting, 3 unconformity
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'التأريخ النسبي — الطبقات والقواطع',
|
||||
subtitleAr: 'ابنِ مقطعاً جيولوجياً وطبق التراكب والقواطع وعدم التوافق',
|
||||
identity: kEarthStratigraphyToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'قاطع ناري يخترق طبقات رسوبية. عمره النسبي …',
|
||||
checkpointOptions: const [
|
||||
'أحدث من الطبقات التي يقطعها',
|
||||
'أقدم منها دائماً',
|
||||
'مساوٍ لها',
|
||||
'لا يمكن تحديده'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('التراكب: الأسفل أقدم'),
|
||||
if (fault && dike)
|
||||
const LabPill('القواطع أحدث مما تقطعه', color: Color(0xFFFF375F)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _StrataPainter(
|
||||
fault: fault,
|
||||
dike: dike,
|
||||
unconformity: unconformity,
|
||||
highlight: highlight),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabToggle(
|
||||
label: 'صدع (Fault)',
|
||||
hint: 'علاقة القاطع: أحدث',
|
||||
value: fault,
|
||||
onChanged: (v) => setState(() => fault = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabToggle(
|
||||
label: 'قاطع ناري (Dike)',
|
||||
hint: 'يخترق الطبقات',
|
||||
value: dike,
|
||||
accent: const Color(0xFFFF375F),
|
||||
onChanged: (v) => setState(() => dike = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabToggle(
|
||||
label: 'سطح عدم توافق',
|
||||
hint: 'فجوة زمنية (تعرية)',
|
||||
value: unconformity,
|
||||
accent: const Color(0xFFFFD166),
|
||||
onChanged: (v) => setState(() => unconformity = v)),
|
||||
const SizedBox(height: 8),
|
||||
const Text('أبرز المبدأ:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
LabSegments<int>(
|
||||
labels: const ['لا شيء', 'التراكب', 'القواطع', 'عدم التوافق'],
|
||||
values: const [0, 1, 2, 3],
|
||||
current: highlight,
|
||||
onSelected: (v) => setState(() => highlight = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: _lawTitle(),
|
||||
body: _lawBody(),
|
||||
accent: const Color(0xFFFFD166),
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'الأعمار نسبية (أقدم/أحدث)؛ العمر المطلق يحتاج نظائر مشعة في المختبر.',
|
||||
);
|
||||
}
|
||||
|
||||
String _lawTitle() {
|
||||
switch (highlight) {
|
||||
case 1:
|
||||
return 'قانون التراكب (ستينو)';
|
||||
case 2:
|
||||
return 'علاقة القاطع';
|
||||
case 3:
|
||||
return 'عدم التوافق';
|
||||
default:
|
||||
return 'مبادئ التأريخ النسبي';
|
||||
}
|
||||
}
|
||||
|
||||
String _lawBody() {
|
||||
switch (highlight) {
|
||||
case 1:
|
||||
return 'في التتابع غير المقلوب: الطبقة السفلى أقدم من العليا. الطبقة D (سفلى) أقدم من A (عليا).';
|
||||
case 2:
|
||||
return 'كل بنية تقطع أخرى فهي أحدث منها: الصدع والقاطع الناري أحدث من كل الطبقات التي يخترقانها.';
|
||||
case 3:
|
||||
return 'سطح التعرية المتموج = زمن مفقود: ترسيب ثم تعرية ثم ترسيب جديد فوقه.';
|
||||
default:
|
||||
return 'رتّب الأحداث من الأقدم إلى الأحدث باستخدام المبادئ الثلاثة معاً كما في الكتاب.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _StrataPainter extends CustomPainter {
|
||||
final bool fault, dike, unconformity;
|
||||
final int highlight;
|
||||
_StrataPainter(
|
||||
{required this.fault,
|
||||
required this.dike,
|
||||
required this.unconformity,
|
||||
required this.highlight});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF0B1526));
|
||||
const layers = [
|
||||
{'n': 'A (الأحدث)', 'col': Color(0xFFD9C7A7)},
|
||||
{'n': 'B', 'col': Color(0xFFB08968)},
|
||||
{'n': 'C', 'col': Color(0xFF7F5539)},
|
||||
{'n': 'D (الأقدم)', 'col': Color(0xFF5C3D2E)},
|
||||
];
|
||||
final top = 40.0, h = (s.height - 70) / 4;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final y = top + i * h;
|
||||
var path = Path()
|
||||
..moveTo(10, y)
|
||||
..lineTo(s.width - 10, y)
|
||||
..lineTo(s.width - 10, y + h)
|
||||
..lineTo(10, y + h)
|
||||
..close();
|
||||
if (unconformity && i == 2) {
|
||||
// wavy top
|
||||
path = Path()..moveTo(10, y + 8);
|
||||
for (double x = 10; x <= s.width - 10; x += 10) {
|
||||
path.lineTo(x, y + 8 + math.sin(x / 18) * 5);
|
||||
}
|
||||
path.lineTo(s.width - 10, y + h);
|
||||
path.lineTo(10, y + h);
|
||||
path.close();
|
||||
}
|
||||
c.drawPath(path, Paint()..color = (layers[i]['col'] as Color));
|
||||
if (highlight == 1) {
|
||||
c.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.4);
|
||||
}
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: layers[i]['n'] as String,
|
||||
style: const TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl)
|
||||
..layout();
|
||||
tp.paint(c, Offset(18, y + 6));
|
||||
}
|
||||
if (fault) {
|
||||
final fx = s.width * 0.62;
|
||||
c.drawLine(
|
||||
Offset(fx - 24, top),
|
||||
Offset(fx + 24, s.height - 30),
|
||||
Paint()
|
||||
..color = highlight == 2 ? const Color(0xFFFF375F) : Colors.white
|
||||
..strokeWidth = highlight == 2 ? 4 : 2.6);
|
||||
}
|
||||
if (dike) {
|
||||
final dx = s.width * 0.34;
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(
|
||||
dx - 9, top - 6, dx + 9, s.height - 28, const Radius.circular(6)),
|
||||
Paint()
|
||||
..color = highlight == 2
|
||||
? const Color(0xFFFF375F)
|
||||
: const Color(0xFF475569));
|
||||
}
|
||||
if (unconformity) {
|
||||
final tp = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: 'سطح عدم توافق 〜〜',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFFD166),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(c, Offset(s.width - tp.width - 16, top + 2 * h - 6));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _StrataPainter o) =>
|
||||
o.fault != fault ||
|
||||
o.dike != dike ||
|
||||
o.unconformity != unconformity ||
|
||||
o.highlight != highlight;
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_tts/flutter_tts.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ENGLISH (Action Pack 10) + ARABIC LANGUAGE NOTE
|
||||
/// 1. EnglishPhoneticsLabView — IPA studio + TTS speeds + stress + pairs
|
||||
/// 2. EnglishTenseTimelineLabView — Present Perfect vs Past Simple + chips
|
||||
/// Per AGENTS.md: English derives from the published lesson bundle; the word
|
||||
/// list below is a clearly-labelled lesson-level practice set, not a claim
|
||||
/// to be the textbook verbatim. No invented IPA is presented as certified.
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) PHONETICS & IPA STUDIO
|
||||
// ---------------------------------------------------------------------------
|
||||
class EnglishPhoneticsLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const EnglishPhoneticsLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<EnglishPhoneticsLabView> createState() =>
|
||||
_EnglishPhoneticsLabViewState();
|
||||
}
|
||||
|
||||
class _EnglishPhoneticsLabViewState extends State<EnglishPhoneticsLabView> {
|
||||
final FlutterTts tts = FlutterTts();
|
||||
int word = 0;
|
||||
double rate = 1.0;
|
||||
bool speaking = false;
|
||||
|
||||
static const words = [
|
||||
{
|
||||
'w': 'photograph',
|
||||
'ipa': '/ˈfəʊ.tə.ɡrɑːf/',
|
||||
'stress': 'PHO-to-graph',
|
||||
'pair': 'photograph / photographic',
|
||||
'ar': 'صورة فوتوغرافية'
|
||||
},
|
||||
{
|
||||
'w': 'comfortable',
|
||||
'ipa': '/ˈkʌmf.tə.bəl/',
|
||||
'stress': 'COMF-ta-ble',
|
||||
'pair': 'comfortable / uncomfortable',
|
||||
'ar': 'مريح'
|
||||
},
|
||||
{
|
||||
'w': 'vegetable',
|
||||
'ipa': '/ˈvedʒ.tə.bəl/',
|
||||
'stress': 'VEG-ta-ble',
|
||||
'pair': 'vegetable / vegetarian',
|
||||
'ar': 'خضار'
|
||||
},
|
||||
{
|
||||
'w': 'ship / sheep',
|
||||
'ipa': '/ʃɪp/ — /ʃiːp/',
|
||||
'stress': 'minimal pair ɪ/ iː',
|
||||
'pair': 'ship (سفينة) / sheep (خروف)',
|
||||
'ar': 'زوج صوتي minimal pair'
|
||||
},
|
||||
{
|
||||
'w': 'present',
|
||||
'ipa': '/ˈprez.ənt/ (n) — /prɪˈzent/ (v)',
|
||||
'stress': 'PRE-sent / pre-SENT',
|
||||
'pair': 'noun vs verb stress shift',
|
||||
'ar': 'هدية / يقدّم'
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
tts.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _speak(String text) async {
|
||||
setState(() => speaking = true);
|
||||
saqelTick();
|
||||
try {
|
||||
await tts.setLanguage('en-GB');
|
||||
await tts.setSpeechRate(rate * 0.5);
|
||||
await tts.setPitch(1.0);
|
||||
await tts.speak(text);
|
||||
} catch (_) {
|
||||
// TTS unavailable on this device: stay honest, keep text.
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 900));
|
||||
if (mounted) setState(() => speaking = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final w = words[word];
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'استوديو الصوتيات والنطق (IPA)',
|
||||
subtitleAr: 'استمع بسرعات متعددة • مقاطع مشددة • أزواج صوتية',
|
||||
identity: kEnglishPhoneticsToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'In "photograph" the stressed syllable is …',
|
||||
checkpointOptions: const [
|
||||
'PHO (first)',
|
||||
'TO (second)',
|
||||
'GRAPH (third)',
|
||||
'Equal stress'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('${w['w']}'),
|
||||
LabPill('${rate.toStringAsFixed(2)}x', color: const Color(0xFFFFD166)),
|
||||
],
|
||||
canvas: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CustomPaint(
|
||||
painter: _StressPainter(
|
||||
word: w['w']!, stress: w['stress']!, speaking: speaking),
|
||||
child: Container(),
|
||||
),
|
||||
),
|
||||
controls: [
|
||||
const Text('كلمة التدريب (lesson-level practice set):',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: List.generate(words.length, (i) {
|
||||
final sel = i == word;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => word = i),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: sel
|
||||
? AppColors.saqelCyan
|
||||
: Colors.white.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
border: Border.all(
|
||||
color: sel ? AppColors.saqelCyan : Colors.white12),
|
||||
),
|
||||
child: Text(words[i]['w']!,
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white70,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 11.5)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.35),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.10)),
|
||||
),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(w['ipa']!,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF00F5D4),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800)),
|
||||
const SizedBox(height: 4),
|
||||
Text('Stress: ${w['stress']}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700)),
|
||||
Text('${w['pair']} • ${w['ar']}',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text('سرعة النطق:',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 4),
|
||||
LabSegments<double>(
|
||||
labels: const ['0.5x بطيء', '0.75x', '1.0x طبيعي'],
|
||||
values: const [0.5, 0.75, 1.0],
|
||||
current: rate,
|
||||
onSelected: (v) => setState(() => rate = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: Icon(
|
||||
speaking
|
||||
? CupertinoIcons.waveform
|
||||
: CupertinoIcons.speaker_2_fill,
|
||||
size: 17),
|
||||
label: Text(speaking ? 'جارٍ النطق…' : 'استمع للنطق 🔊',
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.w800, fontSize: 13)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.saqelCyan,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: speaking
|
||||
? null
|
||||
: () => _speak((w['w'] as String).split('/').first.trim()),
|
||||
),
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'النطق عبر TTS الجهاز (جودة متغيرة) — ليس تسجيلاً أصلياً للناشر. IPA استرشادي للممارسة.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StressPainter extends CustomPainter {
|
||||
final String word, stress;
|
||||
final bool speaking;
|
||||
_StressPainter(
|
||||
{required this.word, required this.stress, required this.speaking});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
// waveform bars
|
||||
final rnd = math.Random(word.length * 31);
|
||||
final n = 42;
|
||||
for (int i = 0; i < n; i++) {
|
||||
final h = (20 + rnd.nextDouble() * (s.height * 0.3)) *
|
||||
(speaking
|
||||
? (0.6 +
|
||||
0.4 * math.sin(i * 0.8 + DateTime.now().millisecond / 200))
|
||||
: 0.5);
|
||||
final x = 16.0 + i * ((s.width - 32) / n);
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(x, s.height * 0.62 - h / 2, x + (s.width - 32) / n - 3,
|
||||
s.height * 0.62 + h / 2, const Radius.circular(3)),
|
||||
Paint()..color = speaking ? const Color(0xFF00F5D4) : Colors.white24,
|
||||
);
|
||||
}
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: word,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 30, fontWeight: FontWeight.w900)),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout(maxWidth: s.width - 32);
|
||||
tp.paint(c, Offset((s.width - tp.width) / 2, 26));
|
||||
final tp2 = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '● ' + stress,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFFD166),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700)),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
tp2.paint(c, Offset((s.width - tp2.width) / 2, 70));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _StressPainter o) =>
|
||||
o.word != word || o.speaking != speaking;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) TENSE TIMELINE + SENTENCE CHIPS
|
||||
// ---------------------------------------------------------------------------
|
||||
class EnglishTenseTimelineLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const EnglishTenseTimelineLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<EnglishTenseTimelineLabView> createState() =>
|
||||
_EnglishTenseTimelineLabViewState();
|
||||
}
|
||||
|
||||
class _EnglishTenseTimelineLabViewState
|
||||
extends State<EnglishTenseTimelineLabView> {
|
||||
int tense =
|
||||
0; // 0 present perfect, 1 past simple, 2 modals deduction, 3 relative
|
||||
List<String> chosen = [];
|
||||
|
||||
static const data = [
|
||||
{
|
||||
'title': 'Present Perfect (have/has + V3)',
|
||||
'chips': ['I', 'have', 'visited', 'Petra', 'twice'],
|
||||
'ok': ['I', 'have', 'visited', 'Petra', 'twice'],
|
||||
'rule':
|
||||
'حدث ماضٍ بأثر حاضر: have/has + التصريف الثالث. الظروف: just, already, yet, ever, since, for.',
|
||||
'ar': 'لقد زرت البتراء مرتين (والأثر حاضر).',
|
||||
},
|
||||
{
|
||||
'title': 'Past Simple (V2)',
|
||||
'chips': ['We', 'watched', 'the', 'match', 'yesterday'],
|
||||
'ok': ['We', 'watched', 'the', 'match', 'yesterday'],
|
||||
'rule':
|
||||
'حدث انتهى بزمن محدد ماضٍ: التصريف الثاني. الظروف: yesterday, last week, in 2019.',
|
||||
'ar': 'شاهدنا المباراة أمس (زمن محدد انتهى).',
|
||||
},
|
||||
{
|
||||
'title': 'Modals of Deduction',
|
||||
'chips': ['She', 'must', 'be', 'at', 'school'],
|
||||
'ok': ['She', 'must', 'be', 'at', 'school'],
|
||||
'rule': 'استنتاج: must (متأكد) / might (محتمل) / can\'t (مستحيل) + be.',
|
||||
'ar': 'لا بد أنها في المدرسة الآن (استنتاج قوي).',
|
||||
},
|
||||
{
|
||||
'title': 'Relative Clauses',
|
||||
'chips': ['The', 'teacher', 'who', 'helps', 'us'],
|
||||
'ok': ['The', 'teacher', 'who', 'helps', 'us'],
|
||||
'rule': 'who للعاقل / which لغير العاقل / where للمكان / whose للملكية.',
|
||||
'ar': 'المعلم الذي يساعدنا (who تعود على teacher).',
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final d = data[tense];
|
||||
final ok = d['ok'] as List<String>;
|
||||
final chips = List<String>.of(d['chips'] as List<String>)
|
||||
..shuffle(math.Random(tense * 7 + 1));
|
||||
final correct = _listsEqual(chosen, ok);
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'الخط الزمني للأزمنة وبناء الجمل',
|
||||
subtitleAr: 'رتّب الرقائق بالترتيب الصحيح — تحقق نحوي فوري',
|
||||
identity: kEnglishTenseToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: '"I have lived in Amman since 2015" — the tense is …',
|
||||
checkpointOptions: const [
|
||||
'Present Perfect',
|
||||
'Past Simple',
|
||||
'Future',
|
||||
'Present Continuous'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(d['title'] as String),
|
||||
if (chosen.isNotEmpty)
|
||||
LabPill(correct ? 'ترتيب صحيح ✓' : 'واصل المحاولة…',
|
||||
color:
|
||||
correct ? const Color(0xFF30D158) : const Color(0xFFFF9F0A)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _TimelinePainter(tense: tense),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<int>(
|
||||
labels: const ['Perf.', 'Past', 'Modals', 'Relative'],
|
||||
values: const [0, 1, 2, 3],
|
||||
current: tense,
|
||||
onSelected: (v) => setState(() {
|
||||
tense = v;
|
||||
chosen = [];
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(d['rule'] as String,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12, height: 1.7)),
|
||||
Text(d['ar'] as String,
|
||||
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12)),
|
||||
const SizedBox(height: 8),
|
||||
const Text('جملتك (اضغط الرقائق بالترتيب):',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.35),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: correct
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.5)
|
||||
: Colors.white.withValues(alpha: 0.10)),
|
||||
),
|
||||
child: Text(chosen.isEmpty ? '—' : chosen.join(' '),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: chips.map((w) {
|
||||
final used = chosen.contains(w);
|
||||
return GestureDetector(
|
||||
onTap: used
|
||||
? null
|
||||
: () => setState(() {
|
||||
chosen.add(w);
|
||||
saqelTick();
|
||||
}),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: used
|
||||
? Colors.white10
|
||||
: const Color(0xFF0071E3).withValues(alpha: 0.25),
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
border: Border.all(
|
||||
color: used ? Colors.white12 : const Color(0xFF0071E3)),
|
||||
),
|
||||
child: Text(w,
|
||||
style: TextStyle(
|
||||
color: used ? Colors.white24 : Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12.5)),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.refresh, size: 15),
|
||||
label: const Text('مسح الجملة'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24)),
|
||||
onPressed: () => setState(chosen.clear),
|
||||
),
|
||||
],
|
||||
footerNote: 'تدريب ترتيب ذاتي؛ التصحيح المعتمد للواجبات يتم على الخادم.',
|
||||
);
|
||||
}
|
||||
|
||||
bool _listsEqual(List<String> a, List<String> b) {
|
||||
if (a.length != b.length) return false;
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class _TimelinePainter extends CustomPainter {
|
||||
final int tense;
|
||||
_TimelinePainter({required this.tense});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
final y = s.height * 0.55;
|
||||
c.drawLine(
|
||||
Offset(20, y),
|
||||
Offset(s.width - 20, y),
|
||||
Paint()
|
||||
..color = Colors.white38
|
||||
..strokeWidth = 2.5);
|
||||
c.drawLine(
|
||||
Offset(s.width - 20, y),
|
||||
Offset(s.width - 32, y - 7),
|
||||
Paint()
|
||||
..color = Colors.white38
|
||||
..strokeWidth = 2.5);
|
||||
c.drawLine(
|
||||
Offset(s.width - 20, y),
|
||||
Offset(s.width - 32, y + 7),
|
||||
Paint()
|
||||
..color = Colors.white38
|
||||
..strokeWidth = 2.5);
|
||||
// now marker
|
||||
final nowX = tense == 1 ? s.width * 0.35 : s.width * 0.72;
|
||||
c.drawLine(
|
||||
Offset(nowX, y - 34),
|
||||
Offset(nowX, y + 34),
|
||||
Paint()
|
||||
..color = const Color(0xFFFF375F)
|
||||
..strokeWidth = 2);
|
||||
final labels = tense == 0
|
||||
? ['past event ●━━━', 'NOW ★ (result)']
|
||||
: tense == 1
|
||||
? ['finished ●', 'NOW ★']
|
||||
: tense == 2
|
||||
? ['evidence', 'NOW deduction']
|
||||
: ['antecedent', 'relative clause'];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
final x = i == 0 ? s.width * 0.25 : nowX;
|
||||
c.drawCircle(
|
||||
Offset(x, y),
|
||||
9,
|
||||
Paint()
|
||||
..color =
|
||||
i == 0 ? const Color(0xFF00F5D4) : const Color(0xFFFF375F));
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: labels[i],
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 11)),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
tp.paint(c,
|
||||
Offset((x - tp.width / 2).clamp(4, s.width - tp.width - 4), y + 16));
|
||||
}
|
||||
if (tense == 0) {
|
||||
// bridge from past to now
|
||||
c.drawLine(
|
||||
Offset(s.width * 0.25, y - 16),
|
||||
Offset(nowX, y - 16),
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..strokeWidth = 2.5);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _TimelinePainter o) => o.tense != tense;
|
||||
}
|
||||
@@ -0,0 +1,736 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// FINANCIAL LITERACY — GRADE 10 (الثقافة المالية)
|
||||
/// 1. FinanceBudgetLabView — موازنة شخصية + طوارئ + فائدة مركبة ROI
|
||||
/// 2. FinanceFeasibilityLabView — جدوى مشروع + تعادل + تكلفة الفرصة
|
||||
/// All figures are EDUCATIONAL SIMULATIONS (clearly labelled) — never real
|
||||
/// income, profit promises, or platform monetization figures (per AGENTS.md).
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) PERSONAL BUDGET + COMPOUND ROI
|
||||
// ---------------------------------------------------------------------------
|
||||
class FinanceBudgetLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const FinanceBudgetLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<FinanceBudgetLabView> createState() => _FinanceBudgetLabViewState();
|
||||
}
|
||||
|
||||
class _FinanceBudgetLabViewState extends State<FinanceBudgetLabView> {
|
||||
double income = 500;
|
||||
double fixed = 220;
|
||||
double variable = 150;
|
||||
double saveRate = 0.15;
|
||||
double roi = 0.06;
|
||||
int years = 5;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final saving = income * saveRate;
|
||||
final balance = income - fixed - variable - saving;
|
||||
final future = saving * 12 * ((math.pow(1 + roi, years) - 1) / roi);
|
||||
final healthy = balance >= 0 && saveRate >= 0.1;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'الموازنة الشخصية والاستثمار',
|
||||
subtitleAr: 'دخل • ثابت ومتغير • طوارئ • فائدة مركبة — محاكاة تعليمية',
|
||||
identity: kFinanceBudgetToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'صندوق الطوارئ الموصى به للأسرة يغطي مصاريف …',
|
||||
checkpointOptions: const [
|
||||
'3–6 أشهر',
|
||||
'أسبوع واحد',
|
||||
'10 سنوات',
|
||||
'لا حاجة له'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('المتبقي: ${balance.toStringAsFixed(0)} د',
|
||||
color: balance >= 0
|
||||
? const Color(0xFF30D158)
|
||||
: const Color(0xFFFF375F)),
|
||||
LabPill('ادخار شهري: ${saving.toStringAsFixed(0)} د'),
|
||||
LabPill(
|
||||
'قيمة الاستثمار بعد $years سنوات ≈ ${future.toStringAsFixed(0)} د',
|
||||
color: const Color(0xFFFFD166)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _BudgetPainter(
|
||||
income: income,
|
||||
fixed: fixed,
|
||||
variable: variable,
|
||||
saving: saving,
|
||||
balance: balance),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'الدخل الشهري (دينار)',
|
||||
value: income,
|
||||
min: 200,
|
||||
max: 2000,
|
||||
display: '${income.toInt()} د',
|
||||
onChanged: (v) => setState(() => income = v)),
|
||||
LabSlider(
|
||||
label: 'مصاريف ثابتة (سكن/مواصلات)',
|
||||
value: fixed,
|
||||
min: 0,
|
||||
max: 1200,
|
||||
display: '${fixed.toInt()} د',
|
||||
accent: const Color(0xFFFF375F),
|
||||
onChanged: (v) => setState(() => fixed = v)),
|
||||
LabSlider(
|
||||
label: 'مصاريف متغيرة (طعام/ترفيه)',
|
||||
value: variable,
|
||||
min: 0,
|
||||
max: 1000,
|
||||
display: '${variable.toInt()} د',
|
||||
accent: const Color(0xFFFF9F0A),
|
||||
onChanged: (v) => setState(() => variable = v)),
|
||||
LabSlider(
|
||||
label: 'نسبة الادخار',
|
||||
value: saveRate,
|
||||
min: 0,
|
||||
max: 0.4,
|
||||
display: '${(saveRate * 100).toInt()}%',
|
||||
accent: const Color(0xFF30D158),
|
||||
onChanged: (v) => setState(() => saveRate = v)),
|
||||
LabSlider(
|
||||
label: 'عائد سنوي متوقع (تعليمي)',
|
||||
value: roi,
|
||||
min: 0,
|
||||
max: 0.15,
|
||||
display: '${(roi * 100).toStringAsFixed(1)}%',
|
||||
accent: const Color(0xFF0071E3),
|
||||
onChanged: (v) => setState(() => roi = v)),
|
||||
LabSlider(
|
||||
label: 'أفق الاستثمار (سنوات)',
|
||||
value: years.toDouble(),
|
||||
min: 1,
|
||||
max: 15,
|
||||
display: '$years',
|
||||
accent: Colors.white70,
|
||||
onChanged: (v) => setState(() => years = v.toInt())),
|
||||
const SizedBox(height: 6),
|
||||
LabFormulaCard(
|
||||
title: healthy ? 'موازنة سليمة ✓' : 'عجز — راجع بنودك ⚠️',
|
||||
body:
|
||||
'القاعدة: الدخل − (ثابت + متغير + ادخار) = ${balance.toStringAsFixed(0)} د\nصندوق الطوارئ المستهدف: 3–6 أشهر من المصاريف.\nالقيمة المستقبلية للادخار الشهري: FV = P·[((1+r)ⁿ−1)/r] ≈ ${future.toStringAsFixed(0)} د',
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'محاكاة صفية بأرقام افتراضية — ليست استشارة مالية ولا وعداً بعوائد.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BudgetPainter extends CustomPainter {
|
||||
final double income, fixed, variable, saving, balance;
|
||||
_BudgetPainter(
|
||||
{required this.income,
|
||||
required this.fixed,
|
||||
required this.variable,
|
||||
required this.saving,
|
||||
required this.balance});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070F1C));
|
||||
final parts = [
|
||||
{'v': fixed, 'c': const Color(0xFFFF375F), 't': 'ثابت'},
|
||||
{'v': variable, 'c': const Color(0xFFFF9F0A), 't': 'متغير'},
|
||||
{'v': saving, 'c': const Color(0xFF30D158), 't': 'ادخار'},
|
||||
{'v': math.max(0, balance), 'c': const Color(0xFF00F5D4), 't': 'فائض'},
|
||||
];
|
||||
final total = parts.fold(0.0, (a, e) => a + (e['v'] as double));
|
||||
final cx = s.width / 2, cy = s.height * 0.42;
|
||||
final r = math.min(s.width, s.height) * 0.30;
|
||||
double start = -math.pi / 2;
|
||||
for (final p in parts) {
|
||||
final frac = total <= 0 ? 0.0 : (p['v'] as double) / total;
|
||||
if (frac <= 0) continue;
|
||||
c.drawArc(Rect.fromCircle(center: Offset(cx, cy), radius: r), start,
|
||||
frac * math.pi * 2, true, Paint()..color = p['c'] as Color);
|
||||
start += frac * math.pi * 2;
|
||||
}
|
||||
c.drawCircle(
|
||||
Offset(cx, cy), r * 0.55, Paint()..color = const Color(0xFF070F1C));
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${income.toInt()}\nدخل',
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 15, fontWeight: FontWeight.w900)),
|
||||
textDirection: TextDirection.rtl,
|
||||
textAlign: TextAlign.center,
|
||||
)..layout();
|
||||
tp.paint(c, Offset(cx - tp.width / 2, cy - tp.height / 2));
|
||||
// bars
|
||||
double by = s.height * 0.42 + r + 18;
|
||||
for (final p in parts) {
|
||||
final frac = total <= 0 ? 0.0 : (p['v'] as double) / total;
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(20, by, 20 + (s.width - 40) * frac, by + 14,
|
||||
const Radius.circular(7)),
|
||||
Paint()..color = p['c'] as Color);
|
||||
final lt = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${p['t']} ${(frac * 100).toStringAsFixed(0)}%',
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 10)),
|
||||
textDirection: TextDirection.rtl)
|
||||
..layout();
|
||||
lt.paint(c, Offset(s.width - lt.width - 20, by - 1));
|
||||
by += 22;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _BudgetPainter o) =>
|
||||
o.income != income ||
|
||||
o.fixed != fixed ||
|
||||
o.variable != variable ||
|
||||
o.saving != saving;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) FEASIBILITY + BREAK-EVEN + OPPORTUNITY COST
|
||||
// ---------------------------------------------------------------------------
|
||||
class FinanceFeasibilityLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const FinanceFeasibilityLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<FinanceFeasibilityLabView> createState() =>
|
||||
_FinanceFeasibilityLabViewState();
|
||||
}
|
||||
|
||||
class _FinanceFeasibilityLabViewState extends State<FinanceFeasibilityLabView> {
|
||||
double fixedCost = 3000;
|
||||
double price = 8;
|
||||
double varCost = 4.5;
|
||||
double demand = 900;
|
||||
double altReturn = 400; // opportunity: alternative project profit
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final margin = price - varCost;
|
||||
final beUnits = margin > 0 ? fixedCost / margin : double.infinity;
|
||||
final profit = margin * demand - fixedCost;
|
||||
final vsAlt = profit - altReturn;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'دراسة الجدوى وتكلفة الفرصة البديلة',
|
||||
subtitleAr: 'التعادل: الكمية × هامش الربح = التكاليف الثابتة',
|
||||
identity: kFinanceFeasibilityLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'نقطة التعادل (وحدات) = …',
|
||||
checkpointOptions: const [
|
||||
'التكاليف الثابتة ÷ هامش المساهمة',
|
||||
'السعر × الكمية',
|
||||
'التكاليف المتغيرة فقط',
|
||||
'الربح ÷ السعر'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(
|
||||
beUnits.isInfinite
|
||||
? 'لا تعادل (هامش سالب)'
|
||||
: 'التعادل: ${beUnits.toStringAsFixed(0)} وحدة',
|
||||
color: const Color(0xFFFFD166)),
|
||||
LabPill('الربح عند الطلب: ${profit.toStringAsFixed(0)} د',
|
||||
color: profit >= 0
|
||||
? const Color(0xFF30D158)
|
||||
: const Color(0xFFFF375F)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _BreakEvenPainter(
|
||||
fixed: fixedCost, price: price, varCost: varCost, demand: demand),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'تكاليف ثابتة (إيجار/معدات)',
|
||||
value: fixedCost,
|
||||
min: 500,
|
||||
max: 10000,
|
||||
display: '${fixedCost.toInt()} د',
|
||||
onChanged: (v) => setState(() => fixedCost = v)),
|
||||
LabSlider(
|
||||
label: 'سعر البيع للوحدة',
|
||||
value: price,
|
||||
min: 2,
|
||||
max: 25,
|
||||
display: '${price.toStringAsFixed(1)} د',
|
||||
accent: const Color(0xFF30D158),
|
||||
onChanged: (v) => setState(() => price = v)),
|
||||
LabSlider(
|
||||
label: 'تكلفة متغيرة للوحدة',
|
||||
value: varCost,
|
||||
min: 1,
|
||||
max: 20,
|
||||
display: '${varCost.toStringAsFixed(1)} د',
|
||||
accent: const Color(0xFFFF375F),
|
||||
onChanged: (v) => setState(() => varCost = v)),
|
||||
LabSlider(
|
||||
label: 'الطلب المتوقع (وحدة/شهر)',
|
||||
value: demand,
|
||||
min: 100,
|
||||
max: 2500,
|
||||
display: '${demand.toInt()}',
|
||||
accent: const Color(0xFF0071E3),
|
||||
onChanged: (v) => setState(() => demand = v)),
|
||||
LabSlider(
|
||||
label: 'ربح البديل الأفضل (تكلفة الفرصة)',
|
||||
value: altReturn,
|
||||
min: 0,
|
||||
max: 3000,
|
||||
display: '${altReturn.toInt()} د',
|
||||
accent: const Color(0xFFBF5AF2),
|
||||
onChanged: (v) => setState(() => altReturn = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabFormulaCard(
|
||||
title: 'هامش المساهمة = ${margin.toStringAsFixed(2)} د/وحدة',
|
||||
body:
|
||||
'التعادل = ${fixedCost.toInt()} ÷ ${margin.toStringAsFixed(2)} = ${beUnits.isInfinite ? '∞' : beUnits.toStringAsFixed(0)} وحدة\nالربح = (${margin.toStringAsFixed(2)} × ${demand.toInt()}) − ${fixedCost.toInt()} = ${profit.toStringAsFixed(0)} د\nمقابل البديل: ${vsAlt.toStringAsFixed(0)} د ${vsAlt >= 0 ? '(هذا المشروع أفضل)' : '(البديل أفضل — تكلفة فرصة موجبة)'}',
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'أرقام افتراضية للتدريب الصفي — قرار الاستثمار الحقيقي يحتاج دراسة سوق وتمويل.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BreakEvenPainter extends CustomPainter {
|
||||
final double fixed, price, varCost, demand;
|
||||
_BreakEvenPainter(
|
||||
{required this.fixed,
|
||||
required this.price,
|
||||
required this.varCost,
|
||||
required this.demand});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070F1C));
|
||||
final L = 34.0, R = s.width - 16, T = 20.0, B = s.height - 30;
|
||||
c.drawLine(Offset(L, T), Offset(L, B), Paint()..color = Colors.white38);
|
||||
c.drawLine(Offset(L, B), Offset(R, B), Paint()..color = Colors.white38);
|
||||
final maxQ = math.max(demand * 1.3, 1200).toDouble();
|
||||
final maxM =
|
||||
math.max(price * maxQ, fixed + varCost * maxQ).toDouble() * 1.1;
|
||||
Offset map(double q, double m) =>
|
||||
Offset(L + q / maxQ * (R - L), B - m / maxM * (B - T));
|
||||
// total cost line
|
||||
c.drawLine(
|
||||
map(0, fixed),
|
||||
map(maxQ, fixed + varCost * maxQ),
|
||||
Paint()
|
||||
..color = const Color(0xFFFF375F)
|
||||
..strokeWidth = 2.4);
|
||||
// revenue line
|
||||
c.drawLine(
|
||||
map(0, 0),
|
||||
map(maxQ, price * maxQ),
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..strokeWidth = 2.4);
|
||||
// break-even dot
|
||||
final margin = price - varCost;
|
||||
if (margin > 0) {
|
||||
final be = fixed / margin;
|
||||
final p = map(be, price * be);
|
||||
c.drawCircle(p, 6, Paint()..color = const Color(0xFFFFD166));
|
||||
c.drawCircle(
|
||||
p,
|
||||
6,
|
||||
Paint()
|
||||
..color = Colors.black
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.4);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: 'التعادل ${be.toStringAsFixed(0)}',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFFD166),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl)
|
||||
..layout();
|
||||
tp.paint(
|
||||
c,
|
||||
Offset((p.dx - tp.width / 2).clamp(4, s.width - tp.width - 4),
|
||||
(p.dy - 26).clamp(4, s.height - 20)));
|
||||
// demand marker
|
||||
final dp = map(demand, price * demand);
|
||||
c.drawLine(
|
||||
Offset(dp.dx, B),
|
||||
dp,
|
||||
Paint()
|
||||
..color = Colors.white38
|
||||
..strokeWidth = 1.2);
|
||||
}
|
||||
final leg = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: '— إيراد — تكلفة كلية',
|
||||
style: TextStyle(color: Colors.white60, fontSize: 10.5)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
leg.paint(c, Offset(s.width - leg.width - 14, 6));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _BreakEvenPainter o) =>
|
||||
o.fixed != fixed ||
|
||||
o.price != price ||
|
||||
o.varCost != varCost ||
|
||||
o.demand != demand;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3) PROJECT MANAGEMENT & ENTREPRENEURSHIP (المشروع وإدارته)
|
||||
// ---------------------------------------------------------------------------
|
||||
class FinanceProjectManagementLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const FinanceProjectManagementLabView(
|
||||
{super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<FinanceProjectManagementLabView> createState() =>
|
||||
_FinanceProjectManagementLabViewState();
|
||||
}
|
||||
|
||||
class _FinanceProjectManagementLabViewState
|
||||
extends State<FinanceProjectManagementLabView> {
|
||||
int currentPhase = 0; // 0: Scope, 1: Team, 2: Budget, 3: Quality/Review
|
||||
double allocatedBudget = 150.0; // 50 to 500 JOD
|
||||
double teamSize = 5.0; // 3 to 10 members
|
||||
double daysPlanned = 14.0; // 5 to 30 days
|
||||
bool hasContingencyReserve = true;
|
||||
|
||||
double get projectSuccessScore {
|
||||
double score = 50.0;
|
||||
// Adequate budget
|
||||
if (allocatedBudget >= 100 && allocatedBudget <= 350) score += 20.0;
|
||||
// Team size
|
||||
if (teamSize >= 4 && teamSize <= 7) score += 15.0;
|
||||
// Contingency reserve
|
||||
if (hasContingencyReserve) score += 15.0;
|
||||
return score.clamp(20.0, 100.0);
|
||||
}
|
||||
|
||||
static const phases = [
|
||||
{
|
||||
'title': 'الفكرة وتحديد النطاق',
|
||||
'desc': 'تحديد أهداف المشروع، المستفيدين، ومخرجات العمل بوضوح.'
|
||||
},
|
||||
{
|
||||
'title': 'توزيع الأدوار والمهام',
|
||||
'desc':
|
||||
'تحديد مسؤوليات مدير المشروع، الفريق المالي، المنسق، ومسؤول الإعلام.'
|
||||
},
|
||||
{
|
||||
'title': 'الميزانية والموارد',
|
||||
'desc': 'تخصيص بنود النفقات وتأمين المواد مع الاحتفاظ باحتياطي طوارئ.'
|
||||
},
|
||||
{
|
||||
'title': 'إدارة الوقت ومتابعة التنفيذ',
|
||||
'desc':
|
||||
'متابعة مراحل الإنجاز وضمان تسليم المشروع بالجودة والوقت المطلوبين.'
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final score = projectSuccessScore;
|
||||
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'مختبر المشروع وإدارته',
|
||||
subtitleAr:
|
||||
'محاكاة تفاعلية لتخطيط المشروع، توزيع المهام، الميزانية، ومؤشر الجودة',
|
||||
identity: kFinanceProjectManagementLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'الشخص المسؤول عن التخطيط للمشروع، وتنظيم مهامه، وتوزيع الأدوار على الفريق، ومتابعة التنفيذ في الوقت المحدد هو …',
|
||||
checkpointOptions: const [
|
||||
'مدير المشروع (Project Manager)',
|
||||
'المستهلك النهائي',
|
||||
'المستثمر الخارجي',
|
||||
'المنافس في السوق',
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('مؤشر نجاح المشروع: ${score.toInt()}%',
|
||||
color: score >= 80
|
||||
? const Color(0xFF30D158)
|
||||
: const Color(0xFFFFD60A)),
|
||||
LabPill('الميزانية: ${allocatedBudget.toInt()} دينار',
|
||||
color: const Color(0xFF60A5FA)),
|
||||
LabPill('فريق العمل: ${teamSize.toInt()} أعضاء',
|
||||
color: const Color(0xFF00F5D4)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _ProjectCanvasPainter(
|
||||
phase: currentPhase,
|
||||
successScore: score,
|
||||
budget: allocatedBudget,
|
||||
teamSize: teamSize,
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
const Text(
|
||||
'مراحل دورة حياة المشروع:',
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...List.generate(phases.length, (idx) {
|
||||
final p = phases[idx];
|
||||
final isSel = currentPhase == idx;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => currentPhase = idx),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSel
|
||||
? const Color(0xFF30D158).withValues(alpha: 0.15)
|
||||
: const Color(0xFF102035),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isSel ? const Color(0xFF30D158) : Colors.white12,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: isSel ? const Color(0xFF30D158) : Colors.white24,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${idx + 1}',
|
||||
style: TextStyle(
|
||||
color: isSel ? Colors.black : Colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
p['title']!,
|
||||
style: TextStyle(
|
||||
color: isSel ? Colors.white : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: isSel ? FontWeight.w700 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 10),
|
||||
LabSlider(
|
||||
label: 'الميزانية المخصصة للمشروع (دينار)',
|
||||
value: allocatedBudget,
|
||||
min: 50,
|
||||
max: 500,
|
||||
display: '${allocatedBudget.toInt()} د',
|
||||
onChanged: (v) => setState(() => allocatedBudget = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabSlider(
|
||||
label: 'حجم فريق العمل',
|
||||
value: teamSize,
|
||||
min: 3,
|
||||
max: 10,
|
||||
display: '${teamSize.toInt()} طلاب',
|
||||
onChanged: (v) => setState(() => teamSize = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF102035),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('بند احتياطي طوارئ (إدارة المخاطر)',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w600)),
|
||||
CupertinoSwitch(
|
||||
value: hasContingencyReserve,
|
||||
activeColor: const Color(0xFF30D158),
|
||||
onChanged: (v) => setState(() => hasContingencyReserve = v),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF30D158).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF30D158).withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'المرحلة الحالية: ${phases[currentPhase]['title']}',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF30D158),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
phases[currentPhase]['desc']!,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12, height: 1.4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'منهاج وزارة التربية والتعليم الأردنية • الثقافة المالية الصف العاشر • الوحدة الأولى ص 8-11',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProjectCanvasPainter extends CustomPainter {
|
||||
final int phase;
|
||||
final double successScore;
|
||||
final double budget;
|
||||
final double teamSize;
|
||||
|
||||
_ProjectCanvasPainter({
|
||||
required this.phase,
|
||||
required this.successScore,
|
||||
required this.budget,
|
||||
required this.teamSize,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width;
|
||||
final h = size.height;
|
||||
final cx = w * 0.5;
|
||||
final cy = h * 0.45;
|
||||
|
||||
// Dark background
|
||||
final bg = Paint()
|
||||
..shader = const RadialGradient(
|
||||
colors: [Color(0xFF0F1E36), Color(0xFF07111F)],
|
||||
radius: 0.85,
|
||||
).createShader(Rect.fromLTWH(0, 0, w, h));
|
||||
canvas.drawRect(Rect.fromLTWH(0, 0, w, h), bg);
|
||||
|
||||
// Circular Project Success Meter
|
||||
final meterRadius = math.min(w, h) * 0.22;
|
||||
final meterTrack = Paint()
|
||||
..color = Colors.white12
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 10.0;
|
||||
canvas.drawCircle(Offset(cx, cy), meterRadius, meterTrack);
|
||||
|
||||
final meterColor =
|
||||
successScore >= 80 ? const Color(0xFF30D158) : const Color(0xFFFFD60A);
|
||||
final meterActive = Paint()
|
||||
..color = meterColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeWidth = 10.0;
|
||||
|
||||
final sweepAngle = (successScore / 100.0) * 2 * math.pi;
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(center: Offset(cx, cy), radius: meterRadius),
|
||||
-math.pi / 2,
|
||||
sweepAngle,
|
||||
false,
|
||||
meterActive,
|
||||
);
|
||||
|
||||
// Center text: Success Score
|
||||
final tpScore = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${successScore.toInt()}%',
|
||||
style: TextStyle(
|
||||
color: meterColor, fontSize: 24, fontWeight: FontWeight.w900),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tpScore.paint(canvas, Offset(cx - (tpScore.width / 2), cy - 18));
|
||||
|
||||
final tpLabel = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: 'مؤشر كفاءة المشروع',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF94A3B8),
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tpLabel.paint(canvas, Offset(cx - (tpLabel.width / 2), cy + 10));
|
||||
|
||||
// Phase milestone track at bottom
|
||||
final barY = h - 45.0;
|
||||
final trackPaint = Paint()
|
||||
..color = Colors.white24
|
||||
..strokeWidth = 3.0;
|
||||
canvas.drawLine(Offset(w * 0.15, barY), Offset(w * 0.85, barY), trackPaint);
|
||||
|
||||
final activeTrackPaint = Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..strokeWidth = 3.0;
|
||||
final activeX = w * 0.15 + ((w * 0.7) * (phase / 3.0));
|
||||
canvas.drawLine(
|
||||
Offset(w * 0.15, barY), Offset(activeX, barY), activeTrackPaint);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final nodeX = w * 0.15 + ((w * 0.7) * (i / 3.0));
|
||||
final isDone = i <= phase;
|
||||
final nodePaint = Paint()
|
||||
..color = isDone ? const Color(0xFF30D158) : const Color(0xFF475569);
|
||||
canvas.drawCircle(Offset(nodeX, barY), 7, nodePaint);
|
||||
if (i == phase) {
|
||||
final ring = Paint()
|
||||
..color = const Color(0xFF30D158).withValues(alpha: 0.35)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4.0;
|
||||
canvas.drawCircle(Offset(nodeX, barY), 13, ring);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _ProjectCanvasPainter oldDelegate) =>
|
||||
oldDelegate.phase != phase ||
|
||||
oldDelegate.successScore != successScore ||
|
||||
oldDelegate.budget != budget ||
|
||||
oldDelegate.teamSize != teamSize;
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// GEOGRAPHY LABS — GRADE 10 (جغرافيا الصف العاشر)
|
||||
/// Lesson 1: الغلاف الجوي: الطبقات، الحرارة، الضغط، والغازات الدفيئة
|
||||
/// Direct alignment with Jordan Grade 10 Geography Curriculum (Unit 1, Lesson 1)
|
||||
/// ============================================================================
|
||||
|
||||
class GeographyAtmosphereLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const GeographyAtmosphereLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<GeographyAtmosphereLabView> createState() =>
|
||||
_GeographyAtmosphereLabViewState();
|
||||
}
|
||||
|
||||
class _GeographyAtmosphereLabViewState
|
||||
extends State<GeographyAtmosphereLabView> {
|
||||
double altitudeKm = 8.0; // 0 to 90 km
|
||||
double co2Ppm = 415.0; // 280 to 560 ppm
|
||||
bool ozoneLayerActive = true;
|
||||
|
||||
// Layer detection
|
||||
String get currentLayer {
|
||||
if (altitudeKm <= 12) return 'التروبوسفير (Troposphere)';
|
||||
if (altitudeKm <= 50) return 'الستراتوسفير (Stratosphere)';
|
||||
if (altitudeKm <= 85) return 'الميزوسفير (Mesosphere)';
|
||||
return 'الثرموسفير (Thermosphere)';
|
||||
}
|
||||
|
||||
// Atmospheric physics calculation approximation
|
||||
double get currentTempCelsius {
|
||||
double base;
|
||||
if (altitudeKm <= 12) {
|
||||
base = 15.0 - (altitudeKm * 6.5); // lapse rate: -6.5C per km
|
||||
} else if (altitudeKm <= 50) {
|
||||
// Stratosphere: warms up due to Ozone UV absorption (-60C to 0C)
|
||||
base = -63.0 + ((altitudeKm - 12) * 1.65);
|
||||
} else if (altitudeKm <= 85) {
|
||||
// Mesosphere: drops rapidly to -90C
|
||||
base = 0.0 - ((altitudeKm - 50) * 2.5);
|
||||
} else {
|
||||
// Thermosphere: rises rapidly due to solar X-ray absorption
|
||||
base = -87.5 + ((altitudeKm - 85) * 15.0);
|
||||
}
|
||||
// Greenhouse effect at surface levels
|
||||
if (altitudeKm <= 12) {
|
||||
base += ((co2Ppm - 280) / 280) * 3.5;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
double get currentPressureHpa {
|
||||
// Barometric formula approximation: P = 1013.25 * exp(-h / 7.4)
|
||||
return 1013.25 * math.exp(-altitudeKm / 7.4);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final temp = currentTempCelsius;
|
||||
final press = currentPressureHpa;
|
||||
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'مختبر الغلاف الجوي وطبقاته',
|
||||
subtitleAr:
|
||||
'محاكاة تفاعلية للطبقات، درجات الحرارة، الضغط، والغازات الدفيئة',
|
||||
identity: kGeographyAtmosphereLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'طبقة الغلاف الجوي التي تحتوي على طبقة الأوزون وتحمي الأرض من الأشعة فوق البنفسجية الضارة هي …',
|
||||
checkpointOptions: const [
|
||||
'الستراتوسفير (Stratosphere)',
|
||||
'التروبوسفير (Troposphere)',
|
||||
'الميزوسفير (Mesosphere)',
|
||||
'الثرموسفير (Thermosphere)',
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(currentLayer, color: AppColors.saqelCyan),
|
||||
LabPill('الارتفاع: ${altitudeKm.toStringAsFixed(1)} كم',
|
||||
color: const Color(0xFF60A5FA)),
|
||||
LabPill('${temp.toStringAsFixed(1)} °C',
|
||||
color:
|
||||
temp > 0 ? const Color(0xFFFF9F0A) : const Color(0xFF38BDF8)),
|
||||
LabPill('${press.toStringAsFixed(1)} hPa',
|
||||
color: const Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _AtmosphereCanvasPainter(
|
||||
altitudeKm: altitudeKm,
|
||||
co2Ppm: co2Ppm,
|
||||
ozoneActive: ozoneLayerActive,
|
||||
tempCelsius: temp,
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'ارتفاع المسبار (كم)',
|
||||
value: altitudeKm,
|
||||
min: 0,
|
||||
max: 90,
|
||||
display: '${altitudeKm.toStringAsFixed(1)} كم',
|
||||
onChanged: (v) => setState(() => altitudeKm = v),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabSlider(
|
||||
label: 'تركيز غازات الدفيئة (CO₂ ppm)',
|
||||
value: co2Ppm,
|
||||
min: 280,
|
||||
max: 560,
|
||||
display: '${co2Ppm.toInt()} ppm',
|
||||
onChanged: (v) => setState(() => co2Ppm = v),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: kSaqelGlass,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border:
|
||||
Border.all(color: AppColors.saqelCyan.withValues(alpha: 0.25)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'درع طبقة الأوزون (O₃)',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 13),
|
||||
),
|
||||
CupertinoSwitch(
|
||||
value: ozoneLayerActive,
|
||||
activeColor: AppColors.saqelCyan,
|
||||
onChanged: (v) => setState(() => ozoneLayerActive = v),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
ozoneLayerActive
|
||||
? 'الأوزون يمتص الأشعة فوق البنفسجية (UV) في الستراتوسفير ويرفع حرارتها.'
|
||||
: 'تحذير: نضوب الأوزون يسمح بنفاذ الأشعة الضارة مباشرة نحو سطح الأرض.',
|
||||
style: TextStyle(
|
||||
color: ozoneLayerActive
|
||||
? const Color(0xFF94A3B8)
|
||||
: const Color(0xFFFF453A),
|
||||
fontSize: 11.5,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_LayerCardInfo(layer: currentLayer, altitude: altitudeKm),
|
||||
],
|
||||
footerNote:
|
||||
'منهاج وزارة التربية والتعليم الأردنية • الجغرافيا الصف العاشر • الوحدة الأولى ص 8-13',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LayerCardInfo extends StatelessWidget {
|
||||
final String layer;
|
||||
final double altitude;
|
||||
const _LayerCardInfo({required this.layer, required this.altitude});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String desc = '';
|
||||
Color color = AppColors.saqelCyan;
|
||||
if (altitude <= 12) {
|
||||
desc =
|
||||
'التروبوسفير: تحوي 75% من كتلة الغلاف الجوي وجميع تقلبات الطقس والسحب؛ تنخفض الحرارة بالارتفاع بمعدل 6.5°C لكل 1 كم.';
|
||||
color = const Color(0xFF30D158);
|
||||
} else if (altitude <= 50) {
|
||||
desc =
|
||||
'الستراتوسفير: طبقة مستقرة خالية من السحب، مناسبة لطيران الطائرات، تحوي غاز الأوزون الماص للأشعة فوق البنفسجية الضارة.';
|
||||
color = const Color(0xFF00F5D4);
|
||||
} else if (altitude <= 85) {
|
||||
desc =
|
||||
'الميزوسفير: أبرد طبقات الغلاف الجوي (تصل لـ -90°C)، تحترق فيها النيازك والشهب نتيجة الاحتكاك بجزيئات الهواء.';
|
||||
color = const Color(0xFF60A5FA);
|
||||
} else {
|
||||
desc =
|
||||
'الثرموسفير: ترتفع حرارتها لآلاف الدرجات بسبب امتصاص الأشعة السينية والشمسية، وتحدث فيها ظاهرة الشفق القطبي (الأورورا).';
|
||||
color = const Color(0xFFFF9F0A);
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: color.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.info_circle_fill, color: color, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'خصائص الطبقة الحالية',
|
||||
style: TextStyle(
|
||||
color: color, fontWeight: FontWeight.w800, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
desc,
|
||||
style:
|
||||
const TextStyle(color: Colors.white, fontSize: 12, height: 1.4),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AtmosphereCanvasPainter extends CustomPainter {
|
||||
final double altitudeKm;
|
||||
final double co2Ppm;
|
||||
final bool ozoneActive;
|
||||
final double tempCelsius;
|
||||
|
||||
_AtmosphereCanvasPainter({
|
||||
required this.altitudeKm,
|
||||
required this.co2Ppm,
|
||||
required this.ozoneActive,
|
||||
required this.tempCelsius,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width;
|
||||
final h = size.height;
|
||||
|
||||
// Background gradient: dark space to blue troposphere
|
||||
final bgPaint = Paint()
|
||||
..shader = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0xFF020617), // Deep outer space
|
||||
Color(0xFF0F172A), // Thermosphere
|
||||
Color(0xFF1E293B), // Mesosphere
|
||||
Color(0xFF0C4A6E), // Stratosphere
|
||||
Color(0xFF0284C7), // Troposphere
|
||||
Color(0xFF38BDF8), // Surface air
|
||||
],
|
||||
stops: [0.0, 0.2, 0.45, 0.7, 0.9, 1.0],
|
||||
).createShader(Rect.fromLTWH(0, 0, w, h));
|
||||
canvas.drawRect(Rect.fromLTWH(0, 0, w, h), bgPaint);
|
||||
|
||||
// Layer boundaries
|
||||
final pTropo = h * (1.0 - (12.0 / 90.0));
|
||||
final pStrato = h * (1.0 - (50.0 / 90.0));
|
||||
final pMeso = h * (1.0 - (85.0 / 90.0));
|
||||
|
||||
final dashPaint = Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.25)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0;
|
||||
|
||||
canvas.drawLine(Offset(0, pTropo), Offset(w, pTropo), dashPaint);
|
||||
canvas.drawLine(Offset(0, pStrato), Offset(w, pStrato), dashPaint);
|
||||
canvas.drawLine(Offset(0, pMeso), Offset(w, pMeso), dashPaint);
|
||||
|
||||
// Ozone layer band in Stratosphere (approx 20 to 35 km)
|
||||
final pOzoneBottom = h * (1.0 - (20.0 / 90.0));
|
||||
final pOzoneTop = h * (1.0 - (35.0 / 90.0));
|
||||
if (ozoneActive) {
|
||||
final ozonePaint = Paint()
|
||||
..color = const Color(0xFF00F5D4).withValues(alpha: 0.18)
|
||||
..style = PaintingStyle.fill;
|
||||
canvas.drawRect(Rect.fromLTRB(0, pOzoneTop, w, pOzoneBottom), ozonePaint);
|
||||
}
|
||||
|
||||
// Surface ground
|
||||
final groundPaint = Paint()..color = const Color(0xFF166534);
|
||||
canvas.drawRect(Rect.fromLTWH(0, h - 14, w, 14), groundPaint);
|
||||
|
||||
// Clouds in troposphere
|
||||
final cloudPaint = Paint()..color = Colors.white.withValues(alpha: 0.55);
|
||||
canvas.drawCircle(Offset(w * 0.25, h - 35), 18, cloudPaint);
|
||||
canvas.drawCircle(Offset(w * 0.32, h - 42), 24, cloudPaint);
|
||||
canvas.drawCircle(Offset(w * 0.38, h - 35), 18, cloudPaint);
|
||||
|
||||
// Meteors in mesosphere
|
||||
final meteorPaint = Paint()
|
||||
..color = const Color(0xFFFFD60A).withValues(alpha: 0.8)
|
||||
..strokeWidth = 2.0;
|
||||
canvas.drawLine(Offset(w * 0.75, pMeso + 15), Offset(w * 0.68, pMeso + 45),
|
||||
meteorPaint);
|
||||
|
||||
// Probe position indicator
|
||||
final probeY = h * (1.0 - (altitudeKm / 90.0)).clamp(0.05, 0.98);
|
||||
final probeX = w * 0.5;
|
||||
|
||||
// Glowing scan line
|
||||
final linePaint = Paint()
|
||||
..color = AppColors.saqelCyan.withValues(alpha: 0.7)
|
||||
..strokeWidth = 1.5;
|
||||
canvas.drawLine(Offset(0, probeY), Offset(w, probeY), linePaint);
|
||||
|
||||
// Probe beacon
|
||||
final probePaint = Paint()..color = AppColors.saqelCyan;
|
||||
canvas.drawCircle(Offset(probeX, probeY), 8, probePaint);
|
||||
final ringPaint = Paint()
|
||||
..color = AppColors.saqelCyan.withValues(alpha: 0.3)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.0;
|
||||
canvas.drawCircle(Offset(probeX, probeY), 15, ringPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _AtmosphereCanvasPainter oldDelegate) =>
|
||||
oldDelegate.altitudeKm != altitudeKm ||
|
||||
oldDelegate.co2Ppm != co2Ppm ||
|
||||
oldDelegate.ozoneActive != ozoneActive ||
|
||||
oldDelegate.tempCelsius != tempCelsius;
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// HISTORY OF JORDAN + DIGITAL SKILLS — GRADE 10
|
||||
/// 1. HistoryJordanChronologyLabView — خط زمني تفاعلي (استقلال/دستور/ثورة)
|
||||
/// 2. DigitalFlowchartLabView — خوارزميات ومخططات انسيابية مع تتبع حالة
|
||||
/// 3. DigitalSortingLabView — مصفوفات وفرز (Bubble + Quick) على Canvas
|
||||
/// History content: core national milestones, honestly labelled as a
|
||||
/// classroom overview (not a legal/treaty archive).
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) HISTORY CHRONOLOGY
|
||||
// ---------------------------------------------------------------------------
|
||||
class HistoryJordanChronologyLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const HistoryJordanChronologyLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<HistoryJordanChronologyLabView> createState() =>
|
||||
_HistoryJordanChronologyLabViewState();
|
||||
}
|
||||
|
||||
class _HistoryJordanChronologyLabViewState
|
||||
extends State<HistoryJordanChronologyLabView> {
|
||||
int era = 2;
|
||||
double progress = 1.0;
|
||||
|
||||
static const milestones = [
|
||||
{
|
||||
'y': '1916',
|
||||
't': 'الثورة العربية الكبرى',
|
||||
'd':
|
||||
'انطلاق الثورة بقيادة الشريف الحسين بن علي ضد الحكم العثماني؛ تشكل الوعي العربي الحديث وخط سير الجيوش نحو الشام.',
|
||||
'c': Color(0xFFFF9F0A)
|
||||
},
|
||||
{
|
||||
'y': '1921',
|
||||
't': 'تأسيس إمارة شرق الأردن',
|
||||
'd':
|
||||
'وصول الأمير عبد الله الأول إلى عمان (2 آذار) وتشكيل أول حكومة (11 نيسان) برئاسة رشيد طليع.',
|
||||
'c': Color(0xFF8B5CF6)
|
||||
},
|
||||
{
|
||||
'y': '1928',
|
||||
't': 'القانون الأساسي والمعاهدة',
|
||||
'd':
|
||||
'أول وثيقة دستورية للإمارة (القانون الأساسي) وتنظيم العلاقة مع بريطانيا والمؤتمر الوطني الأول.',
|
||||
'c': Color(0xFF60A5FA)
|
||||
},
|
||||
{
|
||||
'y': '1946',
|
||||
't': 'الاستقلال 25 أيار',
|
||||
'd':
|
||||
'إعلان الاستقلال التام ومبايعة الملك المؤسس ملكاً دستورياً وتحول الاسم إلى المملكة الأردنية الهاشمية.',
|
||||
'c': Color(0xFF30D158)
|
||||
},
|
||||
{
|
||||
'y': '1952',
|
||||
't': 'دستور 1952',
|
||||
'd':
|
||||
'صدور الدستور الأردني الحالي في عهد الملك طلال؛ ترسيخ الملكية الدستورية والحقوق والحريات.',
|
||||
'c': Color(0xFF00F5D4)
|
||||
},
|
||||
{
|
||||
'y': '1994',
|
||||
't': 'معاهدة وادي عربة',
|
||||
'd':
|
||||
'معاهدة السلام الأردنية–الإسرائيلية؛ ملفات الحدود والمياه والأمن — تُدرس من الكتاب المقرر حصراً.',
|
||||
'c': Color(0xFF0071E3)
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final m = milestones[era];
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'التسلسل الزمني لتاريخ الأردن',
|
||||
subtitleAr: 'اسحب المؤشر عبر المحطات — خريطة طريق + بطاقة الحدث',
|
||||
identity: kHistoryJordanChronologyToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'اليوم الوطني لاستقلال المملكة الأردنية الهاشمية هو …',
|
||||
checkpointOptions: const [
|
||||
'25 أيار 1946',
|
||||
'2 آذار 1921',
|
||||
'16 نيسان 1928',
|
||||
'10 حزيران 1916'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('${m['y']} • ${m['t']}', color: m['c'] as Color),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _ChronoPainter(index: era, progress: progress),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'المحطة التاريخية',
|
||||
value: era.toDouble(),
|
||||
min: 0,
|
||||
max: 5,
|
||||
display: '${milestones[era]['y']}',
|
||||
onChanged: (v) => setState(() => era = v.round())),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
height: 40,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: milestones.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 6),
|
||||
itemBuilder: (ctx, i) {
|
||||
final sel = i == era;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => era = i),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: sel
|
||||
? (milestones[i]['c'] as Color)
|
||||
: Colors.white.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(milestones[i]['y'] as String,
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white70,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 12)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: '${m['y']} — ${m['t']}',
|
||||
body: m['d'] as String,
|
||||
accent: m['c'] as Color,
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'عرض صفي موجز؛ التفاصيل الدقيقة والوثائق من الكتاب المدرسي المقرر.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChronoPainter extends CustomPainter {
|
||||
final int index;
|
||||
final double progress;
|
||||
_ChronoPainter({required this.index, required this.progress});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF0A1220));
|
||||
// simplified Jordan outline (schematic polygon, clearly stylised)
|
||||
final mapRect = Rect.fromLTWH(18, 18, s.width * 0.40, s.height - 90);
|
||||
c.drawRRect(RRect.fromRectAndRadius(mapRect, const Radius.circular(14)),
|
||||
Paint()..color = const Color(0xFF0F2036));
|
||||
final poly = [
|
||||
const Offset(0.55, 0.08),
|
||||
const Offset(0.80, 0.18),
|
||||
const Offset(0.86, 0.42),
|
||||
const Offset(0.70, 0.62),
|
||||
const Offset(0.72, 0.88),
|
||||
const Offset(0.40, 0.92),
|
||||
const Offset(0.22, 0.66),
|
||||
const Offset(0.30, 0.34),
|
||||
]
|
||||
.map((e) => Offset(mapRect.left + e.dx * mapRect.width,
|
||||
mapRect.top + e.dy * mapRect.height))
|
||||
.toList();
|
||||
c.drawPath(Path()..addPolygon(poly, true),
|
||||
Paint()..color = const Color(0xFF1E3A5F));
|
||||
// revolt route dashed
|
||||
final route = Path()
|
||||
..moveTo(mapRect.left + mapRect.width * 0.72,
|
||||
mapRect.top + mapRect.height * 0.88)
|
||||
..quadraticBezierTo(
|
||||
mapRect.left + mapRect.width * 0.5,
|
||||
mapRect.top + mapRect.height * 0.5,
|
||||
mapRect.left + mapRect.width * 0.6,
|
||||
mapRect.top + mapRect.height * 0.15);
|
||||
_dashed(c, route, const Color(0xFFFF9F0A));
|
||||
final dotT = (index / 5).clamp(0.0, 1.0);
|
||||
final dotPos = _pointOn(route, dotT, mapRect);
|
||||
c.drawCircle(dotPos, 7, Paint()..color = const Color(0xFFFF375F));
|
||||
c.drawCircle(
|
||||
dotPos,
|
||||
7,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.6);
|
||||
// timeline rail (right side)
|
||||
final rx = s.width * 0.52, ry0 = 30.0, ry1 = s.height - 40;
|
||||
c.drawLine(
|
||||
Offset(rx, ry0),
|
||||
Offset(rx, ry1),
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..strokeWidth = 3);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
final y = ry0 + i * ((ry1 - ry0) / 5);
|
||||
final sel = i == index;
|
||||
c.drawCircle(Offset(rx, y), sel ? 11 : 7,
|
||||
Paint()..color = sel ? const Color(0xFF00F5D4) : Colors.white24);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: ['1916', '1921', '1928', '1946', '1952', '1994'][i],
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.white : Colors.white54,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
tp.paint(c, Offset(rx + 18, y - 8));
|
||||
}
|
||||
}
|
||||
|
||||
void _dashed(Canvas c, Path p, Color col) {
|
||||
final metrics = p.computeMetrics().toList();
|
||||
for (final m in metrics) {
|
||||
double d = 0;
|
||||
while (d < m.length) {
|
||||
c.drawPath(
|
||||
m.extractPath(d, d + 8),
|
||||
Paint()
|
||||
..color = col
|
||||
..strokeWidth = 2.4);
|
||||
d += 14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Offset _pointOn(Path route, double t, Rect mapRect) {
|
||||
// approximate: lerp along bounding diagonal of route (classroom schematic)
|
||||
return Offset(
|
||||
mapRect.left + mapRect.width * (0.72 - 0.12 * t),
|
||||
mapRect.top + mapRect.height * (0.88 - 0.73 * t),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _ChronoPainter o) => o.index != index;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) FLOWCHART / ALGORITHM SIMULATOR
|
||||
// ---------------------------------------------------------------------------
|
||||
class DigitalFlowchartLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const DigitalFlowchartLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<DigitalFlowchartLabView> createState() =>
|
||||
_DigitalFlowchartLabViewState();
|
||||
}
|
||||
|
||||
class _DigitalFlowchartLabViewState extends State<DigitalFlowchartLabView> {
|
||||
int program = 0; // 0 sum loop, 1 max-if, 2 array scan
|
||||
double n = 5;
|
||||
int pc = -1; // program counter highlight
|
||||
List<String> trace = [];
|
||||
int result = 0;
|
||||
|
||||
void _run() {
|
||||
setState(() {
|
||||
trace = [];
|
||||
if (program == 0) {
|
||||
int s = 0;
|
||||
for (int i = 1; i <= n.toInt(); i++) {
|
||||
s += i;
|
||||
trace.add('i=$i → sum=$s');
|
||||
}
|
||||
result = s;
|
||||
pc = 2;
|
||||
} else if (program == 1) {
|
||||
final arr = [4, 9, 2, 7, 5];
|
||||
int mx = arr[0];
|
||||
trace.add('max=${arr[0]}');
|
||||
for (final x in arr.skip(1)) {
|
||||
if (x > mx) {
|
||||
mx = x;
|
||||
trace.add('$x > max → max=$mx');
|
||||
} else {
|
||||
trace.add('$x ≤ max ($mx) → تخطٍ');
|
||||
}
|
||||
}
|
||||
result = mx;
|
||||
pc = 1;
|
||||
} else {
|
||||
final arr =
|
||||
List.generate(n.toInt().clamp(3, 8), (i) => (i * 3 + 2) % 9);
|
||||
final evens = arr.where((x) => x % 2 == 0).toList();
|
||||
for (final x in arr) {
|
||||
trace.add('$x ${x % 2 == 0 ? 'زوجي ✓' : 'فردي ✗'}');
|
||||
}
|
||||
result = evens.length;
|
||||
pc = 2;
|
||||
}
|
||||
});
|
||||
saqelTick();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const titles = ['حلقة الجمع 1..N', 'شرط: أكبر عنصر', 'مسح مصفوفة (زوجي)'];
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'محاكي الخوارزميات والمخططات',
|
||||
subtitleAr: 'نفّذ الحلقات والشروط خطوة بخطوة مع تتبع الحالة',
|
||||
identity: kDigitalFlowchartToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'ناتج جمع الأعداد من 1 إلى 5 بحلقة هو …',
|
||||
checkpointOptions: const ['15', '10', '25', '5'],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(titles[program]),
|
||||
if (trace.isNotEmpty)
|
||||
LabPill('الناتج: $result', color: const Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _FlowPainter(program: program, pc: pc),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<int>(
|
||||
labels: const ['جمع', 'أكبر', 'مسح'],
|
||||
values: const [0, 1, 2],
|
||||
current: program,
|
||||
onSelected: (v) => setState(() {
|
||||
program = v;
|
||||
trace = [];
|
||||
pc = -1;
|
||||
}),
|
||||
),
|
||||
if (program != 1)
|
||||
LabSlider(
|
||||
label: program == 0 ? 'قيمة N' : 'طول المصفوفة',
|
||||
value: n,
|
||||
min: 3,
|
||||
max: 8,
|
||||
display: '${n.toInt()}',
|
||||
onChanged: (v) => setState(() => n = v)),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.play_fill, size: 16),
|
||||
label: const Text('تنفيذ خطوة بخطوة ▶',
|
||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 13)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.saqelCyan,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12))),
|
||||
onPressed: _run,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxHeight: 150),
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.45),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.10))),
|
||||
child: trace.isEmpty
|
||||
? const Text('اضغط تنفيذ لترى تتبع الحالة سطراً بسطر…',
|
||||
style: TextStyle(color: Color(0xFF64748B), fontSize: 11.5))
|
||||
: ListView(
|
||||
shrinkWrap: true,
|
||||
children: trace
|
||||
.map((t) => Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Text('› $t',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF00F5D4),
|
||||
fontSize: 11.5,
|
||||
fontFamily: 'monospace')),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'أثر تعليمي لتدريب التفكير الخوارزمي؛ بيئة بايثون الحقيقية في مختبر منفصل.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FlowPainter extends CustomPainter {
|
||||
final int program, pc;
|
||||
_FlowPainter({required this.program, required this.pc});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
final cx = s.width / 2;
|
||||
final labels = program == 0
|
||||
? ['ابدأ', 'sum=0, i=1', 'i ≤ N؟', 'sum+=i, i++', 'اطبع sum']
|
||||
: program == 1
|
||||
? ['ابدأ', 'max=a[0]', 'x > max؟', 'حدّث/تخطَّ', 'اطبع max']
|
||||
: ['ابدأ', 'i=0', 'زوجي؟', 'عُدَّه', 'التالي'];
|
||||
double y = 26;
|
||||
for (int i = 0; i < labels.length; i++) {
|
||||
final sel = i == pc || (pc == -1 && i == 0);
|
||||
final isDecision = i == 2;
|
||||
final rect = RRect.fromLTRBR(
|
||||
cx - 110, y, cx + 110, y + 40, Radius.circular(isDecision ? 20 : 12));
|
||||
c.drawRRect(
|
||||
rect,
|
||||
Paint()
|
||||
..color = sel
|
||||
? const Color(0xFF00F5D4).withValues(alpha: 0.30)
|
||||
: Colors.white.withValues(alpha: 0.05));
|
||||
c.drawRRect(
|
||||
rect,
|
||||
Paint()
|
||||
..color = sel ? const Color(0xFF00F5D4) : Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = sel ? 2.2 : 1.2);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: labels[i],
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.white : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout(maxWidth: 200);
|
||||
tp.paint(c, Offset(cx - tp.width / 2, y + 20 - tp.height / 2));
|
||||
if (i < labels.length - 1) {
|
||||
c.drawLine(
|
||||
Offset(cx, y + 40),
|
||||
Offset(cx, y + 52),
|
||||
Paint()
|
||||
..color = Colors.white38
|
||||
..strokeWidth = 2);
|
||||
final ah = Path()
|
||||
..moveTo(cx, y + 58)
|
||||
..lineTo(cx - 5, y + 50)
|
||||
..lineTo(cx + 5, y + 50)
|
||||
..close();
|
||||
c.drawPath(ah, Paint()..color = Colors.white38);
|
||||
}
|
||||
y += 52;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _FlowPainter o) =>
|
||||
o.program != program || o.pc != pc;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3) SORTING PLAYGROUND (Bubble vs Quick trace)
|
||||
// ---------------------------------------------------------------------------
|
||||
class DigitalSortingLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const DigitalSortingLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<DigitalSortingLabView> createState() => _DigitalSortingLabViewState();
|
||||
}
|
||||
|
||||
class _DigitalSortingLabViewState extends State<DigitalSortingLabView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
List<int> arr = [7, 2, 9, 4, 1, 6];
|
||||
int algo = 0; // 0 bubble, 1 quick (trace)
|
||||
int stepIdx = 0;
|
||||
List<List<int>> snaps = [];
|
||||
List<String> notes = [];
|
||||
bool playing = false;
|
||||
late final AnimationController ctl;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_buildTrace();
|
||||
ctl = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 600))
|
||||
..addListener(() {
|
||||
if (!playing) return;
|
||||
});
|
||||
}
|
||||
|
||||
void _buildTrace() {
|
||||
snaps = [List.of(arr)];
|
||||
notes = ['البداية: ${arr.join(' ')}'];
|
||||
if (algo == 0) {
|
||||
final a = List<int>.of(arr);
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
for (int j = 0; j < a.length - 1 - i; j++) {
|
||||
if (a[j] > a[j + 1]) {
|
||||
final t = a[j];
|
||||
a[j] = a[j + 1];
|
||||
a[j + 1] = t;
|
||||
snaps.add(List.of(a));
|
||||
notes.add('بدّل ${a[j + 1]} و ${a[j]} → ${a.join(' ')}');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// quicksort Lomuto trace (simplified, honest label)
|
||||
final a = List<int>.of(arr);
|
||||
void qs(int lo, int hi) {
|
||||
if (lo >= hi) return;
|
||||
final pivot = a[hi];
|
||||
int i = lo;
|
||||
for (int j = lo; j < hi; j++) {
|
||||
if (a[j] < pivot) {
|
||||
final t = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = t;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
final t = a[i];
|
||||
a[i] = a[hi];
|
||||
a[hi] = t;
|
||||
snaps.add(List.of(a));
|
||||
notes.add('محور $pivot → ${a.join(' ')}');
|
||||
qs(lo, i - 1);
|
||||
qs(i + 1, hi);
|
||||
}
|
||||
|
||||
qs(0, a.length - 1);
|
||||
}
|
||||
stepIdx = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ctl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cur = snaps.isEmpty ? arr : snaps[stepIdx.clamp(0, snaps.length - 1)];
|
||||
final sorted = _isSorted(cur);
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'ملعب المصفوفات والفرز',
|
||||
subtitleAr: 'قارن فقاعي O(n²) مع سريع O(n log n) خطوة بخطوة',
|
||||
identity: kDigitalSortingToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'التعقيد الزمني الأسوأ للفرز الفقاعي (n عناصر) هو …',
|
||||
checkpointOptions: const ['O(n²)', 'O(n)', 'O(log n)', 'O(1)'],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(algo == 0 ? 'Bubble O(n²)' : 'Quick O(n log n)'),
|
||||
LabPill('خطوة ${stepIdx + 1}/${snaps.length}'),
|
||||
if (sorted) const LabPill('مرتبة ✓', color: Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _BarsPainter(values: cur),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<int>(
|
||||
labels: const ['فقاعي', 'سريع'],
|
||||
values: const [0, 1],
|
||||
current: algo,
|
||||
onSelected: (v) => setState(() {
|
||||
algo = v;
|
||||
_buildTrace();
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: stepIdx > 0 ? () => setState(() => stepIdx--) : null,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24)),
|
||||
child: const Text('◀ سابق'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: stepIdx < snaps.length - 1
|
||||
? () => setState(() => stepIdx++)
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.saqelCyan,
|
||||
foregroundColor: Colors.black),
|
||||
child: const Text('التالي ▶',
|
||||
style: TextStyle(fontWeight: FontWeight.w800)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.shuffle, size: 15),
|
||||
label: const Text('خلط المصفوفة'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: const Color(0xFFFFD166),
|
||||
side: const BorderSide(color: Color(0xFFFFD166))),
|
||||
onPressed: () => setState(() {
|
||||
arr.shuffle(math.Random());
|
||||
_buildTrace();
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Text(
|
||||
notes.isEmpty ? '' : notes[stepIdx.clamp(0, notes.length - 1)],
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF00F5D4),
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace')),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const LabFormulaCard(
|
||||
title: 'لماذا السريع أسرع؟',
|
||||
body:
|
||||
'الفقاعي يقارن كل زوج مجاور (تربيعي). السريع يقسم حول محور ويفرز كل قسم (لوغاريتمي) — لاحظ قفزات المحور في التتبع.',
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'تتبع تعليمي لمدخلات صغيرة؛ قياس الأداء الحقيقي يحتاج n كبيرة وتوقيتاً فعلياً.',
|
||||
);
|
||||
}
|
||||
|
||||
bool _isSorted(List<int> a) {
|
||||
for (int i = 0; i < a.length - 1; i++) {
|
||||
if (a[i] > a[i + 1]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class _BarsPainter extends CustomPainter {
|
||||
final List<int> values;
|
||||
_BarsPainter({required this.values});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
if (values.isEmpty) return;
|
||||
final mx = values.reduce(math.max).toDouble();
|
||||
final bw = s.width / values.length;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
final h = (values[i] / mx) * (s.height - 70);
|
||||
final x = i * bw + 6;
|
||||
final grad = const LinearGradient(
|
||||
colors: [Color(0xFF00F5D4), Color(0xFF0071E3)],
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter)
|
||||
.createShader(Rect.fromLTWH(x, s.height - 30 - h, bw - 12, h));
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(x, s.height - 30 - h, x + bw - 12, s.height - 30,
|
||||
const Radius.circular(7)),
|
||||
Paint()..shader = grad);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${values[i]}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900)),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
tp.paint(c, Offset(x + (bw - 12 - tp.width) / 2, s.height - 30 - h - 20));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _BarsPainter o) => o.values != values;
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// HISTORY LABS — GRADE 10 (تاريخ الحضارات الصف العاشر)
|
||||
/// Lesson 1: الإمبراطورية الفارسية: النشأة والتطور (560 ق.م - 651 م)
|
||||
/// Direct alignment with Jordan Grade 10 History Curriculum (Unit 1, Lesson 1)
|
||||
/// ============================================================================
|
||||
|
||||
class HistoryPersianEmpireLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const HistoryPersianEmpireLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<HistoryPersianEmpireLabView> createState() =>
|
||||
_HistoryPersianEmpireLabViewState();
|
||||
}
|
||||
|
||||
class _HistoryPersianEmpireLabViewState
|
||||
extends State<HistoryPersianEmpireLabView> {
|
||||
int selectedDynasty =
|
||||
0; // 0: Achaemenid, 1: Seleucid, 2: Parthian, 3: Sasanian
|
||||
double expansionFactor = 1.0; // 0.2 to 1.0
|
||||
bool showRoyalRoad = true;
|
||||
|
||||
static const dynasties = [
|
||||
{
|
||||
'name': 'الدولة الأخمينية (Achaemenid)',
|
||||
'period': '560 ق.م - 330 ق.م',
|
||||
'founder': 'الملك كورش (Cyrus the Great)',
|
||||
'capital': 'برسبوليس (Persepolis) وشوشان',
|
||||
'desc':
|
||||
'أسسها كورش وامتدت لتشمل آسيا الصغرى، بلاد الشام، مصر، وبلاد ما بين النهرين. اشتهرت بالطريق الملكي والعملة الذهبية (الدارك).',
|
||||
'color': Color(0xFFFFD60A),
|
||||
},
|
||||
{
|
||||
'name': 'الدولة السلوقية (Seleucid)',
|
||||
'period': '312 ق.م - 64 ق.م',
|
||||
'founder': 'القائد سلوقس الأول (سلوقس نيكاتور)',
|
||||
'capital': 'أنطاكية وسلوقية',
|
||||
'desc':
|
||||
'نشأت بعد وفاة الإسكندر المقدوني الأكبر وتقاسم قادته لإمبراطوريته، وتميزت بالمزج بين الثقافة اليونانية الهلنستية والشرقية.',
|
||||
'color': Color(0xFF60A5FA),
|
||||
},
|
||||
{
|
||||
'name': 'الدولة البارثية (Parthian)',
|
||||
'period': '247 ق.م - 224 م',
|
||||
'founder': 'الملك أرساكيس الأول (Arsaces)',
|
||||
'capital': 'أساك ثم طيسفون (المدائن)',
|
||||
'desc':
|
||||
'قامت في شمال شرق إيران وتميزت بفرسانها ورماة السهام البارثيين، وخاضت حروباً طاحنة ضد التوسع الإمبراطوري الروماني.',
|
||||
'color': Color(0xFF30D158),
|
||||
},
|
||||
{
|
||||
'name': 'الدولة الساسانية (Sasanian)',
|
||||
'period': '224 م - 651 م',
|
||||
'founder': 'الملك أردشير الأول (Ardashir I)',
|
||||
'capital': 'طيسفون (المدائن)',
|
||||
'desc':
|
||||
'شهدت نهضة معمارية وفنية كبرى ومنافسة شرسة مع الإمبراطورية البيزنطية، حتى انتهت بالفتح الإسلامي في عهد الخليفة عمر بن الخطاب.',
|
||||
'color': Color(0xFFFF453A),
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cur = dynasties[selectedDynasty];
|
||||
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'مختبر الإمبراطورية الفارسية (560 ق.م - 651 م)',
|
||||
subtitleAr:
|
||||
'محاكاة تفاعلية للدول الفارسية الأربع: الأخمينية، السلوقية، البارثية، الساسانية',
|
||||
identity: kHistoryPersianEmpireLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'مؤسس الإمبراطورية الفارسية الأخمينية التي امتدت عبر آسيا وأفريقيا وأوروبا هو الملك …',
|
||||
checkpointOptions: const [
|
||||
'كورش (Cyrus)',
|
||||
'داريوس الأكبر (Darius)',
|
||||
'سلوقس الأول (Seleucus)',
|
||||
'أرساكيس (Arsaces)',
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill(cur['period'] as String, color: cur['color'] as Color),
|
||||
LabPill('المؤسس: ${cur['founder']}', color: AppColors.saqelCyan),
|
||||
LabPill('العاصمة: ${cur['capital']}', color: const Color(0xFF94A3B8)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _PersianEmpirePainter(
|
||||
dynastyIndex: selectedDynasty,
|
||||
dynastyColor: cur['color'] as Color,
|
||||
expansion: expansionFactor,
|
||||
showRoad: showRoyalRoad,
|
||||
),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
const Text(
|
||||
'اختر المرحلة التاريخية للإمبراطورية:',
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...List.generate(dynasties.length, (i) {
|
||||
final dyn = dynasties[i];
|
||||
final isSel = selectedDynasty == i;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: InkWell(
|
||||
onTap: () => setState(() => selectedDynasty = i),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSel
|
||||
? (dyn['color'] as Color).withValues(alpha: 0.18)
|
||||
: kSaqelGlass,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isSel ? (dyn['color'] as Color) : Colors.white12,
|
||||
width: isSel ? 1.5 : 1.0,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: dyn['color'] as Color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
dyn['name'] as String,
|
||||
style: TextStyle(
|
||||
color: isSel ? Colors.white : Colors.white70,
|
||||
fontWeight:
|
||||
isSel ? FontWeight.w800 : FontWeight.w600,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
dyn['period'] as String,
|
||||
style: TextStyle(
|
||||
color: (dyn['color'] as Color)
|
||||
.withValues(alpha: 0.8),
|
||||
fontSize: 10.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 10),
|
||||
LabSlider(
|
||||
label: 'نطاق التوسع الجغرافي الإمبراطوري',
|
||||
value: expansionFactor,
|
||||
min: 0.2,
|
||||
max: 1.0,
|
||||
display: '${(expansionFactor * 100).toInt()}%',
|
||||
onChanged: (v) => setState(() => expansionFactor = v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: kSaqelGlass,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border:
|
||||
Border.all(color: AppColors.saqelCyan.withValues(alpha: 0.2)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('إظهار الطريق الملكي ومحطات البريد (2700 كم)',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w600)),
|
||||
CupertinoSwitch(
|
||||
value: showRoyalRoad,
|
||||
activeColor: AppColors.saqelCyan,
|
||||
onChanged: (v) => setState(() => showRoyalRoad = v),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: (cur['color'] as Color).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: (cur['color'] as Color).withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Text(
|
||||
cur['desc'] as String,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12, height: 1.45),
|
||||
),
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'منهاج وزارة التربية والتعليم الأردنية • كتاب التاريخ للصف العاشر • الوحدة الأولى ص 8-13',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PersianEmpirePainter extends CustomPainter {
|
||||
final int dynastyIndex;
|
||||
final Color dynastyColor;
|
||||
final double expansion;
|
||||
final bool showRoad;
|
||||
|
||||
_PersianEmpirePainter({
|
||||
required this.dynastyIndex,
|
||||
required this.dynastyColor,
|
||||
required this.expansion,
|
||||
required this.showRoad,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width;
|
||||
final h = size.height;
|
||||
|
||||
// Dark parchment background
|
||||
final bg = Paint()
|
||||
..shader = const RadialGradient(
|
||||
colors: [Color(0xFF131D2D), Color(0xFF070E18)],
|
||||
radius: 0.9,
|
||||
).createShader(Rect.fromLTWH(0, 0, w, h));
|
||||
canvas.drawRect(Rect.fromLTWH(0, 0, w, h), bg);
|
||||
|
||||
// Ancient Map Grid lines
|
||||
final gridPaint = Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.05)
|
||||
..strokeWidth = 1.0;
|
||||
for (double x = 0; x < w; x += 40) {
|
||||
canvas.drawLine(Offset(x, 0), Offset(x, h), gridPaint);
|
||||
}
|
||||
for (double y = 0; y < h; y += 40) {
|
||||
canvas.drawLine(Offset(0, y), Offset(w, y), gridPaint);
|
||||
}
|
||||
|
||||
// Imperial territory blob (Center: Iran / Persepolis)
|
||||
final cx = w * 0.58;
|
||||
final cy = h * 0.52;
|
||||
final maxRadiusX = (w * 0.38) * expansion;
|
||||
final maxRadiusY = (h * 0.32) * expansion;
|
||||
|
||||
final territoryGlow = Paint()
|
||||
..color = dynastyColor.withValues(alpha: 0.15)
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 25);
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(cx, cy),
|
||||
width: maxRadiusX * 2,
|
||||
height: maxRadiusY * 2),
|
||||
territoryGlow);
|
||||
|
||||
final territoryBorder = Paint()
|
||||
..color = dynastyColor.withValues(alpha: 0.6)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0;
|
||||
canvas.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(cx, cy),
|
||||
width: maxRadiusX * 2,
|
||||
height: maxRadiusY * 2),
|
||||
territoryBorder);
|
||||
|
||||
// Key Historical Cities
|
||||
final cityPaint = Paint()..color = const Color(0xFFFFD60A);
|
||||
final capitalPaint = Paint()..color = const Color(0xFFFF375F);
|
||||
|
||||
// Persepolis / Shushan
|
||||
canvas.drawCircle(Offset(cx + 20, cy + 10), 5, capitalPaint);
|
||||
_drawCityLabel(canvas, Offset(cx + 20, cy + 10), 'برسبوليس (Persepolis)');
|
||||
|
||||
// Babylon / Mesopotamia
|
||||
canvas.drawCircle(Offset(cx - 70, cy), 4, cityPaint);
|
||||
_drawCityLabel(canvas, Offset(cx - 70, cy), 'بابل (Babylon)');
|
||||
|
||||
// Sardis (Asia Minor)
|
||||
canvas.drawCircle(Offset(cx - 160, cy - 40), 4, cityPaint);
|
||||
_drawCityLabel(canvas, Offset(cx - 160, cy - 40), 'ساردس (Sardis)');
|
||||
|
||||
// Egypt / Nile
|
||||
canvas.drawCircle(Offset(cx - 150, cy + 55), 4, cityPaint);
|
||||
_drawCityLabel(canvas, Offset(cx - 150, cy + 55), 'ممفيس (Egypt)');
|
||||
|
||||
// Royal Road (Sardis to Susa/Persepolis)
|
||||
if (showRoad) {
|
||||
final roadPaint = Paint()
|
||||
..color = const Color(0xFF00F5D4)
|
||||
..strokeWidth = 2.2
|
||||
..style = PaintingStyle.stroke;
|
||||
final path = Path()
|
||||
..moveTo(cx - 160, cy - 40) // Sardis
|
||||
..quadraticBezierTo(cx - 110, cy - 60, cx - 70, cy) // Babylon
|
||||
..lineTo(cx + 20, cy + 10); // Susa / Persepolis
|
||||
canvas.drawPath(path, roadPaint);
|
||||
|
||||
// Courier Station dots
|
||||
final stationPaint = Paint()..color = const Color(0xFF00F5D4);
|
||||
canvas.drawCircle(Offset(cx - 110, cy - 35), 3, stationPaint);
|
||||
canvas.drawCircle(Offset(cx - 30, cy + 5), 3, stationPaint);
|
||||
}
|
||||
}
|
||||
|
||||
void _drawCityLabel(Canvas canvas, Offset offset, String text) {
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 10.5, fontWeight: FontWeight.w700),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(canvas, Offset(offset.dx - (tp.width / 2), offset.dy - 16));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _PersianEmpirePainter oldDelegate) =>
|
||||
oldDelegate.dynastyIndex != dynastyIndex ||
|
||||
oldDelegate.expansion != expansion ||
|
||||
oldDelegate.showRoad != showRoad;
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// ISLAMIC STUDIES — GRADE 10
|
||||
/// 1. IslamicTajweedLabView — مخارج الحروف (الحلق/اللسان/الشفتان/الخيشوم)
|
||||
/// 2. IslamicInheritanceLabView — حاسبة المواريث (أصحاب الفروض + pie chart)
|
||||
/// الأحكام تعليمية استرشادية من الكتاب المدرسي؛ الفتوى والقسمة النهائية
|
||||
/// للجهات الشرعية المختصة — يُذكر ذلك صراحة في الواجهة.
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) TAJWEED ARTICULATION POINTS
|
||||
// ---------------------------------------------------------------------------
|
||||
class IslamicTajweedLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const IslamicTajweedLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<IslamicTajweedLabView> createState() => _IslamicTajweedLabViewState();
|
||||
}
|
||||
|
||||
class _IslamicTajweedLabViewState extends State<IslamicTajweedLabView> {
|
||||
int organ = 1; // 0 throat, 1 tongue, 2 lips, 3 nasal
|
||||
int letterIdx = 0;
|
||||
|
||||
static const organs = [
|
||||
{
|
||||
'name': 'الحلق',
|
||||
'letters': ['ء هـ', 'ع ح', 'غ خ'],
|
||||
'desc':
|
||||
'أقصى الحلق (ء هـ) • وسطه (ع ح) • أدناه (غ خ). حروف الإظهار الستة.',
|
||||
'examples': '﴿مِنْ خَشْيَةِ اللَّهِ﴾ — إظهار الغنة عند الخاء.',
|
||||
},
|
||||
{
|
||||
'name': 'اللسان',
|
||||
'letters': ['ق ك', 'ج ش ي', 'ل ن ر', 'ط د ت', 'ص ز س', 'ظ ذ ث'],
|
||||
'desc':
|
||||
'أقصاه (ق ك) • وسطه (ج ش ي) • حافته (ل) • طرفه (ن ر، ط د ت، ص ز س، ظ ذ ث).',
|
||||
'examples': 'القلقلة في (ق ط ب ج د) ساكنة: ﴿وَالْفَجْرِ﴾.',
|
||||
},
|
||||
{
|
||||
'name': 'الشفتان',
|
||||
'letters': ['ف', 'ب م و', 'و المد'],
|
||||
'desc':
|
||||
'بطن الشفة مع أطراف الثنايا (ف) • انطباق الشفتين (ب م) • انضمامهما (و).',
|
||||
'examples': 'الإخفاء الشفوي: ميم ساكنة + ب: ﴿هُم بِهِ﴾.',
|
||||
},
|
||||
{
|
||||
'name': 'الخيشوم (الغنة)',
|
||||
'letters': ['نّ مّ', 'إدغام بغنة', 'إخفاء'],
|
||||
'desc':
|
||||
'صوت الغنة من الخيشوم بمقدار حركتين في النون والميم المشددتين والإدغام بغنة (ينمو).',
|
||||
'examples': '﴿مِن نُّورٍ﴾ — إدغام بغنة بمقدار حركتين.',
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final o = organs[organ];
|
||||
final letters = o['letters'] as List<String>;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'أحكام التجويد — مخارج الحروف',
|
||||
subtitleAr: 'اضغط مخرجاً في مخطط جهاز النطق لترى حروفه وأمثلته',
|
||||
identity: kIslamicTajweedToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'حروف الإظهار الحلقي الستة مخرجها …',
|
||||
checkpointOptions: const [
|
||||
'الحلق',
|
||||
'الشفتان',
|
||||
'الخيشوم',
|
||||
'وسط اللسان فقط'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('المخرج: ${o['name']}', color: const Color(0xFF30D158)),
|
||||
LabPill(letters[letterIdx.clamp(0, letters.length - 1)]),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _VocalTractPainter(active: organ),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSegments<int>(
|
||||
labels: const ['الحلق', 'اللسان', 'الشفتان', 'الخيشوم'],
|
||||
values: const [0, 1, 2, 3],
|
||||
current: organ,
|
||||
onSelected: (v) => setState(() {
|
||||
organ = v;
|
||||
letterIdx = 0;
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: List.generate(letters.length, (i) {
|
||||
final sel = i == letterIdx;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => letterIdx = i),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 14, vertical: 9),
|
||||
decoration: BoxDecoration(
|
||||
color: sel
|
||||
? const Color(0xFF30D158)
|
||||
: Colors.white.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: sel ? const Color(0xFF30D158) : Colors.white12),
|
||||
),
|
||||
child: Text(letters[i],
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LabFormulaCard(
|
||||
title: 'مخرج ${o['name']}',
|
||||
body: '${o['desc']}\nمثال تطبيقي: ${o['examples']}',
|
||||
accent: const Color(0xFF30D158),
|
||||
),
|
||||
],
|
||||
footerNote: 'مخطط تعليمي مبسط؛ إتقان التلاوة يكون بالتلقي على معلم متقن.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _VocalTractPainter extends CustomPainter {
|
||||
final int active;
|
||||
_VocalTractPainter({required this.active});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070F1C));
|
||||
// head profile (side view, schematic)
|
||||
final head = Path()
|
||||
..moveTo(s.width * 0.30, s.height * 0.08)
|
||||
..quadraticBezierTo(
|
||||
s.width * 0.72, s.height * 0.10, s.width * 0.70, s.height * 0.34)
|
||||
..quadraticBezierTo(
|
||||
s.width * 0.78, s.height * 0.44, s.width * 0.66, s.height * 0.52)
|
||||
..lineTo(s.width * 0.60, s.height * 0.60)
|
||||
..quadraticBezierTo(
|
||||
s.width * 0.58, s.height * 0.78, s.width * 0.44, s.height * 0.86)
|
||||
..lineTo(s.width * 0.30, s.height * 0.86)
|
||||
..close();
|
||||
c.drawPath(
|
||||
head, Paint()..color = const Color(0xFFDBB894).withValues(alpha: 0.9));
|
||||
// oral cavity
|
||||
c.drawOval(
|
||||
Rect.fromCenter(
|
||||
center: Offset(s.width * 0.50, s.height * 0.52),
|
||||
width: s.width * 0.22,
|
||||
height: 44),
|
||||
Paint()..color = const Color(0xFF7C2D12).withValues(alpha: 0.85));
|
||||
// tongue
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(s.width * 0.40, s.height * 0.56)
|
||||
..quadraticBezierTo(
|
||||
s.width * 0.50, s.height * 0.46, s.width * 0.60, s.height * 0.54)
|
||||
..lineTo(s.width * 0.58, s.height * 0.60)
|
||||
..quadraticBezierTo(
|
||||
s.width * 0.48, s.height * 0.58, s.width * 0.40, s.height * 0.60)
|
||||
..close(),
|
||||
Paint()..color = const Color(0xFFF87171),
|
||||
);
|
||||
// highlight zones
|
||||
final zones = [
|
||||
{'p': Offset(s.width * 0.42, s.height * 0.68), 'r': 20.0, 't': 'الحلق'},
|
||||
{'p': Offset(s.width * 0.52, s.height * 0.52), 'r': 24.0, 't': 'اللسان'},
|
||||
{'p': Offset(s.width * 0.66, s.height * 0.52), 'r': 18.0, 't': 'الشفتان'},
|
||||
{'p': Offset(s.width * 0.56, s.height * 0.26), 'r': 18.0, 't': 'الخيشوم'},
|
||||
];
|
||||
for (int i = 0; i < zones.length; i++) {
|
||||
final p = zones[i]['p'] as Offset;
|
||||
final r = zones[i]['r'] as double;
|
||||
final on = i == active;
|
||||
c.drawCircle(
|
||||
p,
|
||||
r + (on ? 5 : 0),
|
||||
Paint()
|
||||
..color = (on ? const Color(0xFF30D158) : Colors.white24)
|
||||
.withValues(alpha: on ? 0.35 : 0.25));
|
||||
c.drawCircle(
|
||||
p,
|
||||
r,
|
||||
Paint()
|
||||
..color = on ? const Color(0xFF30D158) : Colors.white38
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = on ? 3 : 1.6);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: zones[i]['t'] as String,
|
||||
style: TextStyle(
|
||||
color: on ? const Color(0xFF30D158) : Colors.white70,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800)),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout();
|
||||
tp.paint(c, p + Offset(-tp.width / 2, r + 4));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _VocalTractPainter o) => o.active != active;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) INHERITANCE CALCULATOR (educational, non-fatwa)
|
||||
// ---------------------------------------------------------------------------
|
||||
class IslamicInheritanceLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const IslamicInheritanceLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<IslamicInheritanceLabView> createState() =>
|
||||
_IslamicInheritanceLabViewState();
|
||||
}
|
||||
|
||||
class _IslamicInheritanceLabViewState extends State<IslamicInheritanceLabView> {
|
||||
bool husband = true;
|
||||
bool wife = false;
|
||||
int sons = 1;
|
||||
int daughters = 1;
|
||||
bool father = true;
|
||||
bool mother = true;
|
||||
double estate = 12000;
|
||||
|
||||
Map<String, double> shares() {
|
||||
// Simplified school-level faraid (husband xor wife, parents, children).
|
||||
// Returns fractions of 1. Honest label: simplified educational model.
|
||||
final m = <String, double>{};
|
||||
final hasChild = sons + daughters > 0;
|
||||
if (husband) m['الزوج'] = hasChild ? 1 / 4 : 1 / 2;
|
||||
if (wife) m['الزوجة'] = hasChild ? 1 / 8 : 1 / 4;
|
||||
if (father)
|
||||
m['الأب'] =
|
||||
hasChild ? 1 / 6 : 0; // + residue handled as family remainder note
|
||||
if (mother) m['الأم'] = hasChild ? 1 / 6 : 1 / 3;
|
||||
final fixed = m.values.fold(0.0, (a, b) => a + b);
|
||||
final rest = (1 - fixed).clamp(0.0, 1.0);
|
||||
final units = sons * 2 + daughters * 1;
|
||||
if (units > 0 && rest > 0) {
|
||||
if (sons > 0) m['الأبناء (عصبة)'] = rest * (sons * 2 / units);
|
||||
if (daughters > 0) {
|
||||
m['البنات (عصبة)'] =
|
||||
(m['البنات (عصبة)'] ?? 0) + rest * (daughters / units);
|
||||
}
|
||||
} else if (units == 0 && rest > 0) {
|
||||
m['باقي الورثة/ذوو الأرحام (يُرجع للقضاء)'] = rest;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sh = shares();
|
||||
final total = sh.values.fold(0.0, (a, b) => a + b);
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'المواريث والفرائض — حاسبة تعليمية',
|
||||
subtitleAr:
|
||||
'أصحاب الفروض: النصف الربع الثمن الثلثان الثلث السدس + العصبات',
|
||||
identity: kIslamicInheritanceToolIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'نصيب الزوج عند وجود الفرع الوارث هو …',
|
||||
checkpointOptions: const ['الربع', 'النصف', 'الثمن', 'الثلث'],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('التركة: ${estate.toInt()} دينار'),
|
||||
LabPill('المجموع: ${(total * 100).toStringAsFixed(1)}%'),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _PiePainter(shares: sh),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabToggle(
|
||||
label: 'الزوج موجود',
|
||||
value: husband,
|
||||
onChanged: (v) => setState(() {
|
||||
husband = v;
|
||||
if (v) wife = false;
|
||||
})),
|
||||
LabToggle(
|
||||
label: 'الزوجة موجودة',
|
||||
value: wife,
|
||||
onChanged: (v) => setState(() {
|
||||
wife = v;
|
||||
if (v) husband = false;
|
||||
})),
|
||||
LabToggle(
|
||||
label: 'الأب موجود',
|
||||
value: father,
|
||||
onChanged: (v) => setState(() => father = v)),
|
||||
LabToggle(
|
||||
label: 'الأم موجودة',
|
||||
value: mother,
|
||||
onChanged: (v) => setState(() => mother = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabSlider(
|
||||
label: 'عدد الأبناء (ذكور)',
|
||||
value: sons.toDouble(),
|
||||
min: 0,
|
||||
max: 5,
|
||||
display: '$sons',
|
||||
onChanged: (v) => setState(() => sons = v.toInt())),
|
||||
LabSlider(
|
||||
label: 'عدد البنات',
|
||||
value: daughters.toDouble(),
|
||||
min: 0,
|
||||
max: 5,
|
||||
display: '$daughters',
|
||||
accent: const Color(0xFFFF9F0A),
|
||||
onChanged: (v) => setState(() => daughters = v.toInt())),
|
||||
LabSlider(
|
||||
label: 'قيمة التركة (دينار — تعليمية)',
|
||||
value: estate,
|
||||
min: 1000,
|
||||
max: 100000,
|
||||
display: '${estate.toInt()}',
|
||||
accent: const Color(0xFF30D158),
|
||||
onChanged: (v) => setState(() => estate = v)),
|
||||
const SizedBox(height: 6),
|
||||
...sh.entries.map((e) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(e.key,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700)),
|
||||
Text(
|
||||
'${(e.value * 100).toStringAsFixed(1)}% • ${(e.value * estate).toStringAsFixed(0)} د',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF00F5D4),
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w800)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
footerNote:
|
||||
'نموذج مدرسي مبسط للحصص الصفية فقط — القسمة الشرعية النهائية لدى المحكمة الشرعية/دائرة الإفتاء.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PiePainter extends CustomPainter {
|
||||
final Map<String, double> shares;
|
||||
_PiePainter({required this.shares});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070F1C));
|
||||
final center = Offset(s.width / 2, s.height * 0.44);
|
||||
final r = math.min(s.width, s.height) * 0.32;
|
||||
const cols = [
|
||||
Color(0xFF00F5D4),
|
||||
Color(0xFFFF9F0A),
|
||||
Color(0xFF30D158),
|
||||
Color(0xFF0071E3),
|
||||
Color(0xFFBF5AF2),
|
||||
Color(0xFFFF375F),
|
||||
Color(0xFFFFD166)
|
||||
];
|
||||
double start = -math.pi / 2;
|
||||
int i = 0;
|
||||
final entries = shares.entries.where((e) => e.value > 0.001).toList();
|
||||
for (final e in entries) {
|
||||
final sweep = e.value * math.pi * 2;
|
||||
c.drawArc(Rect.fromCircle(center: center, radius: r), start, sweep, true,
|
||||
Paint()..color = cols[i % cols.length]);
|
||||
start += sweep;
|
||||
i++;
|
||||
}
|
||||
c.drawCircle(
|
||||
center,
|
||||
r,
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.6);
|
||||
// legend
|
||||
double ly = s.height * 0.44 + r + 14;
|
||||
i = 0;
|
||||
for (final e in entries) {
|
||||
c.drawCircle(Offset(26, ly), 6, Paint()..color = cols[i % cols.length]);
|
||||
final tp = TextPainter(
|
||||
text: TextSpan(
|
||||
text: '${e.key} ${(e.value * 100).toStringAsFixed(0)}%',
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 11),
|
||||
),
|
||||
textDirection: TextDirection.rtl,
|
||||
)..layout(maxWidth: s.width - 60);
|
||||
tp.paint(c, Offset(40, ly - 8));
|
||||
ly += 20;
|
||||
i++;
|
||||
if (ly > s.height - 8) break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _PiePainter o) => o.shares != shares;
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// SAQEL VIRTUAL LABS — EXPLICIT CURRICULUM IDENTITY
|
||||
/// ============================================================================
|
||||
/// Binds a lab operationally ONLY to a `curriculumLessonId` (the exact
|
||||
/// `curriculum_lesson_id` from the approved syllabus package). Matching by
|
||||
/// lesson title, keywords, or `lesson_01` alone is forbidden.
|
||||
///
|
||||
/// A lab is never shown to a student unless:
|
||||
/// 1. it carries a bound, [isReleased] identity, AND
|
||||
/// 2. the server published bundle supplies `publishedBundleVersion`.
|
||||
/// Unbound entries are standalone authoring tools previewable only in the
|
||||
/// clearly-labelled authoring gallery. No demo mastery/income ever lives here.
|
||||
/// ============================================================================
|
||||
|
||||
@immutable
|
||||
class LabIdentity {
|
||||
/// Exact syllabus identity, e.g. `math_10_semester_1_unit_01_lesson_02`.
|
||||
final String curriculumLessonId;
|
||||
|
||||
/// Stable identifier for standalone authoring tools without a lesson anchor.
|
||||
final String toolKey;
|
||||
final String gradeKey;
|
||||
final String subjectKey;
|
||||
final String semesterKey;
|
||||
final String unitKey;
|
||||
final String lessonKey;
|
||||
|
||||
/// Version published in the approved package (never invented).
|
||||
final String curriculumVersion;
|
||||
|
||||
/// Source markdown path inside the curriculum storage, e.g.
|
||||
/// `grade_10/math_10/semester_1/unit_01/lesson_02.md`.
|
||||
final String sourceMarkdown;
|
||||
|
||||
/// Spec `status` from the authoring protocol.
|
||||
final String reviewStatus;
|
||||
|
||||
/// Spec `source_extraction_review_status`.
|
||||
final String sourceExtractionReviewStatus;
|
||||
|
||||
final String subjectAr;
|
||||
final String lessonAr;
|
||||
|
||||
/// Server-provided only. Null until the published bundle is returned.
|
||||
final String? publishedBundleVersion;
|
||||
|
||||
const LabIdentity({
|
||||
this.curriculumLessonId = '',
|
||||
this.toolKey = '',
|
||||
this.gradeKey = 'grade_10',
|
||||
this.subjectKey = '',
|
||||
this.semesterKey = '',
|
||||
this.unitKey = '',
|
||||
this.lessonKey = '',
|
||||
this.curriculumVersion = 'jordan-grade10-2026-source-review',
|
||||
this.sourceMarkdown = '',
|
||||
this.reviewStatus = 'draft_requires_academic_review',
|
||||
this.sourceExtractionReviewStatus = 'needs_human_review',
|
||||
required this.subjectAr,
|
||||
required this.lessonAr,
|
||||
this.publishedBundleVersion,
|
||||
});
|
||||
|
||||
/// True when this lab is bound to an explicit curriculum lesson.
|
||||
bool get isBound => curriculumLessonId.isNotEmpty;
|
||||
|
||||
/// True when a subject expert released the content of this lesson.
|
||||
bool get isReleased => reviewStatus == 'released';
|
||||
|
||||
/// A lab is published only when released AND the server bundle version
|
||||
/// has actually been returned. Both facts are never assumed locally.
|
||||
bool get isPublished =>
|
||||
isReleased && (publishedBundleVersion?.isNotEmpty ?? false);
|
||||
|
||||
/// Honest one-line header for the scaffold header.
|
||||
String get displayOrigin {
|
||||
if (isBound) {
|
||||
return 'العاشر • $subjectAr • $curriculumLessonId • $curriculumVersion';
|
||||
}
|
||||
return 'العاشر • $subjectAr • أداة تأليف مستقلة ($toolKey)';
|
||||
}
|
||||
|
||||
String get sourceLabel => sourceMarkdown.isEmpty ? toolKey : sourceMarkdown;
|
||||
|
||||
String get statusLabel {
|
||||
if (isPublished) return 'منشور';
|
||||
if (isBound) return 'مسودة — بمراجعة أكاديمية';
|
||||
return 'أداة معاينة';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LESSON-BOUND LAB IDENTITIES (verified against spec front-matter)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LabIdentity kPhysicsVectorAdditionLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'physics_10_semester_1_unit_01_lesson_02',
|
||||
subjectKey: 'physics_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_02',
|
||||
sourceMarkdown: 'grade_10/physics_10/semester_1/unit_01/lesson_02.md',
|
||||
subjectAr: 'الفيزياء',
|
||||
lessonAr: 'جمع المتجهات وتحليلها',
|
||||
);
|
||||
|
||||
const LabIdentity kPhysicsMotion1DLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'physics_10_semester_1_unit_02_lesson_01',
|
||||
subjectKey: 'physics_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/physics_10/semester_1/unit_02/lesson_01.md',
|
||||
subjectAr: 'الفيزياء',
|
||||
lessonAr: 'الحركة في بعد واحد',
|
||||
);
|
||||
|
||||
const LabIdentity kPhysicsCircularMotionLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'physics_10_semester_2_unit_04_lesson_03',
|
||||
subjectKey: 'physics_10',
|
||||
semesterKey: 'semester_2',
|
||||
unitKey: 'unit_04',
|
||||
lessonKey: 'lesson_03',
|
||||
sourceMarkdown: 'grade_10/physics_10/semester_2/unit_04/lesson_03.md',
|
||||
subjectAr: 'الفيزياء',
|
||||
lessonAr: 'الحركة الدائرية المنتظمة',
|
||||
);
|
||||
|
||||
const LabIdentity kPhysicsNewtonInclineLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'physics_10_semester_2_unit_04_lesson_02',
|
||||
subjectKey: 'physics_10',
|
||||
semesterKey: 'semester_2',
|
||||
unitKey: 'unit_04',
|
||||
lessonKey: 'lesson_02',
|
||||
sourceMarkdown: 'grade_10/physics_10/semester_2/unit_04/lesson_02.md',
|
||||
subjectAr: 'الفيزياء',
|
||||
lessonAr: 'قوانين نيوتن — المستوى المائل',
|
||||
);
|
||||
|
||||
const LabIdentity kChemistryBohrAtomLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'chemistry_10_semester_1_unit_01_lesson_01',
|
||||
subjectKey: 'chemistry_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/chemistry_10/semester_1/unit_01/lesson_01.md',
|
||||
subjectAr: 'الكيمياء',
|
||||
lessonAr: 'بنية الذرة ونموذج بور',
|
||||
);
|
||||
|
||||
const LabIdentity kChemistryElectronConfigLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'chemistry_10_semester_1_unit_02_lesson_01',
|
||||
subjectKey: 'chemistry_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/chemistry_10/semester_1/unit_02/lesson_01.md',
|
||||
subjectAr: 'الكيمياء',
|
||||
lessonAr: 'التوزيع الإلكتروني وقاعدة هوند',
|
||||
);
|
||||
|
||||
const LabIdentity kChemistryLewisBondingLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'chemistry_10_semester_1_unit_03_lesson_01',
|
||||
subjectKey: 'chemistry_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_03',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/chemistry_10/semester_1/unit_03/lesson_01.md',
|
||||
subjectAr: 'الكيمياء',
|
||||
lessonAr: 'الروابط وتراكيب لويس (VSEPR)',
|
||||
);
|
||||
|
||||
const LabIdentity kBiologyDichotomousKeyLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'biology_10_semester_1_unit_03_lesson_01',
|
||||
subjectKey: 'biology_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_03',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/biology_10/semester_1/unit_03/lesson_01.md',
|
||||
subjectAr: 'العلوم الحياتية',
|
||||
lessonAr: 'التصنيف — المفتاح الثنائي',
|
||||
);
|
||||
|
||||
const LabIdentity kEarthRockCycleLabIdentity = LabIdentity(
|
||||
curriculumLessonId:
|
||||
'earth_sciences_10_semester_1_unit_01_unit_enrichment_and_review',
|
||||
subjectKey: 'earth_sciences_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'unit_enrichment_and_review',
|
||||
sourceMarkdown:
|
||||
'grade_10/earth_sciences_10/semester_1/unit_01/unit_enrichment_and_review.md',
|
||||
subjectAr: 'علوم الأرض والبيئة',
|
||||
lessonAr: 'دورة الصخور',
|
||||
);
|
||||
|
||||
const LabIdentity kMathSystemsLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'math_10_semester_1_unit_01_lesson_02',
|
||||
subjectKey: 'math_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_02',
|
||||
sourceMarkdown: 'grade_10/math_10/semester_1/unit_01/lesson_02.md',
|
||||
subjectAr: 'الرياضيات',
|
||||
lessonAr: 'حل أنظمة خطية وتربيعية',
|
||||
);
|
||||
|
||||
const LabIdentity kMathCircleChordsLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'math_10_semester_1_unit_02_lesson_01',
|
||||
subjectKey: 'math_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/math_10/semester_1/unit_02/lesson_01.md',
|
||||
subjectAr: 'الرياضيات',
|
||||
lessonAr: 'أوتار الدائرة والقطاعات',
|
||||
);
|
||||
|
||||
const LabIdentity kMathUnitCircleLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'math_10_semester_1_unit_03_lesson_02',
|
||||
subjectKey: 'math_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_03',
|
||||
lessonKey: 'lesson_02',
|
||||
sourceMarkdown: 'grade_10/math_10/semester_1/unit_03/lesson_02.md',
|
||||
subjectAr: 'الرياضيات',
|
||||
lessonAr: 'الدائرة المثلثية',
|
||||
);
|
||||
|
||||
const LabIdentity kFinanceProjectManagementLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'financial_10_semester_1_unit_01_lesson_01',
|
||||
subjectKey: 'financial_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/financial_10/semester_1/unit_01/lesson_01.md',
|
||||
subjectAr: 'الثقافة المالية',
|
||||
lessonAr: 'المشروع وإدارته',
|
||||
);
|
||||
|
||||
const LabIdentity kFinanceFeasibilityLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'financial_10_semester_1_unit_02_lesson_01',
|
||||
subjectKey: 'financial_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/financial_10/semester_1/unit_02/lesson_01.md',
|
||||
subjectAr: 'الثقافة المالية',
|
||||
lessonAr: 'الجدوى وتكلفة الفرصة البديلة',
|
||||
);
|
||||
|
||||
const LabIdentity kHistoryPersianEmpireLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'history_10_semester_1_unit_01_lesson_01',
|
||||
subjectKey: 'history_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/history_10/semester_1/unit_01/lesson_01.md',
|
||||
subjectAr: 'التاريخ',
|
||||
lessonAr: 'الإمبراطورية الفارسية: النشأة والتطور',
|
||||
);
|
||||
|
||||
const LabIdentity kGeographyAtmosphereLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'geography_10_semester_1_unit_01_lesson_01',
|
||||
subjectKey: 'geography_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/geography_10/semester_1/unit_01/lesson_01.md',
|
||||
subjectAr: 'الجغرافيا',
|
||||
lessonAr: 'الغلاف الجوي وطبقاته',
|
||||
);
|
||||
|
||||
const LabIdentity kCivicsActiveCitizenshipLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'civics_10_semester_1_unit_01_lesson_03',
|
||||
subjectKey: 'civics_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_03',
|
||||
sourceMarkdown: 'grade_10/civics_10/semester_1/unit_01/lesson_03.md',
|
||||
subjectAr: 'التربية الوطنية والمدنية',
|
||||
lessonAr: 'المواطنة وميزان الحقوق والواجبات',
|
||||
);
|
||||
|
||||
/// Lesson of listening comprehension on the story of كعب بن مالك
|
||||
/// (source pages 8-9). Stage ordering/turning-point reflect the source page
|
||||
/// phrasing and stay flagged for academic review before any release.
|
||||
const LabIdentity kArabicListeningLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_01',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_01/lesson_01.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أستمع بانتباه وتركيز — قصة كعب بن مالك',
|
||||
);
|
||||
|
||||
/// Lesson of speaking fluently: the art of apology and values of tolerance
|
||||
/// (source pages 10-12). The speech-building order (مقدمة/عرض/خاتمة)، the
|
||||
/// tone skill (تنغيم بلا افتعال)، and the apology-culture five questions are
|
||||
/// all taken from the source wording. Flagged for academic review.
|
||||
const LabIdentity kArabicApologyLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_02',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_02',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_01/lesson_02.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أتحدث بطلاقة — فن الاعتذار وقيم التسامح',
|
||||
);
|
||||
|
||||
/// Lesson of fluent reading: apology culture in Qur'anic stories (صفحات 13-18).
|
||||
/// The spec title mentions «سينية البحتري» while the extracted corpus pages
|
||||
/// model القراءة الصامتة وثقافة الاعتذار القرآني واعتذار موسى للعبد الصالح;
|
||||
/// that divergence is flagged for academic review before any release.
|
||||
const LabIdentity kArabicQuranicApologyLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_03',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_03',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_01/lesson_03.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أقرأ بطلاقة وفهم — الاعتذار في قصص قرآنية',
|
||||
);
|
||||
|
||||
/// Lesson of writing an apology & tolerance letter. NOTE: the letter content
|
||||
/// (زينة→سلمى) is extracted at the END of lesson_03.md (pages 19-21); the
|
||||
/// page-range 22-24 bound by the spec to this lesson actually contains
|
||||
/// أسلوب الشرط. This mismatch is flagged in the lab footer for the reviewer.
|
||||
const LabIdentity kArabicApologyLetterLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_04',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_04',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_01/lesson_04.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أكتب محتوى — رسالة اعتذار وتسامح',
|
||||
);
|
||||
|
||||
/// Lesson of the conditional style (أسلوب الشرط). Built on the extractable
|
||||
/// facts of pages 25-28; the OCR-damaged verse glyphs and incomplete fill-in
|
||||
/// blanks were intentionally skipped and flagged in the lab footer.
|
||||
const LabIdentity kArabicConditionalLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_05',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_05',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_01/lesson_05.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أبني لغتي (1) — أسلوب الشرط',
|
||||
);
|
||||
|
||||
/// Lesson of the informative/declarative style (الأسلوب الخبري). Built on the
|
||||
/// extractable rules and examples of pages 29-31; the reflective recap table
|
||||
/// on page 31 was not turned into a lab.
|
||||
const LabIdentity kArabicInformativeStyleLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_06',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_01',
|
||||
lessonKey: 'lesson_06',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_01/lesson_06.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أبني لغتي (2) — الأسلوب الخبري',
|
||||
);
|
||||
|
||||
/// Unit 2, lesson 1 — listening attention (قصة الضيف). Covers only the
|
||||
/// extractable listening content of pages 34-36; page 37 belongs to the next
|
||||
/// lesson (أتحدث بطلاقة). Flagged in the lab footer.
|
||||
const LabIdentity kArabicUnit2ListeningLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_02_lesson_01',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_01',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_02/lesson_01.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أستمع بانتباه وتركيز — قصة الضيف',
|
||||
);
|
||||
|
||||
/// Unit 2, lesson 2 — speaking fluently (العرض التقديمي). Built on page 38.
|
||||
const LabIdentity kArabicUnit2SpeakingLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_02_lesson_02',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_02',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_02/lesson_02.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أتحدثُ بطلاقةٍ — العرضُ التقديميّ',
|
||||
);
|
||||
|
||||
/// Unit 2, lesson 3 — reading resistance poetry (إلى الصامدين غرب النهر).
|
||||
const LabIdentity kArabicUnit2PoetryLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_02_lesson_03',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_03',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_02/lesson_03.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أقرأُ بطلاقةٍ وفهمٍ — إلى الصامدينَ غربَ النهرِ',
|
||||
);
|
||||
|
||||
/// Unit 2, lesson 4 — writing content (تحليل النص الشعري). Built on the
|
||||
/// writing-elements page 46, the analysis criteria checklist of page 48, the
|
||||
/// opinion-reporting criteria of page 48, and quoted analysis fragments of
|
||||
/// Abdul-Karim Al-Karmi's poem on pages 47-48.
|
||||
const LabIdentity kArabicUnit2WritingLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_02_lesson_04',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_04',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_02/lesson_04.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أكتبُ محتوى — تحليل النص الشعري',
|
||||
);
|
||||
|
||||
/// Unit 2, lesson 5 — build my language (1): the vocative style (أسلوب
|
||||
/// النّداءِ). Built on pages 50-55: the vocative concept, its two pillars and
|
||||
/// letters, the five types of the vocative noun (المنادى) with lesson examples,
|
||||
/// calling the definite-article noun with (أيّها/أيّتها), and the deletion of
|
||||
/// the vocative letter estimated as (يا).
|
||||
const LabIdentity kArabicUnit2VocativeLabIdentity = LabIdentity(
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_02_lesson_05',
|
||||
subjectKey: 'arabic_10',
|
||||
semesterKey: 'semester_1',
|
||||
unitKey: 'unit_02',
|
||||
lessonKey: 'lesson_05',
|
||||
sourceMarkdown: 'grade_10/arabic_10/semester_1/unit_02/lesson_05.md',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'أبني لغتي (1) — أسلوبُ النّداءِ',
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// STANDALONE AUTHORING TOOLS (NO curriculum-lesson anchor in the corpus)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LabIdentity kChemistryFlameTestToolIdentity = LabIdentity(
|
||||
toolKey: 'chemistry_flame_test',
|
||||
subjectAr: 'الكيمياء',
|
||||
lessonAr: 'اختبار اللهب',
|
||||
);
|
||||
|
||||
const LabIdentity kBiologyDarwinFinchesToolIdentity = LabIdentity(
|
||||
toolKey: 'biology_darwin_finches',
|
||||
subjectAr: 'العلوم الحياتية',
|
||||
lessonAr: 'التطور — عصافير داروين',
|
||||
);
|
||||
|
||||
const LabIdentity kBiologyBacteriophageToolIdentity = LabIdentity(
|
||||
toolKey: 'biology_bacteriophage',
|
||||
subjectAr: 'العلوم الحياتية',
|
||||
lessonAr: 'نمذجة الفيروسات (آكل البكتيريا)',
|
||||
);
|
||||
|
||||
const LabIdentity kBiologyMicroscopeToolIdentity = LabIdentity(
|
||||
toolKey: 'biology_microscope',
|
||||
subjectAr: 'العلوم الحياتية',
|
||||
lessonAr: 'المجهر المركب والخلايا',
|
||||
);
|
||||
|
||||
const LabIdentity kEarthMohsHardnessToolIdentity = LabIdentity(
|
||||
toolKey: 'earth_mohs_hardness',
|
||||
subjectAr: 'علوم الأرض والبيئة',
|
||||
lessonAr: 'المعادن واختبار موهس',
|
||||
);
|
||||
|
||||
const LabIdentity kEarthStratigraphyToolIdentity = LabIdentity(
|
||||
toolKey: 'earth_stratigraphy',
|
||||
subjectAr: 'علوم الأرض والبيئة',
|
||||
lessonAr: 'التأريخ النسبي والطبقات',
|
||||
);
|
||||
|
||||
const LabIdentity kEnglishPhoneticsToolIdentity = LabIdentity(
|
||||
toolKey: 'english_phonetics_ipa',
|
||||
subjectAr: 'اللغة الإنجليزية',
|
||||
lessonAr: 'الصوتيات و IPA',
|
||||
);
|
||||
|
||||
const LabIdentity kEnglishTenseToolIdentity = LabIdentity(
|
||||
toolKey: 'english_tense_timeline',
|
||||
subjectAr: 'اللغة الإنجليزية',
|
||||
lessonAr: 'الأزمنة وبناء الجمل',
|
||||
);
|
||||
|
||||
const LabIdentity kArabicSyntaxToolIdentity = LabIdentity(
|
||||
toolKey: 'arabic_syntax_tree',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'الإعراب التفاعلي',
|
||||
);
|
||||
|
||||
const LabIdentity kArabicProsodyToolIdentity = LabIdentity(
|
||||
toolKey: 'arabic_prosody',
|
||||
subjectAr: 'العربية لغتي',
|
||||
lessonAr: 'العروض والموسيقى الشعرية',
|
||||
);
|
||||
|
||||
const LabIdentity kIslamicTajweedToolIdentity = LabIdentity(
|
||||
toolKey: 'islamic_tajweed',
|
||||
subjectAr: 'التربية الإسلامية',
|
||||
lessonAr: 'التجويد ومخارج الحروف',
|
||||
);
|
||||
|
||||
const LabIdentity kIslamicInheritanceToolIdentity = LabIdentity(
|
||||
toolKey: 'islamic_inheritance',
|
||||
subjectAr: 'التربية الإسلامية',
|
||||
lessonAr: 'المواريث والفرائض',
|
||||
);
|
||||
|
||||
const LabIdentity kFinanceBudgetToolIdentity = LabIdentity(
|
||||
toolKey: 'finance_budget',
|
||||
subjectAr: 'الثقافة المالية',
|
||||
lessonAr: 'الموازنة الشخصية والاستثمار',
|
||||
);
|
||||
|
||||
const LabIdentity kHistoryJordanChronologyToolIdentity = LabIdentity(
|
||||
toolKey: 'history_jordan_chronology',
|
||||
subjectAr: 'تاريخ الأردن',
|
||||
lessonAr: 'التسلسل الزمني التفاعلي',
|
||||
);
|
||||
|
||||
const LabIdentity kDigitalFlowchartToolIdentity = LabIdentity(
|
||||
toolKey: 'digital_flowchart',
|
||||
subjectAr: 'المهارات الرقمية',
|
||||
lessonAr: 'الخوارزميات والمخططات',
|
||||
);
|
||||
|
||||
const LabIdentity kDigitalSortingToolIdentity = LabIdentity(
|
||||
toolKey: 'digital_sorting',
|
||||
subjectAr: 'المهارات الرقمية',
|
||||
lessonAr: 'المصفوفات والفرز',
|
||||
);
|
||||
@@ -0,0 +1,833 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import 'lab_identity.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// SAQEL VIRTUAL LABS — SHARED CORE SCAFFOLD
|
||||
/// ============================================================================
|
||||
/// Enforces the mandatory architecture for every Grade-10 lesson lab:
|
||||
///
|
||||
/// 1. 2/3 vs 1/3 rule:
|
||||
/// - wide (>=760px, tablet/desktop/landscape): horizontal Row,
|
||||
/// left canvas flex:2, right controls panel 320-360px.
|
||||
/// - narrow (portrait phone): canvas pinned on top at 45-50% of
|
||||
/// viewport height, scrollable controls below. Canvas never scrolls
|
||||
/// away while sliders are adjusted.
|
||||
/// 2. Apple Cupertino luxury dark aesthetic:
|
||||
/// Deep void #07111F -> #0B1728, glass #102035, neon borders with
|
||||
/// AppColors.saqelCyan / emeraldGreen at 0.25 alpha.
|
||||
/// 3. Pure CustomPainter canvases, 120fps-friendly, zero binary assets.
|
||||
/// 4. Socratic checkpoint hook + tactile haptic feedback.
|
||||
///
|
||||
/// Every per-lesson widget is a standalone StatefulWidget named
|
||||
/// `<Subject><Topic>LabView` and composes this scaffold.
|
||||
///
|
||||
/// Curriculum identity is carried by a [LabIdentity] (single source of truth,
|
||||
/// defined once in `lab_identity.dart` and reused by the entry registry).
|
||||
/// The header always states whether the lab is published, bound-but-draft
|
||||
/// (awaiting academic review), or a standalone authoring tool. No demo
|
||||
/// success, income or mastery is ever hard-coded here.
|
||||
/// ============================================================================
|
||||
|
||||
/// Socratic hook: (question, options, correctIndex).
|
||||
typedef LabCheckpointCallback = void Function(
|
||||
String question,
|
||||
List<String> options,
|
||||
int correctIdx,
|
||||
);
|
||||
|
||||
/// True when the OS requests reduced motion; animations should then be zeroed.
|
||||
bool saqelReducedMotion(BuildContext context) =>
|
||||
MediaQuery.maybeOf(context)?.disableAnimations ?? false;
|
||||
|
||||
/// Animation duration honouring reduced-motion accessibility.
|
||||
Duration saqelAnimMs(BuildContext context, [int ms = 180]) =>
|
||||
saqelReducedMotion(context) ? Duration.zero : Duration(milliseconds: ms);
|
||||
|
||||
/// Dark void gradient used by every lab.
|
||||
const LinearGradient kSaqelVoidGradient = LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF07111F), Color(0xFF0B1728)],
|
||||
);
|
||||
|
||||
/// Glass card surface mandated by the brief.
|
||||
const Color kSaqelGlass = Color(0xFF102035);
|
||||
|
||||
/// Standard scaffold: canvas (2/3) + controls (1/3).
|
||||
class SaqelLabScaffold extends StatelessWidget {
|
||||
final String titleAr;
|
||||
final String subtitleAr;
|
||||
final LabIdentity? identity;
|
||||
final Widget canvas;
|
||||
final List<Widget> controls;
|
||||
final List<Widget>? telemetry;
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
final String? checkpointQuestion;
|
||||
final List<String>? checkpointOptions;
|
||||
final int? checkpointCorrectIdx;
|
||||
final double canvasAspectWide;
|
||||
final String? footerNote;
|
||||
|
||||
const SaqelLabScaffold({
|
||||
super.key,
|
||||
required this.titleAr,
|
||||
required this.subtitleAr,
|
||||
this.identity,
|
||||
required this.canvas,
|
||||
required this.controls,
|
||||
this.telemetry,
|
||||
this.onCheckpointTriggered,
|
||||
this.checkpointQuestion,
|
||||
this.checkpointOptions,
|
||||
this.checkpointCorrectIdx,
|
||||
this.canvasAspectWide = 16 / 10,
|
||||
this.footerNote,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(gradient: kSaqelVoidGradient),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final wide = constraints.maxWidth >= 760;
|
||||
if (wide) {
|
||||
return Column(
|
||||
children: [
|
||||
_Header(
|
||||
titleAr: titleAr,
|
||||
subtitleAr: subtitleAr,
|
||||
identity: identity,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 0, 14, 14),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// LEFT 2/3 : live interactive canvas.
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _CanvasCard(
|
||||
canvas: canvas,
|
||||
telemetry: telemetry,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// RIGHT 1/3 : control & parameter panel.
|
||||
SizedBox(
|
||||
width: 340,
|
||||
child: _ControlsPanel(
|
||||
controls: controls,
|
||||
footerNote: footerNote,
|
||||
checkpointSection: _checkpointSection(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
// Portrait phone: pinned canvas (46% viewport) + scrollable panel.
|
||||
final viewportH = MediaQuery.of(context).size.height;
|
||||
final canvasH = (viewportH * 0.46).clamp(300.0, 460.0);
|
||||
return Column(
|
||||
children: [
|
||||
_Header(
|
||||
titleAr: titleAr,
|
||||
subtitleAr: subtitleAr,
|
||||
identity: identity,
|
||||
),
|
||||
SizedBox(
|
||||
height: canvasH,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
||||
child: _CanvasCard(
|
||||
canvas: canvas,
|
||||
telemetry: telemetry,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _ControlsPanel(
|
||||
controls: controls,
|
||||
footerNote: footerNote,
|
||||
checkpointSection: _checkpointSection(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _checkpointSection(BuildContext context) {
|
||||
if (checkpointQuestion == null ||
|
||||
checkpointOptions == null ||
|
||||
checkpointCorrectIdx == null) {
|
||||
return null;
|
||||
}
|
||||
return LabCheckpointButton(
|
||||
question: checkpointQuestion!,
|
||||
options: checkpointOptions!,
|
||||
correctIdx: checkpointCorrectIdx!,
|
||||
onCheckpointTriggered: onCheckpointTriggered,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
final String titleAr;
|
||||
final String subtitleAr;
|
||||
final LabIdentity? identity;
|
||||
|
||||
const _Header({
|
||||
required this.titleAr,
|
||||
required this.subtitleAr,
|
||||
this.identity,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final id = identity;
|
||||
final origin =
|
||||
id == null ? 'بيئة تمرين بدون هوية منهجية' : id.displayOrigin;
|
||||
late final Color badgeColor;
|
||||
late final String badgeLabel;
|
||||
if (id == null) {
|
||||
badgeColor = const Color(0xFFFF9F0A);
|
||||
badgeLabel = 'بدون هوية';
|
||||
} else if (id.isPublished) {
|
||||
badgeColor = const Color(0xFF30D158);
|
||||
badgeLabel = 'منشور';
|
||||
} else if (id.isBound) {
|
||||
badgeColor = const Color(0xFFFF9F0A);
|
||||
badgeLabel = 'مسودة – بمراجعة أكاديمية';
|
||||
} else {
|
||||
badgeColor = const Color(0xFF0071E3);
|
||||
badgeLabel = 'أداة معاينة';
|
||||
}
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF00F5D4), Color(0xFF0071E3)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(CupertinoIcons.lab_flask_solid,
|
||||
color: Colors.black, size: 20),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(titleAr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 14.5)),
|
||||
const SizedBox(height: 2),
|
||||
Text(subtitleAr,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 11.5)),
|
||||
const SizedBox(height: 2),
|
||||
Text(origin,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF00F5D4),
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Semantics(
|
||||
label: 'حالة المختبر: $badgeLabel',
|
||||
container: true,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: badgeColor.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: badgeColor.withValues(alpha: 0.35)),
|
||||
),
|
||||
child: Text(badgeLabel,
|
||||
style: TextStyle(
|
||||
color: badgeColor,
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w800)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CanvasCard extends StatelessWidget {
|
||||
final Widget canvas;
|
||||
final List<Widget>? telemetry;
|
||||
|
||||
const _CanvasCard({required this.canvas, this.telemetry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: kSaqelGlass,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.25), width: 1),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x66000000), blurRadius: 24, offset: Offset(0, 10)),
|
||||
],
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: canvas),
|
||||
if (telemetry != null && telemetry!.isNotEmpty)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.30),
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.25))),
|
||||
),
|
||||
child: Wrap(spacing: 8, runSpacing: 6, children: telemetry!),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ControlsPanel extends StatelessWidget {
|
||||
final List<Widget> controls;
|
||||
final String? footerNote;
|
||||
final Widget? checkpointSection;
|
||||
|
||||
const _ControlsPanel({
|
||||
required this.controls,
|
||||
this.footerNote,
|
||||
this.checkpointSection,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: kSaqelGlass.withValues(alpha: 0.92),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: AppColors.emeraldGreen.withValues(alpha: 0.25), width: 1),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(14),
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.slider_horizontal_3,
|
||||
color: AppColors.saqelCyan, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('لوحة التحكم والضبط',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 13)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
...controls,
|
||||
if (checkpointSection != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
checkpointSection!,
|
||||
],
|
||||
if (footerNote != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
Text(footerNote!,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF64748B), fontSize: 10.5, height: 1.6)),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reusable control primitives (sliders, toggles, readouts)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Tactile helper: light haptic tick on interaction end.
|
||||
void saqelTick() {
|
||||
HapticFeedback.lightImpact();
|
||||
}
|
||||
|
||||
/// Smooth slider with live numeric readout.
|
||||
class LabSlider extends StatelessWidget {
|
||||
final String label;
|
||||
final double value;
|
||||
final double min;
|
||||
final double max;
|
||||
final String display;
|
||||
final Color accent;
|
||||
final ValueChanged<double> onChanged;
|
||||
final int? divisions;
|
||||
|
||||
const LabSlider({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.min,
|
||||
required this.max,
|
||||
required this.display,
|
||||
required this.onChanged,
|
||||
this.accent = AppColors.saqelCyan,
|
||||
this.divisions,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(label,
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withValues(alpha: 0.14),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: accent.withValues(alpha: 0.35)),
|
||||
),
|
||||
child: Text(display,
|
||||
style: TextStyle(
|
||||
color: accent,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12)),
|
||||
),
|
||||
],
|
||||
),
|
||||
CupertinoSlider(
|
||||
value: value.clamp(min, max),
|
||||
min: min,
|
||||
max: max,
|
||||
divisions: divisions,
|
||||
activeColor: accent,
|
||||
thumbColor: Colors.white,
|
||||
onChanged: onChanged,
|
||||
onChangeEnd: (_) => saqelTick(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// iOS-style toggle row.
|
||||
class LabToggle extends StatelessWidget {
|
||||
final String label;
|
||||
final String? hint;
|
||||
final bool value;
|
||||
final Color accent;
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
const LabToggle({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.hint,
|
||||
this.accent = AppColors.saqelCyan,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700)),
|
||||
if (hint != null)
|
||||
Text(hint!,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 11)),
|
||||
],
|
||||
),
|
||||
),
|
||||
CupertinoSwitch(
|
||||
value: value,
|
||||
activeTrackColor: accent,
|
||||
onChanged: (v) {
|
||||
saqelTick();
|
||||
onChanged(v);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Segmented selector (2-4 mutually exclusive options).
|
||||
class LabSegments<T> extends StatelessWidget {
|
||||
final List<String> labels;
|
||||
final List<T> values;
|
||||
final T current;
|
||||
final ValueChanged<T> onSelected;
|
||||
final Color accent;
|
||||
|
||||
const LabSegments({
|
||||
super.key,
|
||||
required this.labels,
|
||||
required this.values,
|
||||
required this.current,
|
||||
required this.onSelected,
|
||||
this.accent = AppColors.saqelCyan,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.30),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
|
||||
),
|
||||
child: Row(
|
||||
children: List.generate(labels.length, (i) {
|
||||
final selected = values[i] == current;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
saqelTick();
|
||||
onSelected(values[i]);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: saqelAnimMs(context, 180),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? accent : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(labels[i],
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: selected ? Colors.black : Colors.white70,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 11.5)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Small telemetry pill shown under the canvas.
|
||||
class LabPill extends StatelessWidget {
|
||||
final String text;
|
||||
final Color color;
|
||||
|
||||
const LabPill(this.text, {super.key, this.color = AppColors.saqelCyan});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.14),
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
border: Border.all(color: color.withValues(alpha: 0.35)),
|
||||
),
|
||||
child: Text(text,
|
||||
style: TextStyle(
|
||||
color: color, fontSize: 11, fontWeight: FontWeight.w800)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Formula / law card shown in controls.
|
||||
class LabFormulaCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String body;
|
||||
final Color accent;
|
||||
|
||||
const LabFormulaCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.body,
|
||||
this.accent = AppColors.emeraldGreen,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withValues(alpha: 0.10),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: accent.withValues(alpha: 0.30)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
color: accent, fontWeight: FontWeight.w800, fontSize: 12.5)),
|
||||
const SizedBox(height: 6),
|
||||
Text(body,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 12, height: 1.7)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checkpoint trigger button. Calls the host hook; if no host hook is
|
||||
/// provided it shows an inline honest practice dialog (client-side only,
|
||||
/// never recorded as server mastery).
|
||||
class LabCheckpointButton extends StatelessWidget {
|
||||
final String question;
|
||||
final List<String> options;
|
||||
final int correctIdx;
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
|
||||
const LabCheckpointButton({
|
||||
super.key,
|
||||
required this.question,
|
||||
required this.options,
|
||||
required this.correctIdx,
|
||||
this.onCheckpointTriggered,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.question_circle_fill, size: 17),
|
||||
label: const Text('فحص الفهم والاستيعاب',
|
||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 13)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF0071E3),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(13)),
|
||||
),
|
||||
onPressed: () {
|
||||
saqelTick();
|
||||
if (onCheckpointTriggered != null) {
|
||||
onCheckpointTriggered!(question, options, correctIdx);
|
||||
return;
|
||||
}
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (ctx) => _InlineCheckpointDialog(
|
||||
question: question,
|
||||
options: options,
|
||||
correctIdx: correctIdx,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InlineCheckpointDialog extends StatefulWidget {
|
||||
final String question;
|
||||
final List<String> options;
|
||||
final int correctIdx;
|
||||
|
||||
const _InlineCheckpointDialog({
|
||||
required this.question,
|
||||
required this.options,
|
||||
required this.correctIdx,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_InlineCheckpointDialog> createState() =>
|
||||
_InlineCheckpointDialogState();
|
||||
}
|
||||
|
||||
class _InlineCheckpointDialogState extends State<_InlineCheckpointDialog> {
|
||||
int? _picked;
|
||||
bool _revealed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: const Text('نقطة فهم — تدريب ذاتي'),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Text(widget.question,
|
||||
style:
|
||||
const TextStyle(fontSize: 13.5, fontWeight: FontWeight.w700)),
|
||||
const SizedBox(height: 10),
|
||||
...List.generate(widget.options.length, (i) {
|
||||
final selected = _picked == i;
|
||||
Color bg = Colors.transparent;
|
||||
if (_revealed && i == widget.correctIdx) {
|
||||
bg = const Color(0xFF30D158).withValues(alpha: 0.18);
|
||||
} else if (_revealed && selected) {
|
||||
bg = const Color(0xFFFF375F).withValues(alpha: 0.15);
|
||||
} else if (selected) {
|
||||
bg = const Color(0xFF00F5D4).withValues(alpha: 0.12);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: _revealed ? null : () => setState(() => _picked = i),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 9),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? const Color(0xFF00F5D4)
|
||||
: const Color(0xFFCBD5E1)),
|
||||
),
|
||||
child: Text(widget.options[i],
|
||||
style: const TextStyle(fontSize: 12.5)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
if (_revealed)
|
||||
Text(
|
||||
_picked == widget.correctIdx
|
||||
? 'إجابة صحيحة في هذا التدريب الذاتي — الإتقان الحقيقي يُحتسب على الخادم من محاولات موثقة.'
|
||||
: 'إجابة غير صحيحة — راجع المحاكاة وحاول مجدداً. هذا تدريب ذاتي ولا يُسجَّل إتقاناً.',
|
||||
style: const TextStyle(
|
||||
fontSize: 11, color: Color(0xFF64748B), height: 1.6),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: const Text('إغلاق'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
onPressed:
|
||||
_picked == null ? null : () => setState(() => _revealed = true),
|
||||
child: Text(_revealed ? 'تم الكشف' : 'تحقق'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Play / pause + reset row shared by animated labs.
|
||||
class LabTransportRow extends StatelessWidget {
|
||||
final bool playing;
|
||||
final VoidCallback onToggle;
|
||||
final VoidCallback onReset;
|
||||
final String playLabel;
|
||||
final String timeLabel;
|
||||
|
||||
const LabTransportRow({
|
||||
super.key,
|
||||
required this.playing,
|
||||
required this.onToggle,
|
||||
required this.onReset,
|
||||
this.playLabel = 'تشغيل',
|
||||
this.timeLabel = '',
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(
|
||||
playing ? CupertinoIcons.pause_fill : CupertinoIcons.play_fill,
|
||||
size: 15),
|
||||
label: Text(playing ? 'إيقاف مؤقت' : playLabel,
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.w800, fontSize: 12)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.saqelCyan,
|
||||
foregroundColor: Colors.black,
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(11)),
|
||||
),
|
||||
onPressed: () {
|
||||
saqelTick();
|
||||
onToggle();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.refresh, size: 15),
|
||||
label: const Text('صفّر',
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(11)),
|
||||
),
|
||||
onPressed: () {
|
||||
saqelTick();
|
||||
onReset();
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
if (timeLabel.isNotEmpty)
|
||||
Text(timeLabel,
|
||||
style: const TextStyle(
|
||||
color: AppColors.saqelCyan,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 14)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../data/models/socratic_checkpoint_model.dart';
|
||||
import '../../widgets/socratic_dialog.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
import 'labs_registry.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// VIRTUAL LABS GALLERY — Grade 10 (all subjects, all lessons)
|
||||
/// Authoring-preview surface. Showcases the 33 lesson-bound labs and
|
||||
/// standalone authoring tools on the mandatory 2/3-canvas + 1/3-controls
|
||||
/// scaffold. Statuses are honest: a lab carrying no released+published bundle
|
||||
/// stays "مسودة — بمراجعة أكاديمية" and unbound tools stay "أداة معاينة".
|
||||
/// Nothing here is shown to students as mastered or published.
|
||||
/// ============================================================================
|
||||
class VirtualLabsGalleryScreen extends StatefulWidget {
|
||||
final String? initialSubject;
|
||||
const VirtualLabsGalleryScreen({super.key, this.initialSubject});
|
||||
|
||||
@override
|
||||
State<VirtualLabsGalleryScreen> createState() =>
|
||||
_VirtualLabsGalleryScreenState();
|
||||
}
|
||||
|
||||
class _VirtualLabsGalleryScreenState extends State<VirtualLabsGalleryScreen> {
|
||||
String? filter;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
filter = widget.initialSubject;
|
||||
}
|
||||
|
||||
void _openLab(BuildContext context, Grade10LabEntry entry) {
|
||||
LabCheckpointCallback hook = (q, opts, correct) {
|
||||
final checkpoint = SocraticCheckpointModel(
|
||||
id: entry.id.hashCode & 0x7fffffff,
|
||||
questionText: q,
|
||||
timestampSeconds: 0,
|
||||
hint: 'راجع المحاكاة والقراءات الحية قبل الإجابة.',
|
||||
options: List.generate(
|
||||
opts.length,
|
||||
(i) => SocraticOptionModel(
|
||||
id: i,
|
||||
text: opts[i],
|
||||
isCorrect: i == correct,
|
||||
),
|
||||
),
|
||||
);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => SocraticCheckpointModal(
|
||||
checkpoint: checkpoint,
|
||||
onOptionSelected: (_) {},
|
||||
),
|
||||
);
|
||||
};
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (_) => Scaffold(
|
||||
backgroundColor: const Color(0xFF07111F),
|
||||
appBar: AppBar(
|
||||
backgroundColor: const Color(0xFF0B1728),
|
||||
foregroundColor: Colors.white,
|
||||
title: Text(entry.lessonAr,
|
||||
style:
|
||||
const TextStyle(fontSize: 15, fontWeight: FontWeight.w800)),
|
||||
leading: IconButton(
|
||||
icon: const Icon(CupertinoIcons.back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
body: SafeArea(child: entry.builder(hook)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subjects = Grade10LabsRegistry.subjects;
|
||||
final shown = filter == null
|
||||
? Grade10LabsRegistry.all
|
||||
: Grade10LabsRegistry.bySubject(filter!);
|
||||
return Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(gradient: kSaqelVoidGradient),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 6),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(CupertinoIcons.lab_flask_solid,
|
||||
color: AppColors.saqelCyan, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('المختبرات الافتراضية — الصف العاشر',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15)),
|
||||
Text(
|
||||
'بيئة معاينة تأليفية • الربط بمعرّف منهج صريح • 2/3 لوحة حية + 1/3 تحكم',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 11.5)),
|
||||
],
|
||||
),
|
||||
),
|
||||
LabPill('${Grade10LabsRegistry.all.length} معاينة',
|
||||
color: const Color(0xFF0071E3)),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 42,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
itemCount: subjects.length + 1,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
||||
itemBuilder: (ctx, i) {
|
||||
if (i == 0) {
|
||||
final sel = filter == null;
|
||||
return _chip(
|
||||
'الكل', sel, () => setState(() => filter = null));
|
||||
}
|
||||
final s = subjects[i - 1];
|
||||
return _chip(
|
||||
s, filter == s, () => setState(() => filter = s));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(14, 0, 14, 20),
|
||||
itemCount: shown.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final e = shown[i];
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.25)),
|
||||
),
|
||||
child: Material(
|
||||
color: kSaqelGlass,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 14, vertical: 6),
|
||||
leading: Container(
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF00F5D4), Color(0xFF0071E3)]),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Center(
|
||||
child: Text('${i + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15)),
|
||||
),
|
||||
),
|
||||
title: Text(e.lessonAr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 13.5)),
|
||||
subtitle: Text(
|
||||
'${e.subjectAr} • ${e.identity.displayOrigin}',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 11)),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
LabPill(e.statusLabel, color: _statusColor(e)),
|
||||
const SizedBox(width: 6),
|
||||
const Icon(CupertinoIcons.chevron_back,
|
||||
color: AppColors.saqelCyan, size: 18),
|
||||
],
|
||||
),
|
||||
onTap: () => _openLab(context, e),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _statusColor(Grade10LabEntry e) {
|
||||
if (e.isPublished) return const Color(0xFF30D158);
|
||||
if (e.isBound) return const Color(0xFFFF9F0A);
|
||||
return const Color(0xFF0071E3);
|
||||
}
|
||||
|
||||
Widget _chip(String label, bool sel, VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
saqelTick();
|
||||
onTap();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
sel ? AppColors.saqelCyan : Colors.white.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(11),
|
||||
border: Border.all(color: sel ? AppColors.saqelCyan : Colors.white12),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(label,
|
||||
style: TextStyle(
|
||||
color: sel ? Colors.black : Colors.white70,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../data/models/socratic_checkpoint_model.dart';
|
||||
import '../../widgets/socratic_dialog.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
import 'physics_labs.dart';
|
||||
import 'chemistry_labs.dart';
|
||||
import 'biology_labs.dart';
|
||||
import 'earth_labs.dart';
|
||||
import 'math_labs.dart';
|
||||
import 'english_labs.dart';
|
||||
import 'islamic_labs.dart';
|
||||
import 'finance_labs.dart';
|
||||
import 'history_digital_labs.dart';
|
||||
import 'history_labs.dart';
|
||||
import 'geography_labs.dart';
|
||||
import 'civics_labs.dart';
|
||||
import 'arabic_labs.dart';
|
||||
import 'arabic_listening_lab.dart';
|
||||
import 'arabic_apology_lab.dart';
|
||||
import 'arabic_quranic_apology_lab.dart';
|
||||
import 'arabic_apology_letter_lab.dart';
|
||||
import 'arabic_conditional_lab.dart';
|
||||
import 'arabic_informative_style_lab.dart';
|
||||
import 'arabic_unit2_listening_lab.dart';
|
||||
import 'arabic_unit2_vocative_lab.dart';
|
||||
import 'arabic_unit2_writing_lab.dart';
|
||||
import 'arabic_unit2_poetry_lab.dart';
|
||||
import 'arabic_unit2_speaking_lab.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// GRADE-10 VIRTUAL LABS REGISTRY
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Single source of truth. Every entry carries a [LabIdentity]:
|
||||
/// - bound entries reference an EXPLICIT `curriculum_lesson_id` from the
|
||||
/// approved syllabus package (never guessed from a title or `lesson_01`),
|
||||
/// - unbound entries are standalone authoring tools with `toolKey` only.
|
||||
///
|
||||
/// A lab is shown to a student ONLY through [publishedEntryForCurriculumLessonId]
|
||||
/// — bound, released, AND carrying a server-provided published bundle version.
|
||||
/// There is no keyword matching, no fallback-to-first-lab, and no demo success.
|
||||
/// ============================================================================
|
||||
|
||||
class Grade10LabEntry {
|
||||
final LabIdentity identity;
|
||||
final Widget Function(LabCheckpointCallback?) builder;
|
||||
|
||||
const Grade10LabEntry({required this.identity, required this.builder});
|
||||
|
||||
String get id => identity.curriculumLessonId.isNotEmpty
|
||||
? identity.curriculumLessonId
|
||||
: identity.toolKey;
|
||||
String get subjectAr => identity.subjectAr;
|
||||
String get lessonAr => identity.lessonAr;
|
||||
bool get isBound => identity.isBound;
|
||||
bool get isPublished => identity.isPublished;
|
||||
String get statusLabel => identity.statusLabel;
|
||||
}
|
||||
|
||||
class Grade10LabsRegistry {
|
||||
Grade10LabsRegistry._();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bound lesson labs (17) and standalone authoring tools (16).
|
||||
// ---------------------------------------------------------------------------
|
||||
static final List<Grade10LabEntry> all = [
|
||||
// ---- Physics (4: 1 bound + 3 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kPhysicsVectorAdditionLabIdentity,
|
||||
builder: (cb) => PhysicsVectorAdditionLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kPhysicsMotion1DLabIdentity,
|
||||
builder: (cb) => PhysicsMotion1DLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kPhysicsCircularMotionLabIdentity,
|
||||
builder: (cb) => PhysicsCircularMotionLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kPhysicsNewtonInclineLabIdentity,
|
||||
builder: (cb) => PhysicsNewtonInclineLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Chemistry (4: 1 tool + 3 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kChemistryBohrAtomLabIdentity,
|
||||
builder: (cb) => ChemistryBohrAtomLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kChemistryElectronConfigLabIdentity,
|
||||
builder: (cb) =>
|
||||
ChemistryElectronConfigLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kChemistryFlameTestToolIdentity,
|
||||
builder: (cb) => ChemistryFlameTestLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kChemistryLewisBondingLabIdentity,
|
||||
builder: (cb) => ChemistryLewisBondingLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Biology (4: 3 tools + 1 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kBiologyDarwinFinchesToolIdentity,
|
||||
builder: (cb) => BiologyDarwinFinchesLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kBiologyBacteriophageToolIdentity,
|
||||
builder: (cb) => BiologyBacteriophageLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kBiologyDichotomousKeyLabIdentity,
|
||||
builder: (cb) => BiologyDichotomousKeyLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kBiologyMicroscopeToolIdentity,
|
||||
builder: (cb) => BiologyMicroscopeLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Earth (3: 2 tools + 1 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kEarthMohsHardnessToolIdentity,
|
||||
builder: (cb) => EarthMohsHardnessLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kEarthRockCycleLabIdentity,
|
||||
builder: (cb) => EarthRockCycleLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kEarthStratigraphyToolIdentity,
|
||||
builder: (cb) => EarthStratigraphyLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Math (3 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kMathSystemsLabIdentity,
|
||||
builder: (cb) => MathSystemsLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kMathCircleChordsLabIdentity,
|
||||
builder: (cb) => MathCircleChordsLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kMathUnitCircleLabIdentity,
|
||||
builder: (cb) => MathUnitCircleLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- English (2 tools) ----
|
||||
Grade10LabEntry(
|
||||
identity: kEnglishPhoneticsToolIdentity,
|
||||
builder: (cb) => EnglishPhoneticsLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kEnglishTenseToolIdentity,
|
||||
builder: (cb) => EnglishTenseTimelineLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Arabic (13: 2 tools + 11 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kArabicUnit2VocativeLabIdentity,
|
||||
builder: (cb) => ArabicUnit2VocativeLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicUnit2WritingLabIdentity,
|
||||
builder: (cb) => ArabicUnit2WritingLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicUnit2PoetryLabIdentity,
|
||||
builder: (cb) => ArabicUnit2PoetryLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicUnit2SpeakingLabIdentity,
|
||||
builder: (cb) => ArabicUnit2SpeakingLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicUnit2ListeningLabIdentity,
|
||||
builder: (cb) => ArabicUnit2ListeningLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicInformativeStyleLabIdentity,
|
||||
builder: (cb) => ArabicInformativeStyleLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicConditionalLabIdentity,
|
||||
builder: (cb) => ArabicConditionalLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicApologyLetterLabIdentity,
|
||||
builder: (cb) => ArabicApologyLetterLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicQuranicApologyLabIdentity,
|
||||
builder: (cb) =>
|
||||
ArabicQuranicApologyReadingLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicApologyLabIdentity,
|
||||
builder: (cb) => ArabicApologyLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicListeningLabIdentity,
|
||||
builder: (cb) =>
|
||||
ArabicListeningAttentionLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicSyntaxToolIdentity,
|
||||
builder: (cb) => ArabicSyntaxLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kArabicProsodyToolIdentity,
|
||||
builder: (cb) => ArabicProsodyLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Islamic (2 tools) ----
|
||||
Grade10LabEntry(
|
||||
identity: kIslamicTajweedToolIdentity,
|
||||
builder: (cb) => IslamicTajweedLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kIslamicInheritanceToolIdentity,
|
||||
builder: (cb) => IslamicInheritanceLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Finance (3: 1 tool + 2 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kFinanceProjectManagementLabIdentity,
|
||||
builder: (cb) =>
|
||||
FinanceProjectManagementLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kFinanceBudgetToolIdentity,
|
||||
builder: (cb) => FinanceBudgetLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kFinanceFeasibilityLabIdentity,
|
||||
builder: (cb) => FinanceFeasibilityLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- History (2: 1 tool + 1 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kHistoryPersianEmpireLabIdentity,
|
||||
builder: (cb) => HistoryPersianEmpireLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kHistoryJordanChronologyToolIdentity,
|
||||
builder: (cb) =>
|
||||
HistoryJordanChronologyLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Geography (1 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kGeographyAtmosphereLabIdentity,
|
||||
builder: (cb) => GeographyAtmosphereLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Civics (1 bound) ----
|
||||
Grade10LabEntry(
|
||||
identity: kCivicsActiveCitizenshipLabIdentity,
|
||||
builder: (cb) =>
|
||||
CivicsActiveCitizenshipLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
// ---- Digital skills (2 tools) ----
|
||||
Grade10LabEntry(
|
||||
identity: kDigitalFlowchartToolIdentity,
|
||||
builder: (cb) => DigitalFlowchartLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
Grade10LabEntry(
|
||||
identity: kDigitalSortingToolIdentity,
|
||||
builder: (cb) => DigitalSortingLabView(onCheckpointTriggered: cb),
|
||||
),
|
||||
];
|
||||
|
||||
/// Labs bound to an explicit curriculum lesson.
|
||||
static List<Grade10LabEntry> get boundEntries =>
|
||||
all.where((e) => e.isBound).toList();
|
||||
|
||||
/// Standalone authoring tools with no curriculum-lesson anchor.
|
||||
static List<Grade10LabEntry> get authoringTools =>
|
||||
all.where((e) => !e.isBound).toList();
|
||||
|
||||
static List<String> get subjects {
|
||||
final seen = <String>[];
|
||||
for (final e in all) {
|
||||
if (!seen.contains(e.subjectAr)) seen.add(e.subjectAr);
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
static List<Grade10LabEntry> bySubject(String subjectAr) =>
|
||||
all.where((e) => e.subjectAr == subjectAr).toList();
|
||||
|
||||
/// Canonical subject name mapping (display/browse use only).
|
||||
static String normalizeSubject(String raw) {
|
||||
final s = raw.toLowerCase().trim();
|
||||
if (s.contains('فيزياء') || s.contains('physics')) return 'الفيزياء';
|
||||
if (s.contains('كيمياء') || s.contains('chemistry')) return 'الكيمياء';
|
||||
if (s.contains('حياتية') ||
|
||||
s.contains('أحياء') ||
|
||||
s.contains('احياء') ||
|
||||
s.contains('biology')) {
|
||||
return 'العلوم الحياتية';
|
||||
}
|
||||
if (s.contains('أرض') ||
|
||||
s.contains('ارض') ||
|
||||
s.contains('بيئة') ||
|
||||
s.contains('earth')) {
|
||||
return 'علوم الأرض والبيئة';
|
||||
}
|
||||
if (s.contains('رياضيات') || s.contains('math')) return 'الرياضيات';
|
||||
if (s.contains('إنجليزي') ||
|
||||
s.contains('انجليزي') ||
|
||||
s.contains('english')) {
|
||||
return 'اللغة الإنجليزية';
|
||||
}
|
||||
if (s.contains('عربي') || s.contains('ضاد') || s.contains('arabic')) {
|
||||
return 'العربية لغتي';
|
||||
}
|
||||
if (s.contains('إسلام') ||
|
||||
s.contains('اسلام') ||
|
||||
s.contains('دين') ||
|
||||
s.contains('islamic')) {
|
||||
return 'التربية الإسلامية';
|
||||
}
|
||||
if (s.contains('مالية') || s.contains('finance')) return 'الثقافة المالية';
|
||||
if (s.contains('جغرافيا') || s.contains('geography')) return 'الجغرافيا';
|
||||
if (s.contains('وطنية') || s.contains('مدنية') || s.contains('civics')) {
|
||||
return 'التربية الوطنية والمدنية';
|
||||
}
|
||||
if (s.contains('أردن') || s.contains('اردن')) return 'تاريخ الأردن';
|
||||
if (s.contains('تاريخ') || s.contains('history')) return 'التاريخ';
|
||||
if (s.contains('رقمية') || s.contains('حاسوب') || s.contains('digital')) {
|
||||
return 'المهارات الرقمية';
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
/// Browse listing for a subject (bound labs + unbound tools of that subject).
|
||||
static List<Grade10LabEntry> bySubjectNormalized(String rawSubject) {
|
||||
final norm = normalizeSubject(rawSubject);
|
||||
final direct = bySubject(norm);
|
||||
if (direct.isNotEmpty) return direct;
|
||||
return all
|
||||
.where((e) => e.subjectAr.contains(norm) || norm.contains(e.subjectAr))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Whether to surface draft / unreleased virtual labs for local inspection and review.
|
||||
/// Defaults to true in development so that the reviewer/teacher can immediately test the labs.
|
||||
static bool previewModeEnabled = true;
|
||||
|
||||
/// Exact identity lookup.
|
||||
/// When [previewModeEnabled] is active or [allowPreview] is true, returns the lab for testing.
|
||||
/// When false (strict production gate), returns ONLY when released & carrying a published bundle.
|
||||
static Grade10LabEntry? findForLesson({
|
||||
required String subjectTitle,
|
||||
required String lessonTitle,
|
||||
String? lessonId,
|
||||
String? curriculumLessonId,
|
||||
bool? allowPreview,
|
||||
}) {
|
||||
final usePreview = allowPreview ?? previewModeEnabled;
|
||||
if (usePreview) {
|
||||
if (curriculumLessonId != null && curriculumLessonId.isNotEmpty) {
|
||||
final exact = entryForCurriculumLessonId(curriculumLessonId);
|
||||
if (exact != null) return exact;
|
||||
}
|
||||
if (lessonId != null && lessonId.isNotEmpty) {
|
||||
for (final e in all) {
|
||||
if (e.identity.curriculumLessonId.contains(lessonId) ||
|
||||
e.identity.toolKey == lessonId ||
|
||||
e.identity.sourceMarkdown.contains(lessonId)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
final candidates = bySubjectNormalized(subjectTitle);
|
||||
if (candidates.isNotEmpty) {
|
||||
final t = lessonTitle.toLowerCase();
|
||||
for (final c in candidates) {
|
||||
if (t.contains(c.lessonAr.toLowerCase()) ||
|
||||
c.lessonAr.toLowerCase().contains(t)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
if (lessonId != null && lessonId.isNotEmpty) {
|
||||
for (final c in candidates) {
|
||||
if (c.identity.lessonKey == lessonId ||
|
||||
c.identity.curriculumLessonId.endsWith(lessonId)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidates.first;
|
||||
}
|
||||
}
|
||||
|
||||
return publishedEntryForCurriculumLessonId(curriculumLessonId ?? '');
|
||||
}
|
||||
|
||||
static Grade10LabEntry? entryForCurriculumLessonId(
|
||||
String curriculumLessonId) {
|
||||
final cid = curriculumLessonId.trim();
|
||||
if (cid.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
for (final e in all) {
|
||||
if (e.identity.curriculumLessonId == cid) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Student-facing gate: bound + released + server-published bundle.
|
||||
/// Until the server returns `publishedBundleVersion` nothing is published.
|
||||
static Grade10LabEntry? publishedEntryForCurriculumLessonId(
|
||||
String curriculumLessonId) {
|
||||
final entry = entryForCurriculumLessonId(curriculumLessonId);
|
||||
if (entry == null || !entry.identity.isPublished) {
|
||||
return null;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// Opens the lab full-screen with standard Cupertino navigation & Socratic
|
||||
/// checkpoint hook. Respects the identity header and status (published /
|
||||
/// draft-awaiting-academic-review / authoring-tool).
|
||||
static void openLab(BuildContext context, Grade10LabEntry entry) {
|
||||
void hook(String q, List<String> opts, int correct) {
|
||||
final checkpoint = SocraticCheckpointModel(
|
||||
id: entry.id.hashCode & 0x7fffffff,
|
||||
questionText: q,
|
||||
timestampSeconds: 0,
|
||||
hint: 'راجع المحاكاة والقراءات الحية في لوحة المختبر قبل الإجابة.',
|
||||
options: List.generate(
|
||||
opts.length,
|
||||
(i) => SocraticOptionModel(
|
||||
id: i,
|
||||
text: opts[i],
|
||||
isCorrect: i == correct,
|
||||
),
|
||||
),
|
||||
);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => SocraticCheckpointModal(
|
||||
checkpoint: checkpoint,
|
||||
onOptionSelected: (_) {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (_) => Scaffold(
|
||||
backgroundColor: const Color(0xFF07111F),
|
||||
appBar: AppBar(
|
||||
backgroundColor: const Color(0xFF0B1728),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
entry.lessonAr,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
entry.identity.displayOrigin,
|
||||
style:
|
||||
const TextStyle(fontSize: 10.5, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(CupertinoIcons.back, color: Colors.white),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
body: SafeArea(child: entry.builder(hook)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lab_identity.dart';
|
||||
import 'lab_scaffold.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// MATHEMATICS — GRADE 10
|
||||
/// 1. MathSystemsLabView — خط y=mx+b مع قطع y=ax²+bx+c + المميز Δ
|
||||
/// 2. MathCircleChordsLabView — أوتار الدائرة والقطاعات (L=rθ, A=½r²θ)
|
||||
/// 3. MathUnitCircleLabView — الدائرة المثلثية sin/cos/tan
|
||||
/// ============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1) LINE × PARABOLA SYSTEMS
|
||||
// ---------------------------------------------------------------------------
|
||||
class MathSystemsLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const MathSystemsLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<MathSystemsLabView> createState() => _MathSystemsLabViewState();
|
||||
}
|
||||
|
||||
class _MathSystemsLabViewState extends State<MathSystemsLabView> {
|
||||
double m = 1, b = 1, a = 1, pb = 0, pc = -3;
|
||||
double scale = 26;
|
||||
|
||||
// Solve ax²+bx+c = mx+b -> ax²+(pb-m)x+(pc-b)=0
|
||||
List<Offset> solutions() {
|
||||
final A = a, B = pb - m, C = pc - b;
|
||||
if (A.abs() < 1e-9) {
|
||||
if (B.abs() < 1e-9) return [];
|
||||
final x = -C / B;
|
||||
return [Offset(x, m * x + b)];
|
||||
}
|
||||
final d = B * B - 4 * A * C;
|
||||
if (d < 0) return [];
|
||||
if (d < 1e-9) {
|
||||
final x = -B / (2 * A);
|
||||
return [Offset(x, m * x + b)];
|
||||
}
|
||||
final s = math.sqrt(d);
|
||||
final x1 = (-B + s) / (2 * A), x2 = (-B - s) / (2 * A);
|
||||
return [Offset(x1, m * x1 + b), Offset(x2, m * x2 + b)];
|
||||
}
|
||||
|
||||
double discriminant() {
|
||||
final B = pb - m, C = pc - b;
|
||||
return B * B - 4 * a * C;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sols = solutions();
|
||||
final d = discriminant();
|
||||
final status = d < 0
|
||||
? 'لا تقاطع (Δ<0)'
|
||||
: d < 1e-9
|
||||
? 'مماس — حل واحد (Δ=0)'
|
||||
: 'قاطع — حلان (Δ>0)';
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'حل أنظمة خطية وتربيعية بيانياً',
|
||||
subtitleAr: 'حرّك المستقيم والقطع وشاهد نقاط التقاطع (x,y) والمميز Δ',
|
||||
identity: kMathSystemsLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'نظام y = x + 1 مع y = x² − 3. عدد الحلول الحقيقية؟',
|
||||
checkpointOptions: const ['حلان', 'حل واحد', 'لا حلول', 'أربعة حلول'],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('Δ = ${d.toStringAsFixed(2)}',
|
||||
color: d < 0 ? const Color(0xFFFF375F) : const Color(0xFF30D158)),
|
||||
LabPill(status),
|
||||
...sols.map((p) => LabPill(
|
||||
'(${p.dx.toStringAsFixed(2)}، ${p.dy.toStringAsFixed(2)})',
|
||||
color: const Color(0xFFFFD166))),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _SystemsPainter(
|
||||
m: m, b: b, a: a, pb: pb, pc: pc, scale: scale, sols: sols),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
const Text('المستقيم y = mx + b',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF00F5D4),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12.5)),
|
||||
LabSlider(
|
||||
label: 'الميل m',
|
||||
value: m,
|
||||
min: -3,
|
||||
max: 3,
|
||||
display: m.toStringAsFixed(2),
|
||||
onChanged: (v) => setState(() => m = v)),
|
||||
LabSlider(
|
||||
label: 'التقاطع b',
|
||||
value: b,
|
||||
min: -6,
|
||||
max: 6,
|
||||
display: b.toStringAsFixed(2),
|
||||
onChanged: (v) => setState(() => b = v)),
|
||||
const Divider(color: Colors.white12, height: 18),
|
||||
const Text('القطع y = ax² + bx + c',
|
||||
style: TextStyle(
|
||||
color: Color(0xFFFF9F0A),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12.5)),
|
||||
LabSlider(
|
||||
label: 'المعامل a',
|
||||
value: a,
|
||||
min: -2,
|
||||
max: 2,
|
||||
display: a.toStringAsFixed(2),
|
||||
accent: const Color(0xFFFF9F0A),
|
||||
onChanged: (v) => setState(() => a = v == 0 ? 0.1 : v)),
|
||||
LabSlider(
|
||||
label: 'المعامل b (للقطع)',
|
||||
value: pb,
|
||||
min: -4,
|
||||
max: 4,
|
||||
display: pb.toStringAsFixed(2),
|
||||
accent: const Color(0xFFFF9F0A),
|
||||
onChanged: (v) => setState(() => pb = v)),
|
||||
LabSlider(
|
||||
label: 'الثابت c',
|
||||
value: pc,
|
||||
min: -6,
|
||||
max: 6,
|
||||
display: pc.toStringAsFixed(2),
|
||||
accent: const Color(0xFFFF375F),
|
||||
onChanged: (v) => setState(() => pc = v)),
|
||||
LabSlider(
|
||||
label: 'التكبير',
|
||||
value: scale,
|
||||
min: 14,
|
||||
max: 44,
|
||||
display: '${scale.toInt()} px',
|
||||
accent: Colors.white70,
|
||||
onChanged: (v) => setState(() => scale = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabFormulaCard(
|
||||
title: status,
|
||||
body:
|
||||
'ax² + (b−m)x + (c−b) = 0\nΔ = (b−m)² − 4a(c−b) = ${d.toStringAsFixed(2)}\n${sols.isEmpty ? 'مجموعة الحل = ∅' : 'الحلول: ${sols.map((p) => '(${p.dx.toStringAsFixed(2)}، ${p.dy.toStringAsFixed(2)})').join(' • ')}'}',
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'الحل البياني تقريبي للرؤية؛ الحل الجبري الدقيق بالمميز والقانون العام.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SystemsPainter extends CustomPainter {
|
||||
final double m, b, a, pb, pc, scale;
|
||||
final List<Offset> sols;
|
||||
_SystemsPainter(
|
||||
{required this.m,
|
||||
required this.b,
|
||||
required this.a,
|
||||
required this.pb,
|
||||
required this.pc,
|
||||
required this.scale,
|
||||
required this.sols});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
final ox = s.width / 2, oy = s.height / 2;
|
||||
final grid = Paint()
|
||||
..color = Colors.white.withValues(alpha: 0.07)
|
||||
..strokeWidth = 1;
|
||||
for (double x = ox % scale; x < s.width; x += scale) {
|
||||
c.drawLine(Offset(x, 0), Offset(x, s.height), grid);
|
||||
}
|
||||
for (double y = oy % scale; y < s.height; y += scale) {
|
||||
c.drawLine(Offset(0, y), Offset(s.width, y), grid);
|
||||
}
|
||||
final axis = Paint()
|
||||
..color = Colors.white38
|
||||
..strokeWidth = 1.4;
|
||||
c.drawLine(Offset(0, oy), Offset(s.width, oy), axis);
|
||||
c.drawLine(Offset(ox, 0), Offset(ox, s.height), axis);
|
||||
Offset map(double x, double y) => Offset(ox + x * scale, oy - y * scale);
|
||||
// parabola
|
||||
final pp = Path();
|
||||
for (double px = 0; px <= s.width; px += 2) {
|
||||
final x = (px - ox) / scale;
|
||||
final y = a * x * x + pb * x + pc;
|
||||
final p = map(x, y);
|
||||
if (px == 0) {
|
||||
pp.moveTo(p.dx, p.dy);
|
||||
} else {
|
||||
pp.lineTo(p.dx, p.dy);
|
||||
}
|
||||
}
|
||||
c.drawPath(
|
||||
pp,
|
||||
Paint()
|
||||
..color = const Color(0xFFFF9F0A)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.4);
|
||||
// line
|
||||
final x0 = -ox / scale, x1 = (s.width - ox) / scale;
|
||||
c.drawLine(
|
||||
map(x0, m * x0 + b),
|
||||
map(x1, m * x1 + b),
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4)
|
||||
..strokeWidth = 2.4);
|
||||
// solutions
|
||||
for (final so in sols) {
|
||||
final p = map(so.dx, so.dy);
|
||||
c.drawCircle(p, 8,
|
||||
Paint()..color = const Color(0xFFFFD166).withValues(alpha: 0.30));
|
||||
c.drawCircle(p, 4.5, Paint()..color = const Color(0xFFFFD166));
|
||||
c.drawCircle(
|
||||
p,
|
||||
4.5,
|
||||
Paint()
|
||||
..color = Colors.black
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _SystemsPainter o) =>
|
||||
o.m != m ||
|
||||
o.b != b ||
|
||||
o.a != a ||
|
||||
o.pb != pb ||
|
||||
o.pc != pc ||
|
||||
o.scale != scale;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2) CIRCLE CHORDS & SECTORS
|
||||
// ---------------------------------------------------------------------------
|
||||
class MathCircleChordsLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const MathCircleChordsLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<MathCircleChordsLabView> createState() =>
|
||||
_MathCircleChordsLabViewState();
|
||||
}
|
||||
|
||||
class _MathCircleChordsLabViewState extends State<MathCircleChordsLabView> {
|
||||
double theta = 70; // central angle deg
|
||||
double chordPos = 0.4; // distance from center / r (-0.8..0.8)
|
||||
bool showTangent = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const r = 100.0;
|
||||
final th = theta * math.pi / 180;
|
||||
final chordLen = 2 * r * math.sin(th / 2) / 100; // in "units" r=1
|
||||
final arc = th; // r=1
|
||||
final area = 0.5 * th;
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'أوتار الدائرة والقطاعات',
|
||||
subtitleAr:
|
||||
'حرّك الوتر والزاوية المركزية — الطول والقوس والمساحة تُحسب حياً',
|
||||
identity: kMathCircleChordsLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion:
|
||||
'في دائرة نصف قطرها r، طول القوس المقابل لزاوية θ (راديان) يساوي …',
|
||||
checkpointOptions: const ['L = r·θ', 'L = r/θ', 'L = r²·θ', 'L = 2r + θ'],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('الوتر = ${chordLen.toStringAsFixed(2)}·r'),
|
||||
LabPill('القوس = ${arc.toStringAsFixed(2)}·r',
|
||||
color: const Color(0xFFFF9F0A)),
|
||||
LabPill('القطاع = ${area.toStringAsFixed(2)}·r²',
|
||||
color: const Color(0xFF30D158)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _CircleChordsPainter(
|
||||
thetaDeg: theta, chordPos: chordPos, showTangent: showTangent),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'الزاوية المركزية θ',
|
||||
value: theta,
|
||||
min: 10,
|
||||
max: 180,
|
||||
display:
|
||||
'${theta.toInt()}° (${(theta * math.pi / 180).toStringAsFixed(2)} rad)',
|
||||
onChanged: (v) => setState(() => theta = v)),
|
||||
LabSlider(
|
||||
label: 'موضع الوتر (البعد عن المركز)',
|
||||
value: chordPos,
|
||||
min: -0.8,
|
||||
max: 0.8,
|
||||
display: chordPos.toStringAsFixed(2),
|
||||
accent: const Color(0xFF0071E3),
|
||||
onChanged: (v) => setState(() => chordPos = v)),
|
||||
LabToggle(
|
||||
label: 'المماس والعمود',
|
||||
hint: 'المماس ⊥ نصف القطر عند نقطة التماس',
|
||||
value: showTangent,
|
||||
onChanged: (v) => setState(() => showTangent = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabFormulaCard(
|
||||
title: 'قوانين القطاع (r = 1 للتبسيط)',
|
||||
body:
|
||||
'طول الوتر = 2·sin(θ/2) = ${chordLen.toStringAsFixed(3)}\nطول القوس L = r·θ = ${arc.toStringAsFixed(3)}\nمساحة القطاع A = ½·r²·θ = ${area.toStringAsFixed(3)}\nالوتر الأطول يقابل قوساً أكبر.',
|
||||
),
|
||||
],
|
||||
footerNote: 'الزاوية بالراديان في القوانين؛ الدرجات للعرض فقط.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CircleChordsPainter extends CustomPainter {
|
||||
final double thetaDeg, chordPos;
|
||||
final bool showTangent;
|
||||
_CircleChordsPainter(
|
||||
{required this.thetaDeg,
|
||||
required this.chordPos,
|
||||
required this.showTangent});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
final o = Offset(s.width / 2, s.height * 0.52);
|
||||
final r = math.min(s.width, s.height) * 0.36;
|
||||
c.drawCircle(o, r, Paint()..color = const Color(0xFF0F2036));
|
||||
c.drawCircle(
|
||||
o,
|
||||
r,
|
||||
Paint()
|
||||
..color = const Color(0xFF00F5D4)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2);
|
||||
c.drawCircle(o, 4, Paint()..color = Colors.white);
|
||||
final th = thetaDeg * math.pi / 180;
|
||||
final p1 = o + Offset(r * math.cos(-th / 2), r * math.sin(-th / 2));
|
||||
final p2 = o + Offset(r * math.cos(th / 2), r * math.sin(th / 2));
|
||||
// sector fill
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(o.dx, o.dy)
|
||||
..arcTo(Rect.fromCircle(center: o, radius: r), -th / 2, th, false)
|
||||
..close(),
|
||||
Paint()..color = const Color(0xFFFF9F0A).withValues(alpha: 0.22));
|
||||
// radii
|
||||
c.drawLine(
|
||||
o,
|
||||
p1,
|
||||
Paint()
|
||||
..color = const Color(0xFFFF9F0A)
|
||||
..strokeWidth = 2);
|
||||
c.drawLine(
|
||||
o,
|
||||
p2,
|
||||
Paint()
|
||||
..color = const Color(0xFFFF9F0A)
|
||||
..strokeWidth = 2);
|
||||
// chord (movable, perpendicular to x-axis at chordPos*r)
|
||||
final cx = o.dx + chordPos * r;
|
||||
final half = math.sqrt(math.max(0, r * r - (cx - o.dx) * (cx - o.dx)));
|
||||
final q1 = Offset(cx, o.dy - half), q2 = Offset(cx, o.dy + half);
|
||||
c.drawLine(
|
||||
q1,
|
||||
q2,
|
||||
Paint()
|
||||
..color = const Color(0xFF0071E3)
|
||||
..strokeWidth = 3.4
|
||||
..strokeCap = StrokeCap.round);
|
||||
c.drawCircle(q1, 4, Paint()..color = Colors.white);
|
||||
c.drawCircle(q2, 4, Paint()..color = Colors.white);
|
||||
// center-to-chord perpendicular
|
||||
c.drawLine(
|
||||
o,
|
||||
Offset(cx, o.dy),
|
||||
Paint()
|
||||
..color = Colors.white38
|
||||
..strokeWidth = 1.4);
|
||||
if (showTangent) {
|
||||
// tangent at p2 (perpendicular to radius)
|
||||
final dir = (p2 - o);
|
||||
final t = Offset(-dir.dy, dir.dx) / dir.distance;
|
||||
c.drawLine(
|
||||
p2 - t * 70,
|
||||
p2 + t * 70,
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..strokeWidth = 2.2);
|
||||
// right-angle marker
|
||||
c.drawRect(
|
||||
Rect.fromCenter(
|
||||
center: p2 - (dir / dir.distance) * 12 + t * 10,
|
||||
width: 10,
|
||||
height: 10),
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.6);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _CircleChordsPainter o) =>
|
||||
o.thetaDeg != thetaDeg ||
|
||||
o.chordPos != chordPos ||
|
||||
o.showTangent != showTangent;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3) UNIT CIRCLE TRIG
|
||||
// ---------------------------------------------------------------------------
|
||||
class MathUnitCircleLabView extends StatefulWidget {
|
||||
final LabCheckpointCallback? onCheckpointTriggered;
|
||||
const MathUnitCircleLabView({super.key, this.onCheckpointTriggered});
|
||||
|
||||
@override
|
||||
State<MathUnitCircleLabView> createState() => _MathUnitCircleLabViewState();
|
||||
}
|
||||
|
||||
class _MathUnitCircleLabViewState extends State<MathUnitCircleLabView> {
|
||||
double deg = 40;
|
||||
bool showTan = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final th = deg * math.pi / 180;
|
||||
final sn = math.sin(th), cs = math.cos(th);
|
||||
final tn = cs.abs() < 0.02 ? double.infinity : sn / cs;
|
||||
final quad = deg < 90
|
||||
? 'الأول'
|
||||
: deg < 180
|
||||
? 'الثاني'
|
||||
: deg < 270
|
||||
? 'الثالث'
|
||||
: 'الرابع';
|
||||
return SaqelLabScaffold(
|
||||
titleAr: 'الدائرة المثلثية — sin و cos و tan',
|
||||
subtitleAr: 'حرّك الزاوية θ من 0° إلى 360° وشاهد الإسقاطات حية',
|
||||
identity: kMathUnitCircleLabIdentity,
|
||||
onCheckpointTriggered: widget.onCheckpointTriggered,
|
||||
checkpointQuestion: 'إذا كانت θ = 150° فإشارة sinθ و cosθ هي …',
|
||||
checkpointOptions: const [
|
||||
'sin موجب و cos سالب',
|
||||
'كلتاهما موجبتان',
|
||||
'كلتاهما سالبتان',
|
||||
'sin سالب و cos موجب'
|
||||
],
|
||||
checkpointCorrectIdx: 0,
|
||||
telemetry: [
|
||||
LabPill('sin = ${sn.toStringAsFixed(3)}',
|
||||
color: const Color(0xFF30D158)),
|
||||
LabPill('cos = ${cs.toStringAsFixed(3)}',
|
||||
color: const Color(0xFF0071E3)),
|
||||
LabPill(
|
||||
tn.isInfinite ? 'tan غير معرف' : 'tan = ${tn.toStringAsFixed(3)}',
|
||||
color: const Color(0xFFFF9F0A)),
|
||||
],
|
||||
canvas: CustomPaint(
|
||||
painter: _UnitCirclePainter(deg: deg, showTan: showTan),
|
||||
child: Container(),
|
||||
),
|
||||
controls: [
|
||||
LabSlider(
|
||||
label: 'الزاوية θ',
|
||||
value: deg,
|
||||
min: 0,
|
||||
max: 360,
|
||||
display:
|
||||
'${deg.toInt()}° (${(deg * math.pi / 180).toStringAsFixed(2)} rad)',
|
||||
onChanged: (v) => setState(() => deg = v)),
|
||||
LabToggle(
|
||||
label: 'خط الظل tan',
|
||||
hint: 'المماس عند x=1',
|
||||
value: showTan,
|
||||
accent: const Color(0xFFFF9F0A),
|
||||
onChanged: (v) => setState(() => showTan = v)),
|
||||
const SizedBox(height: 6),
|
||||
LabFormulaCard(
|
||||
title: 'الربع $quad — θ = ${deg.toInt()}°',
|
||||
body:
|
||||
'sinθ = y = ${sn.toStringAsFixed(3)}\ncosθ = x = ${cs.toStringAsFixed(3)}\ntanθ = sin/cos = ${tn.isInfinite ? 'غير معرف (المقام ≈ 0)' : tn.toStringAsFixed(3)}\nsin²θ + cos²θ = ${(sn * sn + cs * cs).toStringAsFixed(3)} ✓',
|
||||
),
|
||||
],
|
||||
footerNote:
|
||||
'النقطة على الدائرة هي (cosθ، sinθ) دائماً — احفظها بهذه الصورة.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UnitCirclePainter extends CustomPainter {
|
||||
final double deg;
|
||||
final bool showTan;
|
||||
_UnitCirclePainter({required this.deg, required this.showTan});
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
c.drawRect(Offset.zero & s, Paint()..color = const Color(0xFF070D18));
|
||||
final o = Offset(s.width * 0.42, s.height * 0.52);
|
||||
final r = math.min(s.width, s.height) * 0.36;
|
||||
// axes
|
||||
c.drawLine(Offset(10, o.dy), Offset(s.width - 10, o.dy),
|
||||
Paint()..color = Colors.white24);
|
||||
c.drawLine(Offset(o.dx, 10), Offset(o.dx, s.height - 10),
|
||||
Paint()..color = Colors.white24);
|
||||
c.drawCircle(
|
||||
o,
|
||||
r,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.8);
|
||||
final th = deg * math.pi / 180;
|
||||
final p = Offset(o.dx + r * math.cos(th), o.dy - r * math.sin(th));
|
||||
// angle arc
|
||||
c.drawArc(
|
||||
Rect.fromCircle(center: o, radius: 30),
|
||||
0,
|
||||
-th,
|
||||
false,
|
||||
Paint()
|
||||
..color = const Color(0xFFFFD166)
|
||||
..strokeWidth = 2
|
||||
..style = PaintingStyle.stroke);
|
||||
c.drawLine(
|
||||
o,
|
||||
p,
|
||||
Paint()
|
||||
..color = const Color(0xFFFFD166)
|
||||
..strokeWidth = 2.4);
|
||||
c.drawCircle(p, 6, Paint()..color = Colors.white);
|
||||
// sin projection (vertical, green)
|
||||
c.drawLine(
|
||||
p,
|
||||
Offset(p.dx, o.dy),
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..strokeWidth = 3);
|
||||
// cos projection (horizontal, blue)
|
||||
c.drawLine(
|
||||
Offset(o.dx, o.dy),
|
||||
Offset(p.dx, o.dy),
|
||||
Paint()
|
||||
..color = const Color(0xFF0071E3)
|
||||
..strokeWidth = 3);
|
||||
// tan line
|
||||
if (showTan) {
|
||||
final tx = o.dx + r;
|
||||
double ty = o.dy - r * math.tan(th);
|
||||
ty = ty.clamp(6, s.height - 6);
|
||||
c.drawLine(
|
||||
Offset(tx, o.dy - r),
|
||||
Offset(tx, o.dy + r),
|
||||
Paint()
|
||||
..color = Colors.white24
|
||||
..strokeWidth = 1.2);
|
||||
if (math.cos(th).abs() > 0.08) {
|
||||
c.drawLine(
|
||||
o,
|
||||
Offset(tx, ty),
|
||||
Paint()
|
||||
..color = const Color(0xFFFF9F0A)
|
||||
..strokeWidth = 1.6);
|
||||
c.drawCircle(
|
||||
Offset(tx, ty), 4.5, Paint()..color = const Color(0xFFFF9F0A));
|
||||
}
|
||||
}
|
||||
// sine wave preview (right)
|
||||
final wx = s.width * 0.78, ww = s.width * 0.18, wh = r * 1.6;
|
||||
c.drawRRect(
|
||||
RRect.fromLTRBR(wx, o.dy - wh / 2, wx + ww, o.dy + wh / 2,
|
||||
const Radius.circular(8)),
|
||||
Paint()..color = Colors.black.withValues(alpha: 0.5));
|
||||
final path = Path();
|
||||
for (double px = 0; px <= ww; px += 2) {
|
||||
final a = px / ww * 4 * math.pi;
|
||||
final y = o.dy - math.sin(a) * wh / 2 * 0.9;
|
||||
if (px == 0) {
|
||||
path.moveTo(wx + px, y);
|
||||
} else {
|
||||
path.lineTo(wx + px, y);
|
||||
}
|
||||
}
|
||||
c.drawPath(
|
||||
path,
|
||||
Paint()
|
||||
..color = const Color(0xFF30D158)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.8);
|
||||
final mx = wx + (deg / 360 * ww * 2 % ww);
|
||||
c.drawLine(
|
||||
Offset(mx, o.dy - wh / 2),
|
||||
Offset(mx, o.dy + wh / 2),
|
||||
Paint()
|
||||
..color = Colors.white70
|
||||
..strokeWidth = 1.2);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _UnitCirclePainter o) =>
|
||||
o.deg != deg || o.showTan != showTan;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+316
@@ -0,0 +1,316 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../data/models/socratic_checkpoint_model.dart';
|
||||
import '../../widgets/socratic_dialog.dart';
|
||||
import 'labs_gallery_screen.dart';
|
||||
import 'labs_registry.dart';
|
||||
|
||||
/// ============================================================================
|
||||
/// SUBJECT VIRTUAL LABS VIEW
|
||||
/// Integrated Hub for all virtual labs of a specific subject.
|
||||
/// Allows the student to switch between each lesson's live interactive lab,
|
||||
/// triggering checkpoints and running full 2/3 canvas + 1/3 controls simulations.
|
||||
/// ============================================================================
|
||||
class SubjectVirtualLabsView extends StatefulWidget {
|
||||
final String subjectTitle;
|
||||
final Color primaryColor;
|
||||
final Widget? specializedToolWidget;
|
||||
final String? specializedToolTitle;
|
||||
|
||||
const SubjectVirtualLabsView({
|
||||
super.key,
|
||||
required this.subjectTitle,
|
||||
required this.primaryColor,
|
||||
this.specializedToolWidget,
|
||||
this.specializedToolTitle,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SubjectVirtualLabsView> createState() => _SubjectVirtualLabsViewState();
|
||||
}
|
||||
|
||||
class _SubjectVirtualLabsViewState extends State<SubjectVirtualLabsView> {
|
||||
int _selectedIndex = 0;
|
||||
bool _showSpecialized = false;
|
||||
|
||||
void _openGallery(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (_) => Scaffold(
|
||||
backgroundColor: AppColors.darkBackground,
|
||||
body: SafeArea(
|
||||
child: VirtualLabsGalleryScreen(
|
||||
initialSubject:
|
||||
Grade10LabsRegistry.normalizeSubject(widget.subjectTitle),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _triggerCheckpoint(
|
||||
String q, List<String> opts, int correct, String lessonTitle) {
|
||||
final checkpoint = SocraticCheckpointModel(
|
||||
id: lessonTitle.hashCode & 0x7fffffff,
|
||||
questionText: q,
|
||||
timestampSeconds: 0,
|
||||
hint: 'راجع لوحة المختبر والمحاكاة التفاعلية قبل تأكيد إجابتك.',
|
||||
options: List.generate(
|
||||
opts.length,
|
||||
(i) => SocraticOptionModel(
|
||||
id: i,
|
||||
text: opts[i],
|
||||
isCorrect: i == correct,
|
||||
),
|
||||
),
|
||||
);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => SocraticCheckpointModal(
|
||||
checkpoint: checkpoint,
|
||||
onOptionSelected: (_) {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final allLabs =
|
||||
Grade10LabsRegistry.bySubjectNormalized(widget.subjectTitle);
|
||||
final labs = Grade10LabsRegistry.previewModeEnabled
|
||||
? allLabs
|
||||
: allLabs.where((e) => e.identity.isPublished).toList();
|
||||
|
||||
if (labs.isEmpty) {
|
||||
if (widget.specializedToolWidget != null) {
|
||||
return widget.specializedToolWidget!;
|
||||
}
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(CupertinoIcons.lab_flask,
|
||||
color: widget.primaryColor.withValues(alpha: 0.5), size: 48),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'لا مختبرات منشورة لمبحث ${widget.subjectTitle} بعد',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'يبدأ عرض المختبرات للطلاب عند إصدار حزمة المنهج المعتمدة من الخادم. هنا معاينة التأليف التحضيرية فقط.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 11.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
TextButton.icon(
|
||||
onPressed: () => _openGallery(context),
|
||||
icon: const Icon(CupertinoIcons.square_grid_2x2, size: 16),
|
||||
label: const Text('معرض المعاينة التأليفية',
|
||||
style:
|
||||
TextStyle(fontWeight: FontWeight.w800, fontSize: 12.5)),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.saqelCyan,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final activeIndex = _selectedIndex.clamp(0, labs.length - 1);
|
||||
final activeLab = labs[activeIndex];
|
||||
|
||||
return Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Column(
|
||||
children: [
|
||||
// Header & Lab Selector Chips
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(14, 10, 14, 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF09111E),
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.2)),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.primaryColor.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(CupertinoIcons.lab_flask_solid,
|
||||
color: widget.primaryColor, size: 16),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'تجارب ومختبرات ${widget.subjectTitle}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 13.5,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
labs.isEmpty
|
||||
? 'لا تجارب منشورة بعد — معاينة التأليف في معرض المختبرات'
|
||||
: '${labs.length} تجارب تفاعلية معتمدة • 2/3 محاكاة + 1/3 تحكم',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF94A3B8), fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => _openGallery(context),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.saqelCyan.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color:
|
||||
AppColors.saqelCyan.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'كافة المختبرات',
|
||||
style: TextStyle(
|
||||
color: AppColors.saqelCyan,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Icon(CupertinoIcons.square_grid_2x2,
|
||||
color: AppColors.saqelCyan, size: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Horizontal Chips Selector
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: labs.length +
|
||||
(widget.specializedToolWidget != null ? 1 : 0),
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
||||
itemBuilder: (context, idx) {
|
||||
if (widget.specializedToolWidget != null &&
|
||||
idx == labs.length) {
|
||||
final isSel = _showSpecialized;
|
||||
return InkWell(
|
||||
onTap: () => setState(() => _showSpecialized = true),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isSel
|
||||
? widget.primaryColor
|
||||
: Colors.white.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isSel
|
||||
? widget.primaryColor
|
||||
: Colors.white24,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
widget.specializedToolTitle ??
|
||||
'الأداة المتقدمة',
|
||||
style: TextStyle(
|
||||
color: isSel ? Colors.black : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final lab = labs[idx];
|
||||
final isSel = !_showSpecialized && activeIndex == idx;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_showSpecialized = false;
|
||||
_selectedIndex = idx;
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isSel
|
||||
? AppColors.saqelCyan
|
||||
: Colors.white.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color:
|
||||
isSel ? AppColors.saqelCyan : Colors.white12,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${idx + 1}. ${lab.lessonAr}',
|
||||
style: TextStyle(
|
||||
color: isSel ? Colors.black : Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
isSel ? FontWeight.w800 : FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Live Lab Body
|
||||
Expanded(
|
||||
child: _showSpecialized && widget.specializedToolWidget != null
|
||||
? widget.specializedToolWidget!
|
||||
: activeLab.builder((q, opts, correct) {
|
||||
_triggerCheckpoint(q, opts, correct, activeLab.lessonAr);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ PODS:
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -19,6 +21,7 @@ DEPENDENCIES:
|
||||
- flutter_tts (from `Flutter/ephemeral/.symlinks/plugins/flutter_tts/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -32,6 +35,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
video_player_avfoundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
|
||||
|
||||
@@ -41,6 +46,7 @@ SPEC CHECKSUMS:
|
||||
flutter_tts: ae915565cc6948444b513acc8ee021993281e027
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
@@ -340,10 +340,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
version: "0.12.18"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -356,10 +356,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -585,10 +585,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.9"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -609,10 +609,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "611e87fb320b70d1dd721dc46af89c98aceccea9b31fde49e084591414e0c610"
|
||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.33"
|
||||
version: "6.3.30"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -758,5 +758,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
sdks:
|
||||
dart: ">=3.12.0 <4.0.0"
|
||||
flutter: ">=3.44.0"
|
||||
dart: ">=3.11.0 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
|
||||
@@ -0,0 +1,657 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:student_app/presentation/screens/virtual_labs/lab_identity.dart';
|
||||
import 'package:student_app/presentation/screens/virtual_labs/labs_gallery_screen.dart';
|
||||
import 'package:student_app/presentation/screens/virtual_labs/labs_registry.dart';
|
||||
import 'package:student_app/presentation/screens/virtual_labs/subject_virtual_labs_view.dart';
|
||||
|
||||
void main() {
|
||||
test('registry covers all Grade-10 labs (44 entries, 14 subjects)', () {
|
||||
expect(Grade10LabsRegistry.all.length, 44);
|
||||
expect(Grade10LabsRegistry.subjects.length, 14);
|
||||
final ids = Grade10LabsRegistry.all.map((e) => e.id).toSet();
|
||||
expect(ids.length, 44); // stable unique ids
|
||||
});
|
||||
|
||||
test('bound vs authoring-tool split: 28 lesson-bound, 16 standalone tools',
|
||||
() {
|
||||
final bound = Grade10LabsRegistry.boundEntries;
|
||||
final tools = Grade10LabsRegistry.authoringTools;
|
||||
expect(bound.length, 28);
|
||||
expect(tools.length, 16);
|
||||
for (final e in bound) {
|
||||
expect(e.identity.isBound, isTrue, reason: '${e.id} should be bound');
|
||||
expect(e.isPublished, isFalse,
|
||||
reason: 'no lab may be published before server bundle version');
|
||||
}
|
||||
for (final e in tools) {
|
||||
expect(e.identity.curriculumLessonId, isEmpty,
|
||||
reason: '${e.id} must not masquerade as a lesson lab');
|
||||
}
|
||||
});
|
||||
|
||||
test('every bound identity carries an explicit, well-formed curriculum id',
|
||||
() {
|
||||
final pattern = RegExp(r'^[a-z_]+_10_semester_[12]_unit_\d{2}_lesson_\d{2}$'
|
||||
r'|^[a-z_]+_10_semester_[12]_unit_\d{2}_unit_enrichment_and_review$');
|
||||
for (final e in Grade10LabsRegistry.boundEntries) {
|
||||
expect(pattern.hasMatch(e.identity.curriculumLessonId), isTrue,
|
||||
reason: 'malformed lesson id: ${e.identity.curriculumLessonId}');
|
||||
expect(e.identity.curriculumVersion, 'jordan-grade10-2026-source-review');
|
||||
}
|
||||
});
|
||||
|
||||
test('entryForCurriculumLessonId matches ONLY exact curriculum ids', () {
|
||||
// Exact bound id resolves (draft, not published).
|
||||
final vectors = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'physics_10_semester_1_unit_01_lesson_02');
|
||||
expect(vectors, isNotNull);
|
||||
expect(vectors!.identity.curriculumLessonId,
|
||||
'physics_10_semester_1_unit_01_lesson_02');
|
||||
|
||||
// A same-title keyword/lesson_01 guess must NOT resolve.
|
||||
expect(Grade10LabsRegistry.entryForCurriculumLessonId('lesson_01'), isNull);
|
||||
expect(
|
||||
Grade10LabsRegistry.entryForCurriculumLessonId('physics_10'), isNull);
|
||||
expect(
|
||||
Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'physics_10_semester_1_unit_01_lesson_01'),
|
||||
isNull);
|
||||
|
||||
// Unbound authoring tools never resolve through lesson lookup.
|
||||
for (final e in Grade10LabsRegistry.authoringTools) {
|
||||
expect(Grade10LabsRegistry.entryForCurriculumLessonId(e.id), isNull);
|
||||
}
|
||||
});
|
||||
|
||||
test('findForLesson requires publication — nothing published yet', () {
|
||||
Grade10LabsRegistry.previewModeEnabled = false;
|
||||
for (final e in Grade10LabsRegistry.boundEntries) {
|
||||
final found = Grade10LabsRegistry.findForLesson(
|
||||
subjectTitle: e.subjectAr,
|
||||
lessonTitle: e.lessonAr,
|
||||
curriculumLessonId: e.identity.curriculumLessonId,
|
||||
);
|
||||
expect(found, isNull,
|
||||
reason:
|
||||
'draft labs must never surface to students in production: ${e.id}');
|
||||
}
|
||||
Grade10LabsRegistry.previewModeEnabled = true;
|
||||
});
|
||||
|
||||
test('findForLesson surfaces labs in previewMode for local development', () {
|
||||
Grade10LabsRegistry.previewModeEnabled = true;
|
||||
final found = Grade10LabsRegistry.findForLesson(
|
||||
subjectTitle: 'اللغة العربية',
|
||||
lessonTitle: 'أي عنوان',
|
||||
curriculumLessonId: 'arabic_10_semester_1_unit_01_lesson_01',
|
||||
);
|
||||
expect(found, isNotNull);
|
||||
expect(found?.identity.curriculumLessonId,
|
||||
'arabic_10_semester_1_unit_01_lesson_01');
|
||||
});
|
||||
|
||||
test('a lab is published only when released AND bundle version provided', () {
|
||||
// Emulate a server bundle arriving only for released bound lesson.
|
||||
const released = LabIdentity(
|
||||
curriculumLessonId: 'math_10_semester_1_unit_01_lesson_02',
|
||||
subjectAr: 'الرياضيات',
|
||||
lessonAr: 'حل أنظمة خطية وتربيعية',
|
||||
reviewStatus: 'released',
|
||||
publishedBundleVersion: 'pkg-2026.1',
|
||||
);
|
||||
expect(released.isBound, isTrue);
|
||||
expect(released.isReleased, isTrue);
|
||||
expect(released.isPublished, isTrue);
|
||||
|
||||
// Unbound tools cannot be published at all (no lesson anchor).
|
||||
const tool =
|
||||
LabIdentity(toolKey: 'x_tool', subjectAr: 'غير ذلك', lessonAr: 'أداة');
|
||||
expect(tool.isPublished, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('gallery renders the authoring-preview surface', (t) async {
|
||||
await t.pumpWidget(
|
||||
const MaterialApp(home: Scaffold(body: VirtualLabsGalleryScreen())));
|
||||
await t.pumpAndSettle();
|
||||
expect(find.text('المختبرات الافتراضية — الصف العاشر'), findsOneWidget);
|
||||
expect(find.text('جمع المتجهات وتحليلها'), findsOneWidget);
|
||||
// Standalone tools are listed as authoring tools (registry-level proof,
|
||||
// lazy list keeps far tiles unbuilt).
|
||||
expect(
|
||||
Grade10LabsRegistry.authoringTools
|
||||
.any((e) => e.lessonAr == 'اختبار اللهب'),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'subject view shows honest unpublished state when preview is false',
|
||||
(t) async {
|
||||
Grade10LabsRegistry.previewModeEnabled = false;
|
||||
await t.pumpWidget(const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: SubjectVirtualLabsView(
|
||||
subjectTitle: 'الفيزياء',
|
||||
primaryColor: Colors.cyan,
|
||||
),
|
||||
),
|
||||
));
|
||||
await t.pump();
|
||||
expect(find.text('تجارب ومختبرات الفيزياء'), findsNothing);
|
||||
expect(find.text('لا مختبرات منشورة لمبحث الفيزياء بعد'), findsOneWidget);
|
||||
expect(find.text('معرض المعاينة التأليفية'), findsOneWidget);
|
||||
Grade10LabsRegistry.previewModeEnabled = true;
|
||||
});
|
||||
|
||||
testWidgets('sample labs build on the shared 2/3-1/3 scaffold', (t) async {
|
||||
for (final id in [
|
||||
'physics_10_semester_1_unit_01_lesson_02', // vectors (bound)
|
||||
'math_10_semester_1_unit_03_lesson_02', // unit circle (bound)
|
||||
'finance_budget', // budget (tool)
|
||||
'islamic_inheritance', // inheritance (tool, no curriculum anchor)
|
||||
]) {
|
||||
final entry = Grade10LabsRegistry.all.firstWhere((e) => e.id == id);
|
||||
await t
|
||||
.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('لوحة التحكم والضبط'), findsOneWidget);
|
||||
expect(find.text('فحص الفهم والاستيعاب', skipOffstage: false),
|
||||
findsOneWidget);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('arabic listening lab: etiquettes toggle unlock readiness',
|
||||
(t) async {
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_01')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر الانتباه والتركيز'), findsOneWidget);
|
||||
for (var i = 0; i < 4; i++) {
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.text('جاهزٌ للاستماعِ الواعي ✓'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic listening lab: ordering trial stages solves activity',
|
||||
(t) async {
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_01')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('مراحل الابتلاء'));
|
||||
await t.pump();
|
||||
for (final letter in ['د', 'أ', 'ج', 'ب']) {
|
||||
await t.tap(find.text(letter));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('أحسنتَ ترتيبَ المراحل'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic apology lab: speech-building order solves activity',
|
||||
(t) async {
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_02')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر فنّ الاعتذار'), findsOneWidget);
|
||||
for (final letter in ['أ', 'ب', 'ج']) {
|
||||
await t.tap(find.text(letter));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('أحسنتَ بناءَ حديثِك'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic apology lab: tone affects listener reaction', (t) async {
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_02')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('نبرة الصوت'));
|
||||
await t.pump();
|
||||
await t.tap(find.text('هادئٌ صادقٌ'));
|
||||
await t.pump();
|
||||
expect(find.textContaining('بذلُ السلامِ وحُسنُ الكلام'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('arabic quranic lab: surah-apology matching lights pedestals',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2200));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_03')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر القراءة الفاهمة'), findsOneWidget);
|
||||
// حَلّ كل سورة باختيار الخيار الصحيح (الأول دائماً).
|
||||
final labels = [
|
||||
'الإقرارُ بالخطأ وطلبُ المغفرة (﴿لا تثريبَ عليكم﴾)',
|
||||
'إسفٌ شديدٌ والمسارعةُ إلى الرشادِ بعدَ ذكرِ العجل',
|
||||
'الاعترافُ بالنسيانِ وآدابُ التعلّمِ والخُلُقُ الرفيع',
|
||||
];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
await t.tap(find.text(labels[i]));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('صحيح: لكلِّ قصةٍ وجهٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic quranic lab: critical silent-reading behaviours',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2200));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_03')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('القراءةُ الصامتة'));
|
||||
await t.pump();
|
||||
// السلوكان الصحيحان ON والخطآن يبقيان OFF في الحالة الابتدائية.
|
||||
await t.tap(find.byType(CupertinoSwitch).at(0));
|
||||
await t.tap(find.byType(CupertinoSwitch).at(1));
|
||||
await t.pump();
|
||||
expect(find.text('قراءةٌ صامتةٌ صحيحة: العينُ والعقلُ بلا همسٍ ولا صوت ✓'),
|
||||
findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic letter lab: elements fill the letter sheet', (t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_04')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر رسالة الاعتذار'), findsOneWidget);
|
||||
final labels = [
|
||||
'عنصرُ المستلِم',
|
||||
'عنصرُ الموضوع',
|
||||
'عنصرُ التحيةِ والسلامِ',
|
||||
'عنصرُ المقدمة',
|
||||
'عنصرُ صلبِ الرسالةِ: الاعتذار',
|
||||
'عنصرُ الخاتمةِ والتوقيع',
|
||||
];
|
||||
for (var i = 0; i < 6; i++) {
|
||||
await t.tap(find.text(labels[i]));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('رسالتُك اكتملت'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic letter lab: ordering letter paragraphs solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_04')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('ترتيبُ الرسالة'));
|
||||
await t.pump();
|
||||
for (final letter in ['أ', 'ب', 'ج', 'د']) {
|
||||
await t.tap(find.text(letter));
|
||||
await t.pump();
|
||||
}
|
||||
expect(
|
||||
find.textContaining('أتممتَ ترتيبَ رسالةِ الاعتذار'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic conditional lab: classify jazma tools solves bins',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_05')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر أسلوب الشرط'), findsOneWidget);
|
||||
// The 5 jazma tools pick «جازمة» (rows 0-4).
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await t.tap(find.text('جازمة').at(i));
|
||||
await t.pump();
|
||||
}
|
||||
// The 3 non-jazma tools pick «غيرُ جازمة» (rows 5-7).
|
||||
for (var i = 5; i < 8; i++) {
|
||||
await t.tap(find.text('غيرُ جازمة').at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('صنَّفْتَ أدواتِ الشرطِ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic conditional lab: fill iram model solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_05')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('أتمَّ الإعراب'));
|
||||
await t.pump();
|
||||
const row1 = 'فعلٌ مضارعٌ مجزومٌ، وعلامةُ جزمِه حذفُ حرفِ العلةِ من آخرِه';
|
||||
expect(find.text(row1), findsOneWidget);
|
||||
await t.tap(find.text(row1));
|
||||
await t.pump();
|
||||
await t.tap(find.text('ضميرٌ مستترٌ تقديرُه (أنت)'));
|
||||
await t.pump();
|
||||
await t.tap(
|
||||
find.text('ضميرٌ متصلٌّ مبنيٌّ على الكسرِ في محلِّ نصبٍ مفعولٌ به'));
|
||||
await t.pump();
|
||||
expect(find.textContaining('أتممتَ نموذجَ إعرابِ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic informative lab: classify sentences solves buckets',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_06')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر الأسلوب الخبري'), findsOneWidget);
|
||||
// Rows 0,1,4,5 are خبرية; rows 2,3 are إنشائية.
|
||||
await t.tap(find.text('خبريّةٌ').at(0));
|
||||
await t.pump();
|
||||
await t.tap(find.text('خبريّةٌ').at(1));
|
||||
await t.pump();
|
||||
await t.tap(find.text('إنشائيّةٌ').at(2));
|
||||
await t.pump();
|
||||
await t.tap(find.text('إنشائيّةٌ').at(3));
|
||||
await t.pump();
|
||||
await t.tap(find.text('خبريّةٌ').at(4));
|
||||
await t.pump();
|
||||
await t.tap(find.text('خبريّةٌ').at(5));
|
||||
await t.pump();
|
||||
expect(find.textContaining('صنَّفْتَ الجملَ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic informative lab: complete rules solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_01_lesson_06')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('أكملِ القواعدَ'));
|
||||
await t.pump();
|
||||
await t.tap(find.text('الصدقَ والكذبَ'));
|
||||
await t.pump();
|
||||
await t.tap(find.text('الخبريِّ والإنشائيِّ'));
|
||||
await t.pump();
|
||||
await t.tap(find.text('الاسميةِ والفعليةِ'));
|
||||
await t.pump();
|
||||
expect(
|
||||
find.textContaining('أكملتَ قواعدَ الأسلوبِ الخبريِّ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 listening lab: correcting statements solves task',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_01')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر الاستماع الواعي (قصة الضيف)'), findsOneWidget);
|
||||
const targets = [true, true, true, true, false];
|
||||
for (var i = 0; i < targets.length; i++) {
|
||||
if (targets[i]) {
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
}
|
||||
expect(find.textContaining('عباراتُك صحيحة'), findsOneWidget);
|
||||
});
|
||||
testWidgets('arabic unit2 listening lab: story order solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_01')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('رتِّبِ الحكاية'));
|
||||
await t.pump();
|
||||
for (final letter in ['أ', 'ب', 'ج', 'د']) {
|
||||
await t.tap(find.text(letter));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 speaking lab: values checklist solves task',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_02')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر العرض التقديمي (أتحدثُ بطلاقةٍ)'), findsOneWidget);
|
||||
const offIdx = 4;
|
||||
for (var i = 0; i < 6; i++) {
|
||||
if (i == offIdx) continue;
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('قيمُ الوطنيةِ معطوبةٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 speaking lab: presentation plan solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_02')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('عرضُ التحدّثِ'));
|
||||
await t.pump();
|
||||
// targets [0,0,0,1,0] for rows مطلوب/ليس مطلوبًا
|
||||
for (var i = 0; i < 5; i++) {
|
||||
if (i == 3) continue;
|
||||
await t.tap(find.text('مطلوبٌ').at(i));
|
||||
await t.pump();
|
||||
}
|
||||
await t.tap(find.text('ليسَ مطلوبًا').at(3));
|
||||
await t.pump();
|
||||
expect(find.textContaining('خطّةُ العرضِ سليمةٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'arabic unit2 speaking lab: quote-to-meaning solves with '
|
||||
'checkpoint', (t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_02')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('القولُ والمضمونُ'));
|
||||
await t.pump();
|
||||
// correct letters per row: أ، ب، ج، ب → indexes 0,1,2,1
|
||||
await t.tap(find.text('أ').at(0));
|
||||
await t.pump();
|
||||
await t.tap(find.text('ب').at(1));
|
||||
await t.pump();
|
||||
await t.tap(find.text('ج').at(2));
|
||||
await t.pump();
|
||||
await t.tap(find.text('ب').at(3));
|
||||
await t.pump();
|
||||
expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 poetry lab: lexicon matching solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_03')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر قراءةِ شعرِ المقاومة (إلى الصامدين غرب النهر)'),
|
||||
findsOneWidget);
|
||||
// correct letters per row: ب، أ، ب، ب → indices 1,0,1,1
|
||||
const taps = ['ب', 'أ', 'ب'];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
await t.tap(find.text(taps[i]).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
// fourth row correct is 'ب' at index 3
|
||||
await t.tap(find.text('ب').at(3));
|
||||
await t.pump();
|
||||
expect(find.textContaining('معجمُ القصيدةِ محفوظٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 poetry lab: poem journey ordering solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_03')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('جوُّ القصيدةِ'));
|
||||
await t.pump();
|
||||
for (final letter in ['أ', 'ب', 'ج', 'د']) {
|
||||
await t.tap(find.text(letter));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 poetry lab: prosody checklist solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_03')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('رَشِيقُ التفعيلةِ'));
|
||||
await t.pump();
|
||||
// targets [T,T,F,T,T,F]; toggle switches 2 and 5 stay off
|
||||
for (var i = 0; i < 6; i++) {
|
||||
if (i == 2 || i == 5) continue;
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('رَشِيقُ التفعيلةِ واضحٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 writing lab: craft checklist solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_04')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر كتابةِ تحليلِ النصِّ الشعريِّ'), findsOneWidget);
|
||||
// targets [T,T,T,T,T,F,T,T]; index 5 (الورق والحبر) stays off
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (i == 5) continue;
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('عناصرُ الكتابةِ مثبّتةٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'arabic unit2 writing lab: criteria ordering solves with '
|
||||
'checkpoint', (t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_04')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('معاييرُ التحليلِ'));
|
||||
await t.pump();
|
||||
for (final letter in ['أ', 'ب', 'ج', 'د']) {
|
||||
await t.tap(find.text(letter));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 writing lab: analysis checklist solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_04')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('تحليلُ الكرميِّ'));
|
||||
await t.pump();
|
||||
// targets [T,T,T,T,T,F]; toggle switch 5 (كلام مجرد) stays off
|
||||
for (var i = 0; i < 6; i++) {
|
||||
if (i == 5) continue;
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('تحليلُ الكرميِّ مطابقٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('arabic unit2 vocative lab: components checklist solves activity',
|
||||
(t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_05')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
expect(find.text('مختبر أسلوبِ النداءِ'), findsOneWidget);
|
||||
// targets [T,T,T,T,T,T,T,F]; index 7 (المفعول والعِدة) stays off
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (i == 7) continue;
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('مكوّناتُ النداءِ مثبّتةٌ'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'arabic unit2 vocative lab: types matching solves with '
|
||||
'checkpoint', (t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_05')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('أنواعُ المنادى'));
|
||||
await t.pump();
|
||||
// correct letters per row: أ، ب، أ، ج، أ → indices 0,1,0,2,0
|
||||
const taps = ['أ', 'ب', 'أ', 'ج', 'أ'];
|
||||
for (var i = 0; i < 5; i++) {
|
||||
await t.tap(find.text(taps[i]).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.text('فحص الفهم والاستيعاب'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'arabic unit2 vocative lab: situational checklist solves '
|
||||
'activity', (t) async {
|
||||
await t.binding.setSurfaceSize(const Size(900, 2600));
|
||||
addTearDown(() => t.binding.setSurfaceSize(null));
|
||||
final entry = Grade10LabsRegistry.entryForCurriculumLessonId(
|
||||
'arabic_10_semester_1_unit_02_lesson_05')!;
|
||||
await t.pumpWidget(MaterialApp(home: Scaffold(body: entry.builder(null))));
|
||||
await t.pump();
|
||||
await t.tap(find.text('توظيفُ النداءِ'));
|
||||
await t.pump();
|
||||
// targets [T,T,T,T,F,F]; toggles 4 and 5 stay off
|
||||
for (var i = 0; i < 6; i++) {
|
||||
if (i == 4 || i == 5) continue;
|
||||
await t.tap(find.byType(CupertinoSwitch).at(i));
|
||||
await t.pump();
|
||||
}
|
||||
expect(find.textContaining('توظيفُ النداءِ سليمٌ'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -308,7 +308,7 @@ class _TeacherAssignmentsQnATabState extends State<TeacherAssignmentsQnATab> {
|
||||
size: 56, color: TeacherTheme.emeraldPrimary),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'تسجيل رد صوتي سقراطي للطالبة ($studentName) 🎙️',
|
||||
'تسجيل رد صوتي توجيهي للطالبة ($studentName) 🎙️',
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
@@ -332,7 +332,7 @@ class _TeacherAssignmentsQnATabState extends State<TeacherAssignmentsQnATab> {
|
||||
SaqelToast.showSuccess(
|
||||
context,
|
||||
'تم إرسال الرد الصوتي إلى الطالبة $studentName بنجاح 🎧',
|
||||
title: 'رد سقراطي مسجل ✨',
|
||||
title: 'رد صوتي مسجل ✨',
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.paperplane_fill, size: 16),
|
||||
@@ -693,7 +693,7 @@ class _TeacherAssignmentsQnATabState extends State<TeacherAssignmentsQnATab> {
|
||||
onPressed: () =>
|
||||
_recordVoiceReply(doubt.id, doubt.studentName),
|
||||
icon: const Icon(CupertinoIcons.mic_fill, size: 14),
|
||||
label: const Text('تسجيل رد صوتي سقراطي 🎙️',
|
||||
label: const Text('تسجيل رد صوتي توجيهي 🎙️',
|
||||
style: TextStyle(
|
||||
fontSize: 11.5, fontWeight: FontWeight.w800)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
||||
+1
-1
@@ -183,7 +183,7 @@ class _TeacherReputationScorecardTabState
|
||||
_metricProgress('الالتزام بالمنهاج والنتاجات الوزارية',
|
||||
_curriculumAlignment, TeacherTheme.emeraldPrimary),
|
||||
const SizedBox(height: 12),
|
||||
_metricProgress('التفاعل السقراطي وإثارة التفكير',
|
||||
_metricProgress('فحص الفهم التفاعلي وإثارة التفكير',
|
||||
_socraticInteraction, TeacherTheme.cyberCyan),
|
||||
const SizedBox(height: 12),
|
||||
_metricProgress('الوضوح الصوتي وسلامة اللغة', _audioClarity,
|
||||
|
||||
@@ -670,7 +670,7 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
|
||||
const SizedBox(height: 6),
|
||||
_rowMetric('نقاء الصوت ومخارج الحروف:', res.audioClarity),
|
||||
const SizedBox(height: 6),
|
||||
_rowMetric('الفواصل السقراطية التفاعلية:',
|
||||
_rowMetric('محطات فحص الفهم التفاعلية:',
|
||||
'${res.socraticStopsCount} محطات تفكيرية إلزامية'),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -690,7 +690,7 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
|
||||
strokeWidth: 2)),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
'جاري رفع الفيديو وبدء معالجة HLS والتحليل السقراطي... ⏳',
|
||||
'جاري رفع الفيديو وبدء معالجة HLS واستخراج محطات الفهم... ⏳',
|
||||
style: TextStyle(
|
||||
color: TeacherTheme.emeraldLight,
|
||||
fontSize: 12.5,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# بروتوكول تأليف مختبرات صَقِل المرتبطة بالمنهج
|
||||
|
||||
هذا البروتوكول هو بوابة العمل بين ملف الدرس المعتمد والتنفيذ البرمجي. لا يسمح بإنشاء مختبر عام للمبحث، ولا بربطه عبر تشابه العنوان، ولا بإظهار مختبر للطالب قبل وجود حزمة خادمية منشورة وصلاحية وصول.
|
||||
|
||||
## خط الإنتاج
|
||||
|
||||
1. **الهوية:** يبدأ العمل بـ`curriculum_lesson_id` وإصدار المنهج والصف والمبحث والفصل والوحدة والدرس.
|
||||
2. **الاستخراج:** تُقرأ صفحات المصدر وMarkdown كاملًا، وتوثق النتاجات والمفاهيم والعلاقات والأمثلة والأخطاء الشائعة.
|
||||
3. **التصميم:** تُملأ مواصفة الدرس الموجودة تحت `docs/virtual_labs/grade_10/` ويعتمدها مختص المادة.
|
||||
4. **النموذج:** ينفذ الحساب منفصلًا عن الرسم ويختبر قبل إضافة الحركة.
|
||||
5. **التجربة:** يبنى المشهد المتجهي، حلقة اللعب، التحكم المتدرج، ومحطات فحص الفهم.
|
||||
6. **التحقق:** تختبر الدقة والحالات الحدية والهاتف واللوحي والوصول وتقليل الحركة.
|
||||
7. **النشر:** يسجل المختبر في حزمة الدرس على الخادم. العميل يعرض ما يصرح به الخادم فقط.
|
||||
|
||||
## عقد ملف مواصفة الدرس
|
||||
|
||||
كل ملف يجب أن يحتوي على:
|
||||
|
||||
- بيانات الهوية والمصدر وحالة المراجعة.
|
||||
- نتاجات قابلة للقياس ومحاور مثبتة في المصدر.
|
||||
- فكرة لعبة من خمس مراحل: اكتشف، جرّب، ابنِ، فسّر، انقل الفهم.
|
||||
- وصف المشهد المتحرك وطبقات الرسم.
|
||||
- جدول المتغيرات والوحدات والنطاقات والقيم الابتدائية.
|
||||
- المعادلات أو العلاقات مع مرجع الصفحة/القسم.
|
||||
- محطات فهم لها شرط إطلاق ودليل وتعليل وتلميح.
|
||||
- حالات الخطأ وإعادة الضبط وإمكانية الوصول.
|
||||
- اختبارات القبول وتعريف اكتمال لا يمكن تأشيره دون دليل.
|
||||
|
||||
## قواعد الواجهة المشتركة
|
||||
|
||||
- عرض `>=760px`: مساحة التجربة `flex: 2` ولوحة تحكم 320–360px.
|
||||
- هاتف: كانفاس 300–340px في الأعلى وتحكم قابل للتمرير في الأسفل.
|
||||
- رسم متجهي عبر `CustomPainter`، وخط نظام، وأهداف لمس 44×44 على الأقل.
|
||||
- ألوان أساسية: `#07111F` و`#0B1728` و`#102035`، ولهجات `#00F5D4` و`#0071E3` و`#30D158` و`#FF9F0A` و`#FF375F`.
|
||||
- اللون لا يحمل المعنى وحده، والحركة تحترم إعداد تقليل الحركة.
|
||||
- لا نقاط نجاح أو إتقان أو دخل مصطنع، ولا صور أو WebView بديلة عن التنفيذ الأصلي.
|
||||
|
||||
## أمر التوليد والتحقق
|
||||
|
||||
```bash
|
||||
php tools/generate_virtual_lab_specs.php
|
||||
php tools/validate_virtual_lab_specs.php
|
||||
```
|
||||
|
||||
المولّد يعيد بناء المسودات من قائمة القبول. لا يعني نجاحه أن المحتوى صالح للنشر؛ الموافقة الأكاديمية وربط حزمة الخادم شرطان مستقلان.
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# وضع تنفيذ المختبرات الافتراضية — الصف العاشر
|
||||
|
||||
> تحديث: 2026-09-12 — المرحلة: البنية المكتملة والتأليف الجاري (عربية: 11 دروس، الوحدة 01 كاملة + U2L1–U2L5).
|
||||
|
||||
## القاعدة الحاكمة
|
||||
|
||||
لا يُعرض مختبر للطالب إلا إذا:
|
||||
1. حمل هوية منهجية مربوطة (`curriculum_lesson_id` صريح من حزمة معتمدة)، و
|
||||
2. حالته `released`، و
|
||||
3. وصل من الخادم `publishedBundleVersion`.
|
||||
|
||||
حالياً **صفر مختبرات منشورة** — كل المواصفات `draft_requires_academic_review`/`needs_human_review` وليس ثمة حزمة منشورة بعد. العرض للطالب فارغ بصدق؛ المعاينة التأليفية متاحة في معرض المختبرات بوسم واضح.
|
||||
|
||||
## البنية المشتركة — مكتملة ومفحوصة (2026-09-12)
|
||||
|
||||
| الملف | الحالة |
|
||||
|---|---|
|
||||
| `lab_identity.dart` | نموذج `LabIdentity` + 44 ثابتاً (28 مربوطاً + 16 أداة) — عربية U1L1..U1L6 + U2L1..U2L5 |
|
||||
| `labs_registry.dart` | 44 entry (28 bound + 16 tool) — مطابقة صريحة فقط، بلا keywords/fallback، بوابة نشر |
|
||||
| `lab_scaffold.dart` | هوية + شارة حالة (منشور/مسودة/أداة) + تقليل الحركة + Semantics |
|
||||
| الملفات الـ33 `<subject>_labs.dart` | رُبطت بالثوابت وحُذفت `v2026.1`/`curriculumPath` |
|
||||
| `arabic_listening_lab.dart` | **مختبر جديد** مكتوب/مربوط/مُختبر — «أستمعُ بانتباهٍ وتركيزٍ» (قصة كعب بن مالك): آداب الاستماع + مراحل الابتلاء (ترتيب، مصحَّح بلا دمج) + جدول سبب/نتيجة + نقطة تحول، رسم `CustomPainter` فقط |
|
||||
| `arabic_apology_lab.dart` | **مختبر جديد** — «فن الاعتذار وقيم التسامح»: بنية الحديث (تقديم/عرض/خاتمة) + نبرة الصوت (4 نغمات) + جسر الاعتذار (محاور 5) |
|
||||
| `arabic_quranic_apology_lab.dart` | **مختبر جديد** — «أقرأ بطلاقة وفهم»: مطابقة سورة/وجه اعتذار + سلوكيات القراءة الصامتة + ترتيب اعتذار موسى |
|
||||
| `arabic_apology_letter_lab.dart` | **مختبر جديد** — «أكتب محتوى (رسالة اعتذار وتسامح)»: عناصر الرسالة الشخصية من نموذج زينة→سلمى + معايير الاعتذار الناجح + ترتيب فقرات الرسالة. متباين مع بطاقة المواصفة (صفحات 22-24 تحوي أسلوب الشرط والمحتوى الرسالي مستخلص في lesson_03 ص19-21) |
|
||||
| `arabic_conditional_lab.dart` | **مختبر جديد** — «أبني لغتي (1): أسلوب الشرط»: تصنيف الأدوات (جازمة/غير جازمة) + نموذج إعراب «تأتِهِ» + تحليل «أيّ خطأ تخطئْ» إلى الأركان. الآيات المتوضعِة بالرموز وفراغات التمرين الناقصة تخطّيها حتى المراجعة |
|
||||
| `arabic_informative_style_lab.dart` | **مختبر جديد** — «أبني لغتي (2): الأسلوب الخبري»: تصنيف جمل الدرس (خبرية/إنشائية) + إكمال قواعد التعريف (يحتمل الصدق والكذب؛ خبري/إنشائي؛ الاسمية والفعلية) + ميزان صدق/كذب الخبر بمطابقة الواقع |
|
||||
| `arabic_unit2_listening_lab.dart` | **مختبر جديد (الوحدة 02 درس 01)** — «أستمع بانتباه وتركيز — قصة الضيف»: تصحيح العبارات (4 صواب + 1 خطأ) + تمييز شخصيات (صفتا الضيف والراوي) + ترتيب مراحل الحكاية + نقطة تحوّل اليوم السابع. يغطي قابل الإثبات من صفحات 34-36؛ صفحة 37 تعود للدرس الثاني (أتحدث بطلاقة) — تفاوت مسجّل |
|
||||
| `arabic_unit2_speaking_lab.dart` | **مختبر جديد (الوحدة 02 درس 02)** — «أتحدثُ بطلاقةٍ (العرض التقديمي)»: عبارات قيم الوطنية (صواب/خطأ) + عناصر العرض المطلوبة من نص النشاط (مهارات التواصل البصري، الطلاقة، الزمن المحدد) + إسناد «القول إلى مضمونه» (رسالة الملك الحسين الثاني بعيد ميلاده الستين: لن أنسى... والحمى شرفٌ وواجبٌ). نسبة قول «والدي الحسين» ملتبسة الـOCR ولم تُسمَّ؛ المصدر صفحة 38 أحادية وتحتاج مراجعة بشرية |
|
||||
| `arabic_unit2_poetry_lab.dart` | **مختبر جديد (الوحدة 02 درس 03)** — «أقرأُ بطلاقةٍ وفهمٍ (إلى الصامدين غرب النهر)» لخالد محادين: مطابقة معجم القصيدة (الأنداد/الكابي/بيادر/سفر) + ترتيب سير جوّ القصيدة (بكاء الضياع ← رسائل الصامدين ← بيان الارتباط ← خاتمة متفائلة) + تصنيف خصائص شعر التفعيلة (أسطر متباينة الطول، قوافٍ متعددة). أنشطة (2.3)/(3.3) مكثفة ونصوص موازنة البرغوثي وديوان فدوى طوقان لم تُنمذج بنصوص مخترعة |
|
||||
| `arabic_unit2_writing_lab.dart` | **مختبر جديد (الوحدة 02 درس 04)** — «أكتبُ محتوى (تحليل النص الشعري)»: عناصرُ العملِ الأدبيِّ (الأفكار/العواطف/الخيال/اللغة/موسيقا الشعر + أدوات الربط) بالتصنيف، وترتيب معايير التحليل السبع في مسارٍ كتابيّ (الديوان والمناسبة ← الأفكار والعاطفة والتصوير ← دقة الألفاظ والأساليب ← أدوات الربط والاستشهاد بين قوسين)، ولقطاتُ تحليلِ مقطع عبد الكريم الكرمي (الأرض أُمٌّ والخضوع «تزحف» والتراب زهرٌ). نشاطُ «أردن يا بلدي» لحبيب الزيودي (ص49) مهمةُ كتابةٍ حرةٍ تُنجزُ خارج المختبر |
|
||||
| `labs_gallery_screen.dart` | سطّح معاينة تأليفية بشارات الحالة |
|
||||
| `subject_virtual_labs_view.dart` | منشور فقط + حالة فارغة صادقة + رابط المعرض |
|
||||
| `virtual_labs_smoke_test.dart` | **36 اختباراً ✅** (44/28/16، تفرّد، رفض العنوان، صفر منشورات، تفاعلات عربية ×22) — ومن ثمّ كامل المجموعة 39/39 ✅ (36 + widget/fingerprint) |
|
||||
| `flutter analyze` | 0 أخطاء |
|
||||
|
||||
## الربط المقرر (23 درساً + 16 أداة)
|
||||
|
||||
- **17 السابقة** + العربية: `arb-listen` ← `arabic_10_semester_1_unit_01_lesson_01` (قصة كعب — الاستماع الواعي)، `arb-apology` ← `arabic_10_semester_1_unit_01_lesson_02` (فن الاعتذار وقيم التسامح)، `arb-quranic-read` ← `arabic_10_semester_1_unit_01_lesson_03` (أقرأ بطلاقة وفهم — الاعتذار في قصص قرآنية)، `arb-letter` ← `arabic_10_semester_1_unit_01_lesson_04` (أكتب محتوى — رسالة اعتذار وتسامح)، `arb-conditional` ← `arabic_10_semester_1_unit_01_lesson_05` (أبني لغتي — أسلوب الشرط)، `arb-informative` ← `arabic_10_semester_1_unit_01_lesson_06` (أبني لغتي — الأسلوب الخبري)، `arb-unit2-listen` ← `arabic_10_semester_1_unit_02_lesson_01` (الوحدة 02 — أستمع بانتباه وتركيز؛ قصة الضيف)، `arb-unit2-speak` ← `arabic_10_semester_1_unit_02_lesson_02` (الوحدة 02 — أتحدث بطلاقة؛ العرض التقديمي وعرض الوطنية)، `arb-unit2-poem` ← `arabic_10_semester_1_unit_02_lesson_03` (الوحدة 02 — أقرأ بطلاقة وفهم؛ إلى الصامدين غرب النهر)، `arb-unit2-write` ← `arabic_10_semester_1_unit_02_lesson_04` (الوحدة 02 — أكتب محتوى؛ تحليل النص الشعري).
|
||||
- **16 أداة تأليف (غير مربوطة بدرس)**: che-flame، bio-finches، bio-phage، bio-scope، ear-mohs، ear-strata، eng-phon، eng-tense، arb-syntax، arb-prosody، isl-tajweed، isl-inherit، fin-budget، his-chrono، dig-flow، dig-sort.
|
||||
|
||||
### تفاوت مسجّل للمراجعة
|
||||
- مواصفة `lesson_03` تذكر «سينية البحتري» بينما صفحات الكوربوس (13-18) تعرض القراءة الصامتة وثقافة الاعتذار القرآني واعتذار موسى للعبد الصالح. بُني المختبر على المستخلص الفعلي فقط، والتفاوت مسجّل في `footerNote` ويحتاج قرار مراجع أكاديمي قبل أي نشر.
|
||||
- مواصفة `lesson_04` تربط صفحات 22-24 بينما هذه الصفحات في المستخلص تعرض «أسلوب الشرط»، ومحتو الدرس الفعلي (رسالة زينة→سلمى وعناصرها) مستخلص في نهاية `lesson_03.md` (صفحات 19-21). بُني المختبر على المحتوى الرسالي فقط (منقول نصّياً) والتفاوت مسجّل في `footerNote` — يحتاج إعادة إسناد صفحات الكوربوس قبل النشر.
|
||||
- `lesson_05` (أسلوب الشرط): الآيات المستخرجة بالرموز (U+E7xx) وفراغات التمرينات (أكمل) غير قابلة للتحقق من نص الكوربوس — تخطّى المختبر هذه المواضع الصريحة وبُني على النص القابل للإثبات فقط (التصنيف، الإعراب، الأركان).
|
||||
- `lesson_06` (الأسلوب الخبري): صفحة 31 «أدوّن ما تعلمته» صفحة تلخيص ذاتية بلا محتوى منهجيّ — لم يُبنَ عليها ولم تُخترع بيانات.
|
||||
- `unit_02 lesson_01` (قصة الضيف): صفحة 37 من نطاقها تعود فعليًا للدرس الثاني (أتحدث بطلاقة)؛ بُني المختبر على قابل الإثبات من صفحات 34-36 (آداب الاستماع، تصحيح العبارات، نقطة اليوم السابع، أسباب القوة، النهاية) مع تمييز صفات الشخصيات، والأصناف النصية للوصف، و«اليوم السابع نقطة التحوّل» قابلة للإثبات ذاته بتصحيح من المراجع قبل النشر.
|
||||
- `unit_02 lesson_02` (أتحدث بطلاقة): المصدر صفحة 38 فقط ونصُّ OCR متداخل شديداً (النشاط والقيم مع الرسالة الملكية في فقرة واحدة). بُني المختبر على الجُمل القابلة للفصل (نشاط 3.2 «أصمّمُ عرضًا تقديميًّا...»، وقيم الوطنية، ورسالة الملك الحسين الثاني بعيد ميلاده الستين). إسناد قول «كلماتِ والدي الحسين» ملتبس بالـOCR — لم يُسمَّ الشخص ولم تُخترع له ترجمة ولا نسبة.
|
||||
- `unit_02 lesson_03` (إلى الصامدين غرب النهر): صفحات 39-45 ونصوص OCR مكثفة ومتقطعة على القصيدة والحواشي. أنشطة (2.3)/(3.3) (الموازنة مع عبد الرزاق البرغوثي وطلب العودة لديوان فدوى طوقان «لن أبكي») لم تُنمذج لغياب النصوص الصافية؛ بُني المختبر على المعجم وجوّ النص وشعر التفعيلة فحسب.
|
||||
- `unit_02 lesson_04` (تحليل النص الشعري): صفحات 46-49؛ مقدمة التحليل (ص46) وصفٌ عامُّ OCR متداخل وقد جمع في اللقطة الأولى ما بين عناصر العمل الأدبي وتعريف القراءة التحليلية؛ نشاطُ (2.4) «أرْدُن يا بلدي» نصٌّ كامل بلا تمارين قابلة للتحقق — تُرك تحليلُهُ حُرًّا خارج المختبر ولم تُخترع أسئلة عليه. مسودّة مواصفة عامة تُكملها المراجعة الأكاديمية.
|
||||
|
||||
## الربطات المؤقتة (تحتاج تحققاً من المصدر قبل أي إطلاق)
|
||||
|
||||
bio-key، ear-rock، mat-trig، fin-feas، his-persian، geo-atmo، civ-active — مبنية على العناوين فقط حتى مراجعة `source_markdown`.
|
||||
|
||||
## المانع
|
||||
|
||||
- مراجعة أكاديمية لكل المواصفات (لا شيء `released`).
|
||||
- خلل كوربوس: الإسلامية S1 وحدات 02–04 ملوثة؛ لا ميراث/فرائض بالكوربوس؛ العربية S2 تدوير وحدات + OCR.
|
||||
- حزمة النشر من الخادم + ربط `has_virtual_lab` في النموذج لم تُنشأ بعد (عمل خلفي قادم).
|
||||
|
||||
## تنفيذ الدروس — المصفوفة
|
||||
|
||||
| المبحث | عدد دروس | مكتمل/منشور | ملاحظات |
|
||||
|---|---|---|---|
|
||||
| العربية لغتي | 54 | 11/0 | الوحدة 01 مكتملة (6/6) + U2L1–U2L5 في الوحدة 02؛ التالي U2L6 |
|
||||
|
||||
(توسيع الجدول مع كل مبحث مُنجَز.)
|
||||
@@ -0,0 +1,375 @@
|
||||
# فهرس مواصفات مختبرات الصف العاشر
|
||||
|
||||
> مولّد من `manifest.accepted.json`، وعدده 369 ملفاً. جميعها مسودات مراجعة وليست دليلاً على النشر للطلاب.
|
||||
|
||||
| هوية الدرس | المبحث | الدرس | ملف المختبر |
|
||||
|---|---|---|---|
|
||||
| `arabic_10_semester_1_unit_01_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ (قصة كعب بن مالك رضي الله عنه) | [المواصفة](./arabic_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_1_unit_01_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (فن الاعتذار وقيم التسامح) | [المواصفة](./arabic_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_1_unit_01_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (سينية البحتري في الاعتذار) | [المواصفة](./arabic_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_1_unit_01_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (كتابة رسالة اعتذار وتسامح) | [المواصفة](./arabic_10/semester_1/unit_01/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_1_unit_01_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): أسلوبُ الشَّرطِ | [المواصفة](./arabic_10/semester_1/unit_01/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_1_unit_01_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): الأسلوبُ الخَبَريُّ | [المواصفة](./arabic_10/semester_1/unit_01/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_1_unit_02_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_1_unit_02_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (العرض التقديمي) | [المواصفة](./arabic_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_1_unit_02_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (إلى الصامدين غرب النهر) | [المواصفة](./arabic_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_1_unit_02_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (تحليل النص الشعري) | [المواصفة](./arabic_10/semester_1/unit_02/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_1_unit_02_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): أسلوبُ النِّداءِ | [المواصفة](./arabic_10/semester_1/unit_02/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_1_unit_02_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): الأسلوبُ الإنشائيُّ (الإنشاء الطلبي) | [المواصفة](./arabic_10/semester_1/unit_02/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_1_unit_03_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_1_unit_03_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (قراءة الصورة) | [المواصفة](./arabic_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_1_unit_03_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (اللغة الأم) | [المواصفة](./arabic_10/semester_1/unit_03/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_1_unit_03_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (تحليل لوحة فنية) | [المواصفة](./arabic_10/semester_1/unit_03/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_1_unit_03_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): معاني الأفعال المزيدة | [المواصفة](./arabic_10/semester_1/unit_03/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_1_unit_03_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): الأسلوب الإنشائي غير الطلبي | [المواصفة](./arabic_10/semester_1/unit_03/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_1_unit_04_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_1/unit_04/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_1_unit_04_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (كيف أقدم شخصية أدبية؟) | [المواصفة](./arabic_10/semester_1/unit_04/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_1_unit_04_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (شغف القراءة وحكايات أخرى) | [المواصفة](./arabic_10/semester_1/unit_04/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_1_unit_04_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (صفحة أولى من سيرتي الذاتية) | [المواصفة](./arabic_10/semester_1/unit_04/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_1_unit_04_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): مصادر الأفعال الثلاثية | [المواصفة](./arabic_10/semester_1/unit_04/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_1_unit_04_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها | [المواصفة](./arabic_10/semester_1/unit_04/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_1_unit_05_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_1/unit_05/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_1_unit_05_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (قراءة المشاعر) | [المواصفة](./arabic_10/semester_1/unit_05/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_1_unit_05_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (بم التعلل لا أهل ولا وطن) | [المواصفة](./arabic_10/semester_1/unit_05/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_1_unit_05_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (نص إخباري عن مناسبة أممية) | [المواصفة](./arabic_10/semester_1/unit_05/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_1_unit_05_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): مصادر الأفعال غير الثلاثية | [المواصفة](./arabic_10/semester_1/unit_05/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_1_unit_05_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها | [المواصفة](./arabic_10/semester_1/unit_05/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_2_unit_06_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_2/unit_06/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_2_unit_06_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (المناقشة الجماعية الحرة) | [المواصفة](./arabic_10/semester_2/unit_06/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_2_unit_06_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (سينية أحمد شوقي في الحنين للوطن) | [المواصفة](./arabic_10/semester_2/unit_06/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_2_unit_06_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (مقال تحليلي عن تجربة شعورية) | [المواصفة](./arabic_10/semester_2/unit_06/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_2_unit_06_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): الممنوع من الصرف | [المواصفة](./arabic_10/semester_2/unit_06/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_2_unit_06_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): نوعا التشبيه: المؤكد المفصل والمؤكد المجمل | [المواصفة](./arabic_10/semester_2/unit_06/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_2_unit_07_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_2/unit_07/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_2_unit_07_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (فن المناظرة وأدوار المتحدثين) | [المواصفة](./arabic_10/semester_2/unit_07/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_2_unit_07_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (عصر المعلومات بعد الإنترنت: قضية إشكالية) | [المواصفة](./arabic_10/semester_2/unit_07/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_2_unit_07_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (النص الجدلي) | [المواصفة](./arabic_10/semester_2/unit_07/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_2_unit_07_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): تمييز الذات | [المواصفة](./arabic_10/semester_2/unit_07/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_2_unit_07_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): صيغة المبالغة والصفة المشبهة | [المواصفة](./arabic_10/semester_2/unit_07/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_2_unit_08_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_2/unit_08/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_2_unit_08_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (إدارة الندوة) | [المواصفة](./arabic_10/semester_2/unit_08/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_2_unit_08_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (مقطوعات من الغزل العذري) | [المواصفة](./arabic_10/semester_2/unit_08/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_2_unit_08_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (إعداد مخطط مبادرة تطوعية) | [المواصفة](./arabic_10/semester_2/unit_08/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_2_unit_08_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): تثنية الاسم المقصور والمنقوص والممدود وجمعه | [المواصفة](./arabic_10/semester_2/unit_08/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_2_unit_08_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها (بحر الهزج) | [المواصفة](./arabic_10/semester_2/unit_08/lesson_06.lab.md) |
|
||||
| `arabic_10_semester_2_unit_09_lesson_01` | اللغة العربية | الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ | [المواصفة](./arabic_10/semester_2/unit_09/lesson_01.lab.md) |
|
||||
| `arabic_10_semester_2_unit_09_lesson_02` | اللغة العربية | الدرس الثاني: أتحدثُ بطالقةٍ (العرض الشفوي لقصة نجاح) | [المواصفة](./arabic_10/semester_2/unit_09/lesson_02.lab.md) |
|
||||
| `arabic_10_semester_2_unit_09_lesson_03` | اللغة العربية | الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (المفكر العربي إدوارد سعيد) | [المواصفة](./arabic_10/semester_2/unit_09/lesson_03.lab.md) |
|
||||
| `arabic_10_semester_2_unit_09_lesson_04` | اللغة العربية | الدرس الرابع: أكتبُ محتوى (تقرير علمي عن شخصية) | [المواصفة](./arabic_10/semester_2/unit_09/lesson_04.lab.md) |
|
||||
| `arabic_10_semester_2_unit_09_lesson_05` | اللغة العربية | الدرس الخامس: أبني لغتي (1): الأفعال المتعدية إلى مفعولين | [المواصفة](./arabic_10/semester_2/unit_09/lesson_05.lab.md) |
|
||||
| `arabic_10_semester_2_unit_09_lesson_06` | اللغة العربية | الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها (بحر المتقارب) | [المواصفة](./arabic_10/semester_2/unit_09/lesson_06.lab.md) |
|
||||
| `biology_10_semester_1_unit_01_lesson_01` | العلوم الحياتية | الدرس الأول: النقل عبر الغشاء البلازمي والخاصية الأسموزية والانتشار | [المواصفة](./biology_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `biology_10_semester_1_unit_01_unit_review` | العلوم الحياتية | مراجعة واختبار الوحدة الأولى: أنشطة الخلية | [المواصفة](./biology_10/semester_1/unit_01/unit_review.lab.md) |
|
||||
| `biology_10_semester_1_unit_02_lesson_01` | العلوم الحياتية | الدرس الأول: المادة الوراثية وتضاعف الحمض النووي DNA وبناء البروتين | [المواصفة](./biology_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `biology_10_semester_1_unit_02_unit_review` | العلوم الحياتية | مراجعة واختبار الوحدة الثانية: الوراثة والبيولوجيا الجزيئية | [المواصفة](./biology_10/semester_1/unit_02/unit_review.lab.md) |
|
||||
| `biology_10_semester_1_unit_03_lesson_01` | العلوم الحياتية | الدرس 1: أسس علم التصنيف ومستوياته ونظام التسمية الثنائية | [المواصفة](./biology_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `biology_10_semester_1_unit_03_lesson_02` | العلوم الحياتية | الدرس 2: البكتيريا والأثريات وخصائصها الحيوية | [المواصفة](./biology_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `biology_10_semester_1_unit_03_lesson_03` | العلوم الحياتية | الدرس 3: الطلائعيات وتنوعها وأهميتها البيئية والاقتصادية | [المواصفة](./biology_10/semester_1/unit_03/lesson_03.lab.md) |
|
||||
| `biology_10_semester_1_unit_03_lesson_04` | العلوم الحياتية | الدرس 4: الفطريات وتراكيبها وطرائق تغذيتها ودورها في الطبيعة | [المواصفة](./biology_10/semester_1/unit_03/lesson_04.lab.md) |
|
||||
| `biology_10_semester_1_unit_03_unit_review` | العلوم الحياتية | مراجعة واختبار الوحدة الثالثة: تصنيف الكائنات الحية | [المواصفة](./biology_10/semester_1/unit_03/unit_review.lab.md) |
|
||||
| `biology_10_semester_2_unit_03_lesson_05` | العلوم الحياتية | الدرس 5: النباتات اللاوعائية والنباتات الوعائية اللابذرية | [المواصفة](./biology_10/semester_2/unit_03/lesson_05.lab.md) |
|
||||
| `biology_10_semester_2_unit_03_lesson_06` | العلوم الحياتية | الدرس 6: النباتات الوعائية البذرية (معراة ومغطاة البذور) | [المواصفة](./biology_10/semester_2/unit_03/lesson_06.lab.md) |
|
||||
| `biology_10_semester_2_unit_03_lesson_07` | العلوم الحياتية | الدرس 7: خصائص الحيوانات وأسس تصنيف المملكة الحيوانية | [المواصفة](./biology_10/semester_2/unit_03/lesson_07.lab.md) |
|
||||
| `biology_10_semester_2_unit_03_lesson_08` | العلوم الحياتية | الدرس 8: اللافقاريات وخصائص قبائلها الرئيسية | [المواصفة](./biology_10/semester_2/unit_03/lesson_08.lab.md) |
|
||||
| `biology_10_semester_2_unit_03_lesson_09` | العلوم الحياتية | الدرس 9: الفقاريات وطوائفها وتكيفاتها المعيشية | [المواصفة](./biology_10/semester_2/unit_03/lesson_09.lab.md) |
|
||||
| `biology_10_semester_2_unit_03_unit_review` | العلوم الحياتية | مراجعة واختبار الوحدة الثالثة: المملكة النباتية والحيوانية | [المواصفة](./biology_10/semester_2/unit_03/unit_review.lab.md) |
|
||||
| `biology_10_semester_2_unit_04_lesson_01` | العلوم الحياتية | الدرس 1: تدفق الطاقة ودورات المواد في الأنظمة البيئية | [المواصفة](./biology_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `biology_10_semester_2_unit_04_lesson_02` | العلوم الحياتية | الدرس 2: الجماعات الحيوية والعوامل المؤثرة في نموها وكثافتها | [المواصفة](./biology_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `biology_10_semester_2_unit_04_unit_review` | العلوم الحياتية | مراجعة واختبار الوحدة الرابعة: البيئة والتنوع الحيوي | [المواصفة](./biology_10/semester_2/unit_04/unit_review.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_01_lesson_01` | الكيمياء | الدرس الأول: نظرية بور لذرة الهيدروجين والطيوف الذرية | [المواصفة](./chemistry_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_01_lesson_02` | الكيمياء | الدرس الثاني: النموذج الميكانيكي الموجي للذرة وأفلاك الطاقة | [المواصفة](./chemistry_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_01_unit_review` | الكيمياء | مراجعة واختبار الوحدة الأولى: بنية الذرة | [المواصفة](./chemistry_10/semester_1/unit_01/unit_review.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_02_lesson_01` | الكيمياء | الدرس الأول: قواعد التوزيع الإلكتروني للذرات والأيونات | [المواصفة](./chemistry_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_02_lesson_02` | الكيمياء | الدرس الثاني: الخصائص الدورية للعناصر (نصف القطر، طاقة التأين، الكهروسالبية) | [المواصفة](./chemistry_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_02_unit_review` | الكيمياء | مراجعة واختبار الوحدة الثانية: التوزيع الإلكتروني والدورية | [المواصفة](./chemistry_10/semester_1/unit_02/unit_review.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_03_lesson_01` | الكيمياء | الدرس الأول: الروابط الكيميائية وأنواعها (الأيونية، التساهمية، والفلزية) | [المواصفة](./chemistry_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_03_lesson_02` | الكيمياء | الدرس الثاني: الصيغ الكيميائية وخصائص المركبات وقوى التجاذب بين الجزيئات | [المواصفة](./chemistry_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `chemistry_10_semester_1_unit_03_unit_review` | الكيمياء | مراجعة واختبار الوحدة الثالثة: الروابط والمركبات الكيميائية | [المواصفة](./chemistry_10/semester_1/unit_03/unit_review.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_04_lesson_01` | الكيمياء | الدرس الأول: التفاعلات الكيميائية وأنواعها وموازنة المعادلات | [المواصفة](./chemistry_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_04_lesson_02` | الكيمياء | الدرس الثاني: المول والكتلة المولية والكتلة الصيغية | [المواصفة](./chemistry_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_04_lesson_03` | الكيمياء | الدرس الثالث: الحسابات الكيميائية المبنية على المعادلات والمردود المئوي | [المواصفة](./chemistry_10/semester_2/unit_04/lesson_03.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_04_unit_review` | الكيمياء | مراجعة واختبار الوحدة الرابعة: التفاعلات والحسابات الكيميائية | [المواصفة](./chemistry_10/semester_2/unit_04/unit_review.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_05_lesson_01` | الكيمياء | الدرس الأول: تغيرات الطاقة في التفاعلات الكيميائية (الماصة والطاردة) | [المواصفة](./chemistry_10/semester_2/unit_05/lesson_01.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_05_lesson_02` | الكيمياء | الدرس الثاني: الطاقة الممتصة والطاقة المنبعثة من التفاعلات وقانون حفظ الطاقة | [المواصفة](./chemistry_10/semester_2/unit_05/lesson_02.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_05_lesson_03` | الكيمياء | الدرس الثالث: السعة الحرارية والحرارة النوعية وحساب كمية الحرارة | [المواصفة](./chemistry_10/semester_2/unit_05/lesson_03.lab.md) |
|
||||
| `chemistry_10_semester_2_unit_05_unit_review` | الكيمياء | مراجعة واختبار الوحدة الخامسة: الطاقة الكيميائية | [المواصفة](./chemistry_10/semester_2/unit_05/unit_review.lab.md) |
|
||||
| `civics_10_semester_1_unit_01_lesson_01` | التربية الوطنية والمدنية | الدرس الأول: التربية الوطنية والمدنية: المفهوم والدلالات | [المواصفة](./civics_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `civics_10_semester_1_unit_01_lesson_02` | التربية الوطنية والمدنية | الدرس الثاني: هويتي الأردنية | [المواصفة](./civics_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `civics_10_semester_1_unit_01_lesson_03` | التربية الوطنية والمدنية | الدرس الثالث: المواطنة | [المواصفة](./civics_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `civics_10_semester_1_unit_01_lesson_04` | التربية الوطنية والمدنية | الدرس الرابع: الديمقراطية | [المواصفة](./civics_10/semester_1/unit_01/lesson_04.lab.md) |
|
||||
| `civics_10_semester_1_unit_01_lesson_05` | التربية الوطنية والمدنية | الدرس الخامس: خدمة العلم | [المواصفة](./civics_10/semester_1/unit_01/lesson_05.lab.md) |
|
||||
| `civics_10_semester_1_unit_01_unit_review` | التربية الوطنية والمدنية | مراجعة الوحدة الأولى: التربية الوطنية والمدنية | [المواصفة](./civics_10/semester_1/unit_01/unit_review.lab.md) |
|
||||
| `civics_10_semester_1_unit_02_lesson_01` | التربية الوطنية والمدنية | الدرس الأول: الدستور الأردني وسيادة القانون | [المواصفة](./civics_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `civics_10_semester_1_unit_02_lesson_02` | التربية الوطنية والمدنية | الدرس الثاني: المشاركة في الحياة العامة | [المواصفة](./civics_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `civics_10_semester_1_unit_02_lesson_03` | التربية الوطنية والمدنية | الدرس الثالث: الانتخابات (أنا والصندوق) | [المواصفة](./civics_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `civics_10_semester_1_unit_02_lesson_04` | التربية الوطنية والمدنية | الدرس الرابع: الأحزاب السياسية | [المواصفة](./civics_10/semester_1/unit_02/lesson_04.lab.md) |
|
||||
| `civics_10_semester_1_unit_02_lesson_05` | التربية الوطنية والمدنية | الدرس الخامس: أردن المستقبل | [المواصفة](./civics_10/semester_1/unit_02/lesson_05.lab.md) |
|
||||
| `civics_10_semester_1_unit_02_unit_review` | التربية الوطنية والمدنية | مراجعة الوحدة الثانية: المشاركة في الحياة العامة | [المواصفة](./civics_10/semester_1/unit_02/unit_review.lab.md) |
|
||||
| `civics_10_semester_2_unit_03_lesson_01` | التربية الوطنية والمدنية | الدرس الأول: الشرعة الدولية لحقوق الإنسان | [المواصفة](./civics_10/semester_2/unit_03/lesson_01.lab.md) |
|
||||
| `civics_10_semester_2_unit_03_lesson_02` | التربية الوطنية والمدنية | الدرس الثاني: حقوق الإنسان في الأردن | [المواصفة](./civics_10/semester_2/unit_03/lesson_02.lab.md) |
|
||||
| `civics_10_semester_2_unit_03_unit_review` | التربية الوطنية والمدنية | مراجعة الوحدة الثالثة: حقوق الإنسان | [المواصفة](./civics_10/semester_2/unit_03/unit_review.lab.md) |
|
||||
| `civics_10_semester_2_unit_04_lesson_01` | التربية الوطنية والمدنية | الدرس الأول: العلاقات الأردنية العربية | [المواصفة](./civics_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `civics_10_semester_2_unit_04_lesson_02` | التربية الوطنية والمدنية | الدرس الثاني: العلاقات الأردنية الدولية | [المواصفة](./civics_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `civics_10_semester_2_unit_04_unit_review` | التربية الوطنية والمدنية | مراجعة الوحدة الرابعة: العلاقات الأردنية الدولية | [المواصفة](./civics_10/semester_2/unit_04/unit_review.lab.md) |
|
||||
| `civics_10_semester_2_unit_05_lesson_01` | التربية الوطنية والمدنية | الدرس الأول: التراث الوطني | [المواصفة](./civics_10/semester_2/unit_05/lesson_01.lab.md) |
|
||||
| `civics_10_semester_2_unit_05_lesson_02` | التربية الوطنية والمدنية | الدرس الثاني: المحافظة على التراث | [المواصفة](./civics_10/semester_2/unit_05/lesson_02.lab.md) |
|
||||
| `civics_10_semester_2_unit_05_unit_review` | التربية الوطنية والمدنية | مراجعة الوحدة الخامسة: التراث | [المواصفة](./civics_10/semester_2/unit_05/unit_review.lab.md) |
|
||||
| `civics_10_semester_2_unit_06_lesson_01` | التربية الوطنية والمدنية | الدرس الأول: النزاهة الوطنية | [المواصفة](./civics_10/semester_2/unit_06/lesson_01.lab.md) |
|
||||
| `civics_10_semester_2_unit_06_lesson_02` | التربية الوطنية والمدنية | الدرس الثاني: مكافحة الفساد والمحسوبية | [المواصفة](./civics_10/semester_2/unit_06/lesson_02.lab.md) |
|
||||
| `civics_10_semester_2_unit_06_unit_review` | التربية الوطنية والمدنية | مراجعة الوحدة السادسة: النزاهة ومكافحة الفساد | [المواصفة](./civics_10/semester_2/unit_06/unit_review.lab.md) |
|
||||
| `civics_10_semester_2_unit_07_lesson_01` | التربية الوطنية والمدنية | الدرس الأول: المحتوى الإعلامي | [المواصفة](./civics_10/semester_2/unit_07/lesson_01.lab.md) |
|
||||
| `civics_10_semester_2_unit_07_lesson_02` | التربية الوطنية والمدنية | الدرس الثاني: السلوك | [المواصفة](./civics_10/semester_2/unit_07/lesson_02.lab.md) |
|
||||
| `civics_10_semester_2_unit_07_unit_review` | التربية الوطنية والمدنية | مراجعة الوحدة السابعة: مهارات حياتية | [المواصفة](./civics_10/semester_2/unit_07/unit_review.lab.md) |
|
||||
| `digital_skills_10_semester_1_unit_01_lesson_01` | المهارات الرقمية | الدرس الأول: البيانات والمعلومات (Information and Data) | [المواصفة](./digital_skills_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `digital_skills_10_semester_1_unit_01_lesson_02` | المهارات الرقمية | الدرس الثاني: أنواع البيانات وطرائق تنظيمها (Data Types & Organization) | [المواصفة](./digital_skills_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `digital_skills_10_semester_1_unit_01_lesson_03` | المهارات الرقمية | الدرس الثالث: التمثيل المرئي للبيانات (Data Visualization) | [المواصفة](./digital_skills_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `digital_skills_10_semester_1_unit_01_lesson_04` | المهارات الرقمية | الدرس الرابع: تحليل البيانات (Data Analysis with Excel) | [المواصفة](./digital_skills_10/semester_1/unit_01/lesson_04.lab.md) |
|
||||
| `digital_skills_10_semester_1_unit_01_unit_summary` | المهارات الرقمية | ملخص وأسئلة الوحدة وتقويم ذاتي: تحليل البيانات | [المواصفة](./digital_skills_10/semester_1/unit_01/unit_summary.lab.md) |
|
||||
| `digital_skills_10_semester_2_unit_01_lesson_01` | المهارات الرقمية | الدرس الأول: مفهوم إنترنت الأشياء ومكوناتها ووظائفها | [المواصفة](./digital_skills_10/semester_2/unit_01/lesson_01.lab.md) |
|
||||
| `digital_skills_10_semester_2_unit_01_lesson_02` | المهارات الرقمية | الدرس الثاني: تكوين شبكة إنترنت الأشياء ومحاكاة نقل البيانات | [المواصفة](./digital_skills_10/semester_2/unit_01/lesson_02.lab.md) |
|
||||
| `digital_skills_10_semester_2_unit_01_unit_review` | المهارات الرقمية | مراجعة ومشروع الوحدة الأولى: إنترنت الأشياء | [المواصفة](./digital_skills_10/semester_2/unit_01/unit_review.lab.md) |
|
||||
| `digital_skills_10_semester_2_unit_02_lesson_01` | المهارات الرقمية | الدرس الأول: أنظمة قواعد المعرفة ومفهوم المنطق في الذكاء الاصطناعي | [المواصفة](./digital_skills_10/semester_2/unit_02/lesson_01.lab.md) |
|
||||
| `digital_skills_10_semester_2_unit_02_lesson_02` | المهارات الرقمية | الدرس الثاني: تطبيقات الذكاء الاصطناعي وتقنية النانو والهولوغرام | [المواصفة](./digital_skills_10/semester_2/unit_02/lesson_02.lab.md) |
|
||||
| `digital_skills_10_semester_2_unit_02_unit_review` | المهارات الرقمية | ملخص وأسئلة الوحدة الثانية: الذكاء الاصطناعي | [المواصفة](./digital_skills_10/semester_2/unit_02/unit_review.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_01_lesson_01` | علوم الأرض والبيئة | الدرس 1: الصخور النارية (Igneous Rocks) | [المواصفة](./earth_sciences_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_01_lesson_02` | علوم الأرض والبيئة | الدرس 2: الصخور الرسوبية (Sedimentary Rocks) | [المواصفة](./earth_sciences_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_01_lesson_03` | علوم الأرض والبيئة | الدرس 3: الصخور المتحولة (Metamorphic Rocks) | [المواصفة](./earth_sciences_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_01_unit_enrichment_and_review` | علوم الأرض والبيئة | الإثراء والتوسع ومراجعة الوحدة الأولى: الصوف الصخري والصخور | [المواصفة](./earth_sciences_10/semester_1/unit_01/unit_enrichment_and_review.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_02_lesson_01` | علوم الأرض والبيئة | الدرس 1: ماهية النجوم وخصائصها الفيزيائية وطاقتها | [المواصفة](./earth_sciences_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_02_lesson_02` | علوم الأرض والبيئة | الدرس 2: الأنظمة النجمية والكوكبات النجمية | [المواصفة](./earth_sciences_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_02_lesson_03` | علوم الأرض والبيئة | الدرس 3: دورة حياة النجوم ومراحل تطورها وموتها | [المواصفة](./earth_sciences_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `earth_sciences_10_semester_1_unit_02_unit_enrichment_and_review` | علوم الأرض والبيئة | الإثراء والتوسع ومراجعة الوحدة الثانية: مقراد الكوة الدائرية والنجوم | [المواصفة](./earth_sciences_10/semester_1/unit_02/unit_enrichment_and_review.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_03_lesson_01` | علوم الأرض والبيئة | الدرس 1: الكتل والجبهات الهوائية (Air Masses and Fronts) | [المواصفة](./earth_sciences_10/semester_2/unit_03/lesson_01.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_03_lesson_02` | علوم الأرض والبيئة | الدرس 2: أنظمة الضغط الجوي (Pressure Systems) | [المواصفة](./earth_sciences_10/semester_2/unit_03/lesson_02.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_03_unit_enrichment_and_review` | علوم الأرض والبيئة | الإثراء والتوسع ومراجعة الوحدة الثالثة: بالونات الطقس والأرصاد | [المواصفة](./earth_sciences_10/semester_2/unit_03/unit_enrichment_and_review.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_04_lesson_01` | علوم الأرض والبيئة | الدرس 1: خصائص مياه المحيطات (الملوحة والحرارة والكثافة) | [المواصفة](./earth_sciences_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_04_lesson_02` | علوم الأرض والبيئة | الدرس 2: أمواج المحيط (Ocean Waves) | [المواصفة](./earth_sciences_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_04_lesson_03` | علوم الأرض والبيئة | الدرس 3: تيارات المحيط والمناخ (Ocean Currents and Climate) | [المواصفة](./earth_sciences_10/semester_2/unit_04/lesson_03.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_04_unit_enrichment_and_review` | علوم الأرض والبيئة | الإثراء والتوسع ومراجعة الوحدة الرابعة: دراسة المحيطات بالأقمار الصناعية | [المواصفة](./earth_sciences_10/semester_2/unit_04/unit_enrichment_and_review.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_05_lesson_01` | علوم الأرض والبيئة | الدرس 1: مفهوم المياه العادمة ومصادرها | [المواصفة](./earth_sciences_10/semester_2/unit_05/lesson_01.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_05_lesson_02` | علوم الأرض والبيئة | الدرس 2: الآثار السلبية للمياه العادمة على البيئة والصحة | [المواصفة](./earth_sciences_10/semester_2/unit_05/lesson_02.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_05_lesson_03` | علوم الأرض والبيئة | الدرس 3: معالجة المياه العادمة ومراحلها وتطبيقاتها في الأردن | [المواصفة](./earth_sciences_10/semester_2/unit_05/lesson_03.lab.md) |
|
||||
| `earth_sciences_10_semester_2_unit_05_unit_enrichment_and_review` | علوم الأرض والبيئة | الإثراء والتوسع ومراجعة الوحدة الخامسة: فوائد الحمأة ومعالجة المياه | [المواصفة](./earth_sciences_10/semester_2/unit_05/unit_enrichment_and_review.lab.md) |
|
||||
| `english_10_semester_1_unit_01_lesson_01` | اللغة الإنجليزية | Unit 01 - Lesson 1: Grammar & Vocabulary (Present Simple & Continuous, Verb phrases with dress) | [المواصفة](./english_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `english_10_semester_1_unit_01_lesson_02` | اللغة الإنجليزية | Unit 01 - Lesson 2: Vocabulary & Listening (Appearance, Clothes & Accessories, Jobs Podcast) | [المواصفة](./english_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `english_10_semester_1_unit_01_lesson_03` | اللغة الإنجليزية | Unit 01 - Lesson 3: Reading (The Power of Appearance) | [المواصفة](./english_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `english_10_semester_1_unit_01_lesson_04` | اللغة الإنجليزية | Unit 01 - Lesson 4: Grammar (Articles & Pronunciation) | [المواصفة](./english_10/semester_1/unit_01/lesson_04.lab.md) |
|
||||
| `english_10_semester_1_unit_01_lesson_05` | اللغة الإنجليزية | Unit 01 - Lesson 5: Speaking (Participating in Conversations) | [المواصفة](./english_10/semester_1/unit_01/lesson_05.lab.md) |
|
||||
| `english_10_semester_1_unit_01_lesson_06` | اللغة الإنجليزية | Unit 01 - Lesson 6: Writing (An Informal Email) | [المواصفة](./english_10/semester_1/unit_01/lesson_06.lab.md) |
|
||||
| `english_10_semester_1_unit_02_lesson_01` | اللغة الإنجليزية | Unit 02 - Lesson 1: Grammar & Vocabulary (Present Perfect Simple & Continuous, Scientific Research) | [المواصفة](./english_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `english_10_semester_1_unit_02_lesson_02` | اللغة الإنجليزية | Unit 02 - Lesson 2: Reading (Science Fiction or Science Fact?) | [المواصفة](./english_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `english_10_semester_1_unit_02_lesson_03` | اللغة الإنجليزية | Unit 02 - Lesson 3: Vocabulary & Grammar (Science, Think & Mind, Verb Patterns) | [المواصفة](./english_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `english_10_semester_1_unit_02_lesson_04` | اللغة الإنجليزية | Unit 02 - Lesson 4: Listening (Uses of Drones) | [المواصفة](./english_10/semester_1/unit_02/lesson_04.lab.md) |
|
||||
| `english_10_semester_1_unit_02_lesson_05` | اللغة الإنجليزية | Unit 02 - Lesson 5: Speaking (Making Choices) | [المواصفة](./english_10/semester_1/unit_02/lesson_05.lab.md) |
|
||||
| `english_10_semester_1_unit_02_lesson_06` | اللغة الإنجليزية | Unit 02 - Lesson 6: Writing (A Blog Post - Health and Computers) | [المواصفة](./english_10/semester_1/unit_02/lesson_06.lab.md) |
|
||||
| `english_10_semester_1_unit_02_life_skills` | اللغة الإنجليزية | Unit 02 - Life Skills: How to Give a Presentation | [المواصفة](./english_10/semester_1/unit_02/life_skills.lab.md) |
|
||||
| `english_10_semester_1_unit_03_lesson_01` | اللغة الإنجليزية | Unit 03 - Lesson 1: Grammar & Vocabulary (Past Simple, Past Continuous, Past Perfect, Sports) | [المواصفة](./english_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `english_10_semester_1_unit_03_lesson_02` | اللغة الإنجليزية | Unit 03 - Lesson 2: Vocabulary & Listening (Injuries, Accidents & Emergencies) | [المواصفة](./english_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `english_10_semester_1_unit_03_lesson_03` | اللغة الإنجليزية | Unit 03 - Lesson 3: Grammar (Used to & Would) | [المواصفة](./english_10/semester_1/unit_03/lesson_03.lab.md) |
|
||||
| `english_10_semester_1_unit_03_lesson_04` | اللغة الإنجليزية | Unit 03 - Lesson 4: Reading & Vocabulary (Say 'Yum' to Healthy Eating - Diet & Nutrition) | [المواصفة](./english_10/semester_1/unit_03/lesson_04.lab.md) |
|
||||
| `english_10_semester_1_unit_03_lesson_05` | اللغة الإنجليزية | Unit 03 - Lesson 5: Writing (A Short Story) | [المواصفة](./english_10/semester_1/unit_03/lesson_05.lab.md) |
|
||||
| `english_10_semester_1_unit_04_lesson_01` | اللغة الإنجليزية | Unit 04 - Lesson 1: Grammar & Vocabulary (Modal and Related Verbs, Air Travel) | [المواصفة](./english_10/semester_1/unit_04/lesson_01.lab.md) |
|
||||
| `english_10_semester_1_unit_04_lesson_02` | اللغة الإنجليزية | Unit 04 - Lesson 2: Reading (How to Have the Perfect Family Holiday) | [المواصفة](./english_10/semester_1/unit_04/lesson_02.lab.md) |
|
||||
| `english_10_semester_1_unit_04_lesson_03` | اللغة الإنجليزية | Unit 04 - Lesson 3: Vocabulary & Grammar (Travel Essentials, Relative Clauses) | [المواصفة](./english_10/semester_1/unit_04/lesson_03.lab.md) |
|
||||
| `english_10_semester_1_unit_04_lesson_04` | اللغة الإنجليزية | Unit 04 - Lesson 4: Listening & Speaking (Urban Transport, Agreeing & Disagreeing) | [المواصفة](./english_10/semester_1/unit_04/lesson_04.lab.md) |
|
||||
| `english_10_semester_1_unit_04_lesson_05` | اللغة الإنجليزية | Unit 04 - Lesson 5: Writing (A Formal Email of Enquiry) | [المواصفة](./english_10/semester_1/unit_04/lesson_05.lab.md) |
|
||||
| `english_10_semester_1_unit_04_life_skills` | اللغة الإنجليزية | Unit 04 - Life Skills: How to Take Part in a Debate | [المواصفة](./english_10/semester_1/unit_04/life_skills.lab.md) |
|
||||
| `english_10_semester_1_unit_05_culture_spot` | اللغة الإنجليزية | Unit 05 - Culture Spot & Literature Spot | [المواصفة](./english_10/semester_1/unit_05/culture_spot.lab.md) |
|
||||
| `english_10_semester_1_unit_05_lesson_01` | اللغة الإنجليزية | Unit 05 - Lesson 1: Grammar & Vocabulary (Talking About the Future, Personality Adjectives) | [المواصفة](./english_10/semester_1/unit_05/lesson_01.lab.md) |
|
||||
| `english_10_semester_1_unit_05_lesson_02` | اللغة الإنجليزية | Unit 05 - Lesson 2: Grammar & Vocabulary (Future Continuous & Perfect, Studying Phrasal Verbs) | [المواصفة](./english_10/semester_1/unit_05/lesson_02.lab.md) |
|
||||
| `english_10_semester_1_unit_05_lesson_03` | اللغة الإنجليزية | Unit 05 - Lesson 3: Speaking (Describing Strengths and Weaknesses) | [المواصفة](./english_10/semester_1/unit_05/lesson_03.lab.md) |
|
||||
| `english_10_semester_1_unit_05_lesson_04` | اللغة الإنجليزية | Unit 05 - Lesson 4: Listening (The Gig Economy, Taking Notes) | [المواصفة](./english_10/semester_1/unit_05/lesson_04.lab.md) |
|
||||
| `english_10_semester_1_unit_05_lesson_05` | اللغة الإنجليزية | Unit 05 - Lesson 5: Reading & Vocabulary (The Future of Work, Future Jobs) | [المواصفة](./english_10/semester_1/unit_05/lesson_05.lab.md) |
|
||||
| `english_10_semester_1_unit_05_lesson_06` | اللغة الإنجليزية | Unit 05 - Lesson 6: Writing (Personal Statement for University) | [المواصفة](./english_10/semester_1/unit_05/lesson_06.lab.md) |
|
||||
| `english_10_semester_2_unit_06_lesson_01` | اللغة الإنجليزية | Unit 06 - Lesson 1: Grammar & Vocabulary (First & Second Conditionals, Mobile Etiquette) | [المواصفة](./english_10/semester_2/unit_06/lesson_01.lab.md) |
|
||||
| `english_10_semester_2_unit_06_lesson_02` | اللغة الإنجليزية | Unit 06 - Lesson 2: Reading (If We Kept It, We'd Be Rich) | [المواصفة](./english_10/semester_2/unit_06/lesson_02.lab.md) |
|
||||
| `english_10_semester_2_unit_06_lesson_03` | اللغة الإنجليزية | Unit 06 - Lesson 3: Listening & Grammar (Zero Conditional, Alternatives to If) | [المواصفة](./english_10/semester_2/unit_06/lesson_03.lab.md) |
|
||||
| `english_10_semester_2_unit_06_lesson_04` | اللغة الإنجليزية | Unit 06 - Lesson 4: Speaking & Writing (Advice, For-and-Against Essay) | [المواصفة](./english_10/semester_2/unit_06/lesson_04.lab.md) |
|
||||
| `english_10_semester_2_unit_06_life_skills` | اللغة الإنجليزية | Unit 06 - Life Skills: How to Set SMART Goals | [المواصفة](./english_10/semester_2/unit_06/life_skills.lab.md) |
|
||||
| `english_10_semester_2_unit_07_lesson_01` | اللغة الإنجليزية | Unit 07 - Lesson 1: Grammar & Vocabulary (Reported Speech, TV News & Viewing Habits) | [المواصفة](./english_10/semester_2/unit_07/lesson_01.lab.md) |
|
||||
| `english_10_semester_2_unit_07_lesson_02` | اللغة الإنجليزية | Unit 07 - Lesson 2: Reading (What is and isn't Art?) | [المواصفة](./english_10/semester_2/unit_07/lesson_02.lab.md) |
|
||||
| `english_10_semester_2_unit_07_lesson_03` | اللغة الإنجليزية | Unit 07 - Lesson 3: Grammar & Speaking (Reported Questions, Personal Experience) | [المواصفة](./english_10/semester_2/unit_07/lesson_03.lab.md) |
|
||||
| `english_10_semester_2_unit_07_lesson_04` | اللغة الإنجليزية | Unit 07 - Lesson 4: Writing (A Review of a Play) | [المواصفة](./english_10/semester_2/unit_07/lesson_04.lab.md) |
|
||||
| `english_10_semester_2_unit_08_lesson_01` | اللغة الإنجليزية | Unit 08 - Lesson 1: Grammar & Vocabulary (The Passive, Advertising) | [المواصفة](./english_10/semester_2/unit_08/lesson_01.lab.md) |
|
||||
| `english_10_semester_2_unit_08_lesson_02` | اللغة الإنجليزية | Unit 08 - Lesson 2: Listening & Vocabulary (Spending & Saving, Money) | [المواصفة](./english_10/semester_2/unit_08/lesson_02.lab.md) |
|
||||
| `english_10_semester_2_unit_08_lesson_03` | اللغة الإنجليزية | Unit 08 - Lesson 3: Reading (The Way We Pay - Trading & Banking) | [المواصفة](./english_10/semester_2/unit_08/lesson_03.lab.md) |
|
||||
| `english_10_semester_2_unit_08_lesson_04` | اللغة الإنجليزية | Unit 08 - Lesson 4: Speaking & Writing (Complaints, Opinion Essay) | [المواصفة](./english_10/semester_2/unit_08/lesson_04.lab.md) |
|
||||
| `english_10_semester_2_unit_08_life_skills` | اللغة الإنجليزية | Unit 08 - Life Skills: How to Be More Creative | [المواصفة](./english_10/semester_2/unit_08/life_skills.lab.md) |
|
||||
| `english_10_semester_2_unit_09_lesson_01` | اللغة الإنجليزية | Unit 09 - Lesson 1: Grammar & Vocabulary (Third Conditional, Water & Ocean) | [المواصفة](./english_10/semester_2/unit_09/lesson_01.lab.md) |
|
||||
| `english_10_semester_2_unit_09_lesson_02` | اللغة الإنجليزية | Unit 09 - Lesson 2: Listening & Vocabulary (Surviving an Earthquake, Natural Disasters) | [المواصفة](./english_10/semester_2/unit_09/lesson_02.lab.md) |
|
||||
| `english_10_semester_2_unit_09_lesson_03` | اللغة الإنجليزية | Unit 09 - Lesson 3: Grammar & Speaking (I wish / If only for regrets) | [المواصفة](./english_10/semester_2/unit_09/lesson_03.lab.md) |
|
||||
| `english_10_semester_2_unit_09_lesson_04` | اللغة الإنجليزية | Unit 09 - Lesson 4: Reading (Wildlife Documentary Changed My Life) | [المواصفة](./english_10/semester_2/unit_09/lesson_04.lab.md) |
|
||||
| `english_10_semester_2_unit_09_lesson_05` | اللغة الإنجليزية | Unit 09 - Lesson 5: Writing (An Article - Sustainable Homes) | [المواصفة](./english_10/semester_2/unit_09/lesson_05.lab.md) |
|
||||
| `english_10_semester_2_unit_10_culture_spot` | اللغة الإنجليزية | Unit 10 - Culture Spot & Literature Spot | [المواصفة](./english_10/semester_2/unit_10/culture_spot.lab.md) |
|
||||
| `english_10_semester_2_unit_10_lesson_01` | اللغة الإنجليزية | Unit 10 - Lesson 1: Grammar & Vocabulary (Modals for Speculating, Food Tastes) | [المواصفة](./english_10/semester_2/unit_10/lesson_01.lab.md) |
|
||||
| `english_10_semester_2_unit_10_lesson_02` | اللغة الإنجليزية | Unit 10 - Lesson 2: Reading (Dining with a Difference!) | [المواصفة](./english_10/semester_2/unit_10/lesson_02.lab.md) |
|
||||
| `english_10_semester_2_unit_10_lesson_03` | اللغة الإنجليزية | Unit 10 - Lesson 3: Speaking & Listening (Healthy Food, Comparing Photos) | [المواصفة](./english_10/semester_2/unit_10/lesson_03.lab.md) |
|
||||
| `english_10_semester_2_unit_10_lesson_04` | اللغة الإنجليزية | Unit 10 - Lesson 4: Writing (A Formal Letter) | [المواصفة](./english_10/semester_2/unit_10/lesson_04.lab.md) |
|
||||
| `financial_10_semester_1_unit_01_lesson_01` | الثقافة المالية | الدرس (1): المشروع وإدارته | [المواصفة](./financial_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `financial_10_semester_1_unit_01_lesson_02` | الثقافة المالية | الدرس (2): فريق المشروع | [المواصفة](./financial_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `financial_10_semester_1_unit_01_lesson_03` | الثقافة المالية | الدرس (3): مراحل إدارة المشروع وفريق العمل | [المواصفة](./financial_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `financial_10_semester_1_unit_01_lesson_04` | الثقافة المالية | الدرس (4): نماذج بدء المشروع والتخطيط الزمني | [المواصفة](./financial_10/semester_1/unit_01/lesson_04.lab.md) |
|
||||
| `financial_10_semester_1_unit_01_lesson_05` | الثقافة المالية | الدرس (5): مراقبة المشروع وتقييمه | [المواصفة](./financial_10/semester_1/unit_01/lesson_05.lab.md) |
|
||||
| `financial_10_semester_1_unit_01_unit_test_and_project` | الثقافة المالية | اختبار نهاية الوحدة ومشروع الوحدة: إدارة المشروعات | [المواصفة](./financial_10/semester_1/unit_01/unit_test_and_project.lab.md) |
|
||||
| `financial_10_semester_1_unit_02_lesson_01` | الثقافة المالية | الدرس الأول: مفهوم دراسة الجدوى الاقتصادية وأهميتها | [المواصفة](./financial_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `financial_10_semester_1_unit_02_lesson_02` | الثقافة المالية | الدرس الثاني: خطوات إعداد دراسة الجدوى والتحليل المالي | [المواصفة](./financial_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `financial_10_semester_1_unit_02_unit_review` | الثقافة المالية | اختبار ومراجعة الوحدة الثانية: الجدوى الاقتصادية للمشروعات | [المواصفة](./financial_10/semester_1/unit_02/unit_review.lab.md) |
|
||||
| `geography_10_semester_1_unit_01_lesson_01` | الجغرافيا | الدرس الأول: الغلاف الجوي | [المواصفة](./geography_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `geography_10_semester_1_unit_01_lesson_02` | الجغرافيا | الدرس الثاني: الغلاف الحيوي | [المواصفة](./geography_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `geography_10_semester_1_unit_01_lesson_03` | الجغرافيا | الدرس الثالث: التنوع الحيوي | [المواصفة](./geography_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `geography_10_semester_1_unit_01_unit_review` | الجغرافيا | مراجعة الوحدة الأولى: الجغرافيا الطبيعية | [المواصفة](./geography_10/semester_1/unit_01/unit_review.lab.md) |
|
||||
| `geography_10_semester_1_unit_02_lesson_01` | الجغرافيا | الدرس الأول: مقومات السياحة | [المواصفة](./geography_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `geography_10_semester_1_unit_02_lesson_02` | الجغرافيا | الدرس الثاني: الآثار الاقتصادية والاجتماعية للسياحة | [المواصفة](./geography_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `geography_10_semester_1_unit_02_lesson_03` | الجغرافيا | الدرس الثالث: النقل | [المواصفة](./geography_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `geography_10_semester_1_unit_02_unit_review` | الجغرافيا | مراجعة الوحدة الثانية: السياحة والنقل | [المواصفة](./geography_10/semester_1/unit_02/unit_review.lab.md) |
|
||||
| `geography_10_semester_1_unit_03_lesson_01` | الجغرافيا | الدرس الأول: الخرائط الموضوعية | [المواصفة](./geography_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `geography_10_semester_1_unit_03_lesson_02` | الجغرافيا | الدرس الثاني: نظم المعلومات الجغرافية | [المواصفة](./geography_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `geography_10_semester_1_unit_03_lesson_03` | الجغرافيا | الدرس الثالث: الأقمار الصناعية وتحليل الصور الفضائية | [المواصفة](./geography_10/semester_1/unit_03/lesson_03.lab.md) |
|
||||
| `geography_10_semester_1_unit_03_unit_review` | الجغرافيا | مراجعة الوحدة الثالثة: التقنيات الجغرافية | [المواصفة](./geography_10/semester_1/unit_03/unit_review.lab.md) |
|
||||
| `geography_10_semester_2_unit_04_lesson_01` | الجغرافيا | الدرس الأول: الموارد المائية | [المواصفة](./geography_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `geography_10_semester_2_unit_04_lesson_02` | الجغرافيا | الدرس الثاني: الموارد الزراعية | [المواصفة](./geography_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `geography_10_semester_2_unit_04_lesson_03` | الجغرافيا | الدرس الثالث: الموارد المعدنية | [المواصفة](./geography_10/semester_2/unit_04/lesson_03.lab.md) |
|
||||
| `geography_10_semester_2_unit_04_unit_review` | الجغرافيا | مراجعة الوحدة الرابعة: الموارد الطبيعية | [المواصفة](./geography_10/semester_2/unit_04/unit_review.lab.md) |
|
||||
| `geography_10_semester_2_unit_05_lesson_01` | الجغرافيا | الدرس الأول: التنمية المستدامة | [المواصفة](./geography_10/semester_2/unit_05/lesson_01.lab.md) |
|
||||
| `geography_10_semester_2_unit_05_lesson_02` | الجغرافيا | الدرس الثاني: مصادر الطاقة وأنواعها | [المواصفة](./geography_10/semester_2/unit_05/lesson_02.lab.md) |
|
||||
| `geography_10_semester_2_unit_05_lesson_03` | الجغرافيا | الدرس الثالث: الريادة والابتكار | [المواصفة](./geography_10/semester_2/unit_05/lesson_03.lab.md) |
|
||||
| `geography_10_semester_2_unit_05_unit_review` | الجغرافيا | مراجعة الوحدة الخامسة: التنمية المستدامة | [المواصفة](./geography_10/semester_2/unit_05/unit_review.lab.md) |
|
||||
| `geography_10_semester_2_unit_06_lesson_01` | الجغرافيا | الدرس الأول: أمريكا الشمالية والوسطى: الملامح الطبيعية والبشرية | [المواصفة](./geography_10/semester_2/unit_06/lesson_01.lab.md) |
|
||||
| `geography_10_semester_2_unit_06_lesson_02` | الجغرافيا | الدرس الثاني: أمريكا الجنوبية: الملامح الطبيعية والبشرية | [المواصفة](./geography_10/semester_2/unit_06/lesson_02.lab.md) |
|
||||
| `geography_10_semester_2_unit_06_lesson_03` | الجغرافيا | الدرس الثالث: أوقيانوسيا: الملامح الطبيعية والبشرية | [المواصفة](./geography_10/semester_2/unit_06/lesson_03.lab.md) |
|
||||
| `geography_10_semester_2_unit_06_unit_review` | الجغرافيا | مراجعة الوحدة السادسة: جغرافيا العالم الجديد | [المواصفة](./geography_10/semester_2/unit_06/unit_review.lab.md) |
|
||||
| `history_10_semester_1_unit_01_lesson_01` | التاريخ | الدرس الأول: الإمبراطورية الفارسية: النشأة والتطور | [المواصفة](./history_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `history_10_semester_1_unit_01_lesson_02` | التاريخ | الدرس الثاني: الدولة الساسانية (226م - 651م) | [المواصفة](./history_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `history_10_semester_1_unit_01_lesson_03` | التاريخ | الدرس الثالث: الحكم والمؤسسات في الدولة الساسانية | [المواصفة](./history_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `history_10_semester_1_unit_01_lesson_04` | التاريخ | الدرس الرابع: الحياة العامة في الدولة الساسانية | [المواصفة](./history_10/semester_1/unit_01/lesson_04.lab.md) |
|
||||
| `history_10_semester_1_unit_01_lesson_05` | التاريخ | الدرس الخامس: علاقات الدولة الساسانية الخارجية | [المواصفة](./history_10/semester_1/unit_01/lesson_05.lab.md) |
|
||||
| `history_10_semester_1_unit_01_lesson_06` | التاريخ | الدرس السادس: نهاية الدولة الساسانية | [المواصفة](./history_10/semester_1/unit_01/lesson_06.lab.md) |
|
||||
| `history_10_semester_1_unit_01_unit_review` | التاريخ | مراجعة الوحدة الأولى: الإمبراطورية الفارسية | [المواصفة](./history_10/semester_1/unit_01/unit_review.lab.md) |
|
||||
| `history_10_semester_1_unit_02_lesson_01` | التاريخ | الدرس الأول: نشأة الدولة العثمانية | [المواصفة](./history_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `history_10_semester_1_unit_02_lesson_02` | التاريخ | الدرس الثاني: فتح القسطنطينية (إسطنبول) | [المواصفة](./history_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `history_10_semester_1_unit_02_lesson_03` | التاريخ | الدرس الثالث: الحياة العامة في الدولة العثمانية | [المواصفة](./history_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `history_10_semester_1_unit_02_lesson_04` | التاريخ | الدرس الرابع: الوطن العربي في ظل الحكم العثماني | [المواصفة](./history_10/semester_1/unit_02/lesson_04.lab.md) |
|
||||
| `history_10_semester_1_unit_02_lesson_05` | التاريخ | الدرس الخامس: الأردن في ظل الحكم العثماني | [المواصفة](./history_10/semester_1/unit_02/lesson_05.lab.md) |
|
||||
| `history_10_semester_1_unit_02_lesson_06` | التاريخ | الدرس السادس: نهاية الدولة العثمانية | [المواصفة](./history_10/semester_1/unit_02/lesson_06.lab.md) |
|
||||
| `history_10_semester_1_unit_02_unit_review` | التاريخ | مراجعة الوحدة الثانية: الدولة العثمانية | [المواصفة](./history_10/semester_1/unit_02/unit_review.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_01` | التاريخ | الدرس الأول: الثورة الصناعية | [المواصفة](./history_10/semester_2/unit_03/lesson_01.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_02` | التاريخ | الدرس الثاني: إنجازات الثورة الصناعية | [المواصفة](./history_10/semester_2/unit_03/lesson_02.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_03` | التاريخ | الدرس الثالث: الثورة الأمريكية | [المواصفة](./history_10/semester_2/unit_03/lesson_03.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_04` | التاريخ | الدرس الرابع: مراحل الثورة الأمريكية | [المواصفة](./history_10/semester_2/unit_03/lesson_04.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_05` | التاريخ | الدرس الخامس: الثورة الفرنسية | [المواصفة](./history_10/semester_2/unit_03/lesson_05.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_06` | التاريخ | الدرس السادس: نتائج الثورة الفرنسية | [المواصفة](./history_10/semester_2/unit_03/lesson_06.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_07` | التاريخ | الدرس السابع: الثورة الروسية | [المواصفة](./history_10/semester_2/unit_03/lesson_07.lab.md) |
|
||||
| `history_10_semester_2_unit_03_lesson_08` | التاريخ | الدرس الثامن: العالم في القرن العشرين | [المواصفة](./history_10/semester_2/unit_03/lesson_08.lab.md) |
|
||||
| `history_10_semester_2_unit_03_unit_review` | التاريخ | مراجعة الوحدة الثالثة: ثورات غيرت العالم الحديث | [المواصفة](./history_10/semester_2/unit_03/unit_review.lab.md) |
|
||||
| `history_10_semester_2_unit_04_lesson_01` | التاريخ | الدرس الأول: محمد علي باشا (1769م - 1849م) | [المواصفة](./history_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `history_10_semester_2_unit_04_lesson_02` | التاريخ | الدرس الثاني: المهاتما غاندي (1869م - 1948م) | [المواصفة](./history_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `history_10_semester_2_unit_04_lesson_03` | التاريخ | الدرس الثالث: زيغريد هونكه (1913م - 1999م) | [المواصفة](./history_10/semester_2/unit_04/lesson_03.lab.md) |
|
||||
| `history_10_semester_2_unit_04_unit_review` | التاريخ | مراجعة الوحدة الرابعة: شخصيات من التاريخ | [المواصفة](./history_10/semester_2/unit_04/unit_review.lab.md) |
|
||||
| `islamic_10_semester_1_unit_01_lesson_01` | التربية الإسلامية | الدرس 1: واجب المسلم تجاه القرآن الكريم (تلاوته وتدبره والعمل به) | [المواصفة](./islamic_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_1_unit_01_lesson_02` | التربية الإسلامية | الدرس 2: الفقه الإسلامي: البيع وأحكامه في الإسلام | [المواصفة](./islamic_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_1_unit_01_lesson_03` | التربية الإسلامية | الدرس 3: معاملة النبي صلى الله عليه وسلم ليهود المدينة المنورة | [المواصفة](./islamic_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_1_unit_02_lesson_01` | التربية الإسلامية | الدرس الأول: الربا وأنواعه وأحكامه في الشريعة الإسلامية | [المواصفة](./islamic_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_1_unit_02_lesson_02` | التربية الإسلامية | الدرس الثاني: الصرف والقرض في الفقه الإسلامي | [المواصفة](./islamic_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_1_unit_02_lesson_03` | التربية الإسلامية | الدرس الثالث: الشركات المالية الحديثة وضوابطها الشرعية | [المواصفة](./islamic_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_1_unit_03_lesson_01` | التربية الإسلامية | الدرس الأول: وثيقة المدينة المنورة ودولة المواطنة والتعايش | [المواصفة](./islamic_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_1_unit_03_lesson_02` | التربية الإسلامية | الدرس الثاني: غزوة مؤتة والدروس القيادية والعسكرية | [المواصفة](./islamic_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_1_unit_03_lesson_03` | التربية الإسلامية | الدرس الثالث: منجزات الحضارة الإسلامية في العلوم والترجمة | [المواصفة](./islamic_10/semester_1/unit_03/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_1_unit_04_lesson_01` | التربية الإسلامية | الدرس الأول: خلق الحياء والعفة في السلوك الفردي والجماعي | [المواصفة](./islamic_10/semester_1/unit_04/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_1_unit_04_lesson_02` | التربية الإسلامية | الدرس الثاني: صلة الأرحام وحقوق الجوار والمسؤولية الأسرية | [المواصفة](./islamic_10/semester_1/unit_04/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_1_unit_04_lesson_03` | التربية الإسلامية | الدرس الثالث: ترشيد الاستهلاك وحماية البيئة في المنظور الإسلامي | [المواصفة](./islamic_10/semester_1/unit_04/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_2_unit_01_lesson_01` | التربية الإسلامية | الدرس 1: سورة آل عمران: الآيات الكريمة (189-195) | [المواصفة](./islamic_10/semester_2/unit_01/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_2_unit_01_lesson_02` | التربية الإسلامية | الدرس 2: صلح الحديبية | [المواصفة](./islamic_10/semester_2/unit_01/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_2_unit_01_lesson_03` | التربية الإسلامية | الدرس 3: الخرافة وموقف الإسلام منها | [المواصفة](./islamic_10/semester_2/unit_01/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_2_unit_01_lesson_04` | التربية الإسلامية | الدرس 4: الحكم الشرعي التكليفي وأقسامه | [المواصفة](./islamic_10/semester_2/unit_01/lesson_04.lab.md) |
|
||||
| `islamic_10_semester_2_unit_01_lesson_05` | التربية الإسلامية | الدرس 5: من أنواع الوقف الاختياري الجائز (الوقف الحسن) | [المواصفة](./islamic_10/semester_2/unit_01/lesson_05.lab.md) |
|
||||
| `islamic_10_semester_2_unit_01_lesson_06` | التربية الإسلامية | الدرس 6: الإجارة وأحكامها في الفقه الإسلامي | [المواصفة](./islamic_10/semester_2/unit_01/lesson_06.lab.md) |
|
||||
| `islamic_10_semester_2_unit_01_lesson_07` | التربية الإسلامية | الدرس 7: دور القوات المسلحة الأردنية في الدفاع عن فلسطين ومقدساتها | [المواصفة](./islamic_10/semester_2/unit_01/lesson_07.lab.md) |
|
||||
| `islamic_10_semester_2_unit_02_lesson_01` | التربية الإسلامية | الدرس 1: سورة الغاشية | [المواصفة](./islamic_10/semester_2/unit_02/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_2_unit_02_lesson_02` | التربية الإسلامية | الدرس 2: صحيح الإمام مسلم رحمه الله | [المواصفة](./islamic_10/semester_2/unit_02/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_2_unit_02_lesson_03` | التربية الإسلامية | الدرس 3: من خصائص الشريعة الإسلامية: المرونة | [المواصفة](./islamic_10/semester_2/unit_02/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_2_unit_02_lesson_04` | التربية الإسلامية | الدرس 4: الوقف الاختياري غير الجائز (الوقف القبيح) | [المواصفة](./islamic_10/semester_2/unit_02/lesson_04.lab.md) |
|
||||
| `islamic_10_semester_2_unit_02_lesson_05` | التربية الإسلامية | الدرس 5: الإعارة وأحكامها في الفقه الإسلامي | [المواصفة](./islamic_10/semester_2/unit_02/lesson_05.lab.md) |
|
||||
| `islamic_10_semester_2_unit_02_lesson_06` | التربية الإسلامية | الدرس 6: الإسلام والفن | [المواصفة](./islamic_10/semester_2/unit_02/lesson_06.lab.md) |
|
||||
| `islamic_10_semester_2_unit_03_lesson_01` | التربية الإسلامية | الدرس 1: حق المواطنة | [المواصفة](./islamic_10/semester_2/unit_03/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_2_unit_03_lesson_02` | التربية الإسلامية | الدرس 2: المحافظة على الموارد البيئية | [المواصفة](./islamic_10/semester_2/unit_03/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_2_unit_03_lesson_03` | التربية الإسلامية | الدرس 3: التبرع بالأعضاء | [المواصفة](./islamic_10/semester_2/unit_03/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_2_unit_03_lesson_04` | التربية الإسلامية | الدرس 4: الوقف وأحكامه في الفقه الإسلامي | [المواصفة](./islamic_10/semester_2/unit_03/lesson_04.lab.md) |
|
||||
| `islamic_10_semester_2_unit_03_lesson_05` | التربية الإسلامية | الدرس 5: الإيمان والعمل | [المواصفة](./islamic_10/semester_2/unit_03/lesson_05.lab.md) |
|
||||
| `islamic_10_semester_2_unit_03_lesson_06` | التربية الإسلامية | الدرس 6: تطبيقات على أحكام وقف التلاوة في القرآن الكريم (1) | [المواصفة](./islamic_10/semester_2/unit_03/lesson_06.lab.md) |
|
||||
| `islamic_10_semester_2_unit_03_lesson_07` | التربية الإسلامية | الدرس 7: من روائع حضارتنا: المنجزات العلمية | [المواصفة](./islamic_10/semester_2/unit_03/lesson_07.lab.md) |
|
||||
| `islamic_10_semester_2_unit_04_lesson_01` | التربية الإسلامية | الدرس 1: سورة النساء: الآيتان الكريمتان (58-59) | [المواصفة](./islamic_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `islamic_10_semester_2_unit_04_lesson_02` | التربية الإسلامية | الدرس 2: الحديث الشريف: سبعة يظلهم الله في ظله | [المواصفة](./islamic_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `islamic_10_semester_2_unit_04_lesson_03` | التربية الإسلامية | الدرس 3: اللباس والزينة في الإسلام | [المواصفة](./islamic_10/semester_2/unit_04/lesson_03.lab.md) |
|
||||
| `islamic_10_semester_2_unit_04_lesson_04` | التربية الإسلامية | الدرس 4: تطبيقات على أحكام وقف التلاوة في القرآن الكريم (2) | [المواصفة](./islamic_10/semester_2/unit_04/lesson_04.lab.md) |
|
||||
| `islamic_10_semester_2_unit_04_lesson_05` | التربية الإسلامية | الدرس 5: الوديعة وأحكامها في الفقه الإسلامي | [المواصفة](./islamic_10/semester_2/unit_04/lesson_05.lab.md) |
|
||||
| `islamic_10_semester_2_unit_04_lesson_06` | التربية الإسلامية | الدرس 6: الصحابي الجليل أبو عبيدة عامر بن الجراح رضي الله عنه | [المواصفة](./islamic_10/semester_2/unit_04/lesson_06.lab.md) |
|
||||
| `math_10_semester_1_unit_01_geogebra_lab` | الرياضيات | معملُ برمجيةِ جيوجبرا: حلُّ أنظمةِ المعادلاتِ بيانياً (Solving Systems of Equations Graphically) | [المواصفة](./math_10/semester_1/unit_01/geogebra_lab.lab.md) |
|
||||
| `math_10_semester_1_unit_01_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: أنظمة المعادلات في حياتنا | [المواصفة](./math_10/semester_1/unit_01/intro_and_project.lab.md) |
|
||||
| `math_10_semester_1_unit_01_lesson_01` | الرياضيات | الدرس 1: حلُّ معادلاتٍ خاصّةٍ (Solving Special Equations) | [المواصفة](./math_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `math_10_semester_1_unit_01_lesson_02` | الرياضيات | الدرس 2: حلُّ نظامٍ مُكوَّنٍ من معادلةٍ خطيّةٍ ومعادلةٍ تربيعيّةٍ (Solving a System of Linear and Quadratic Equations) | [المواصفة](./math_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `math_10_semester_1_unit_01_lesson_03` | الرياضيات | الدرس 3: حلُّ نظامٍ مُكوَّنٍ من معادلتيْنِ تربيعيّتيْنِ (Solving a System of Two Quadratic Equations) | [المواصفة](./math_10/semester_1/unit_01/lesson_03.lab.md) |
|
||||
| `math_10_semester_1_unit_01_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ الأولى: المعادلات | [المواصفة](./math_10/semester_1/unit_01/unit_exam.lab.md) |
|
||||
| `math_10_semester_1_unit_02_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: استعمالات علمية لخصائص الدائرة | [المواصفة](./math_10/semester_1/unit_02/intro_and_project.lab.md) |
|
||||
| `math_10_semester_1_unit_02_lesson_01` | الرياضيات | الدرس 1: أوتارُ الدائرةِ، وأقطارُها، ومماساتُها (Chords, Diameters and Tangents of a Circle) | [المواصفة](./math_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `math_10_semester_1_unit_02_lesson_02` | الرياضيات | الدرس 2: الأقواسُ والقطاعاتُ الدائريّةُ (Arcs and Sectors) | [المواصفة](./math_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `math_10_semester_1_unit_02_lesson_03` | الرياضيات | الدرس 3: الزوايا في الدائرة (Angles in a Circle) | [المواصفة](./math_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `math_10_semester_1_unit_02_lesson_04` | الرياضيات | الدرس 4: معادلةُ الدائرةِ (Equation of a Circle) | [المواصفة](./math_10/semester_1/unit_02/lesson_04.lab.md) |
|
||||
| `math_10_semester_1_unit_02_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ الثانية: الدائرة | [المواصفة](./math_10/semester_1/unit_02/unit_exam.lab.md) |
|
||||
| `math_10_semester_1_unit_03_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: إنشاء نظام إحداثي جديد | [المواصفة](./math_10/semester_1/unit_03/intro_and_project.lab.md) |
|
||||
| `math_10_semester_1_unit_03_lesson_01` | الرياضيات | الدرس 1: النِّسَبُ المُثلَّثيَّةُ (Trigonometric Ratios) | [المواصفة](./math_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `math_10_semester_1_unit_03_lesson_02` | الرياضيات | الدرس 2: النِّسَبُ المُثلَّثيَّةُ للزوايا ضِمْنَ الدَّوْرَةِ الواحدةِ (Trigonometric Ratios within One Revolution) | [المواصفة](./math_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `math_10_semester_1_unit_03_lesson_03` | الرياضيات | الدرس 3: تمثيلُ الاقتراناتِ المُثلَّثيَّةِ (Graphing Trigonometric Functions) | [المواصفة](./math_10/semester_1/unit_03/lesson_03.lab.md) |
|
||||
| `math_10_semester_1_unit_03_lesson_04` | الرياضيات | الدرس 4: حَلُّ المُعادلاتِ المُثلَّثيَّةِ (Solving Trigonometric Equations) | [المواصفة](./math_10/semester_1/unit_03/lesson_04.lab.md) |
|
||||
| `math_10_semester_1_unit_03_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ الثالثة: حساب المثلثات | [المواصفة](./math_10/semester_1/unit_03/unit_exam.lab.md) |
|
||||
| `math_10_semester_1_unit_04_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: صنع كلينومتر واستعماله | [المواصفة](./math_10/semester_1/unit_04/intro_and_project.lab.md) |
|
||||
| `math_10_semester_1_unit_04_lesson_01` | الرياضيات | الدرس 1: الاتجاهُ مِنَ الشَّمالِ (Bearings) | [المواصفة](./math_10/semester_1/unit_04/lesson_01.lab.md) |
|
||||
| `math_10_semester_1_unit_04_lesson_02` | الرياضيات | الدرس 2: قانونُ الجُيوبِ (The Law of Sines) | [المواصفة](./math_10/semester_1/unit_04/lesson_02.lab.md) |
|
||||
| `math_10_semester_1_unit_04_lesson_03` | الرياضيات | الدرس 3: قانونُ جُيوبِ التَّمامِ (The Law of Cosines) | [المواصفة](./math_10/semester_1/unit_04/lesson_03.lab.md) |
|
||||
| `math_10_semester_1_unit_04_lesson_04` | الرياضيات | الدرس 4: استعمالُ جَيْبِ الزاويةِ لإيجادِ مِساحةِ المُثلَّثِ (Area of a Triangle Using Sine) | [المواصفة](./math_10/semester_1/unit_04/lesson_04.lab.md) |
|
||||
| `math_10_semester_1_unit_04_lesson_05` | الرياضيات | الدرس 5: حَلُّ مسائلَ ثُلاثيَّةِ الأبعادِ (Solving Three-Dimensional Problems) | [المواصفة](./math_10/semester_1/unit_04/lesson_05.lab.md) |
|
||||
| `math_10_semester_1_unit_04_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ الرابعة: تطبيقات المثلثات | [المواصفة](./math_10/semester_1/unit_04/unit_exam.lab.md) |
|
||||
| `math_10_semester_2_unit_05_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: نمذجة علاقات باستعمال كثيرات الحدود | [المواصفة](./math_10/semester_2/unit_05/intro_and_project.lab.md) |
|
||||
| `math_10_semester_2_unit_05_lesson_01` | الرياضيات | الدرس 1: اقتراناتُ كثيراتِ الحدودِ (Polynomial Functions) | [المواصفة](./math_10/semester_2/unit_05/lesson_01.lab.md) |
|
||||
| `math_10_semester_2_unit_05_lesson_02` | الرياضيات | الدرس 2: قسمةُ كثيراتِ الحدودِ والاقتراناتُ النسبيّةُ (Dividing Polynomials and Rational Functions) | [المواصفة](./math_10/semester_2/unit_05/lesson_02.lab.md) |
|
||||
| `math_10_semester_2_unit_05_lesson_03` | الرياضيات | الدرس 3: تركيبُ الاقتراناتِ (Composition of Functions) | [المواصفة](./math_10/semester_2/unit_05/lesson_03.lab.md) |
|
||||
| `math_10_semester_2_unit_05_lesson_04` | الرياضيات | الدرس 4: الاقترانُ العكسيُّ (Inverse Functions) | [المواصفة](./math_10/semester_2/unit_05/lesson_04.lab.md) |
|
||||
| `math_10_semester_2_unit_05_lesson_05` | الرياضيات | الدرس 5: المتتالياتُ (Sequences) | [المواصفة](./math_10/semester_2/unit_05/lesson_05.lab.md) |
|
||||
| `math_10_semester_2_unit_05_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ الخامسة: الاقترانات | [المواصفة](./math_10/semester_2/unit_05/unit_exam.lab.md) |
|
||||
| `math_10_semester_2_unit_06_geogebra_lab` | الرياضيات | معملُ برمجية جيوجبرا: استكشافُ ميلِ مماس المنحنى (Exploring Slope of Tangent with GeoGebra) | [المواصفة](./math_10/semester_2/unit_06/geogebra_lab.lab.md) |
|
||||
| `math_10_semester_2_unit_06_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: عمل صندوق حجمه أكبر ما يمكن | [المواصفة](./math_10/semester_2/unit_06/intro_and_project.lab.md) |
|
||||
| `math_10_semester_2_unit_06_lesson_01` | الرياضيات | الدرس 1: تقديرُ ميلِ المنحنى (Estimating the Slope of a Curve) | [المواصفة](./math_10/semester_2/unit_06/lesson_01.lab.md) |
|
||||
| `math_10_semester_2_unit_06_lesson_02` | الرياضيات | الدرس 2: الاشتقاقُ (Differentiation) | [المواصفة](./math_10/semester_2/unit_06/lesson_02.lab.md) |
|
||||
| `math_10_semester_2_unit_06_lesson_03` | الرياضيات | الدرس 3: القِيَمُ العُظْمى والقِيَمُ الصُّغْرى (Maximum and Minimum Values) | [المواصفة](./math_10/semester_2/unit_06/lesson_03.lab.md) |
|
||||
| `math_10_semester_2_unit_06_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ السادسة: المشتقات | [المواصفة](./math_10/semester_2/unit_06/unit_exam.lab.md) |
|
||||
| `math_10_semester_2_unit_07_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: المتجهات في الجغرافيا | [المواصفة](./math_10/semester_2/unit_07/intro_and_project.lab.md) |
|
||||
| `math_10_semester_2_unit_07_lesson_01` | الرياضيات | الدرس 1: المُتَّجَهاتُ في المستوى الإحداثيِّ (Vectors in the Coordinate Plane) | [المواصفة](./math_10/semester_2/unit_07/lesson_01.lab.md) |
|
||||
| `math_10_semester_2_unit_07_lesson_02` | الرياضيات | الدرس 2: جمعُ المتجهاتِ وطرحُها (Adding and Subtracting Vectors) | [المواصفة](./math_10/semester_2/unit_07/lesson_02.lab.md) |
|
||||
| `math_10_semester_2_unit_07_lesson_03` | الرياضيات | الدرس 3: الضربُ القياسيُّ (The Scalar Product) | [المواصفة](./math_10/semester_2/unit_07/lesson_03.lab.md) |
|
||||
| `math_10_semester_2_unit_07_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ السابعة: المتجهات | [المواصفة](./math_10/semester_2/unit_07/unit_exam.lab.md) |
|
||||
| `math_10_semester_2_unit_08_geogebra_lab` | الرياضيات | معملُ برمجية جيوجبرا: رسمُ المستقيمِ الأفضلِ مطابقةً (Line of Best Fit with GeoGebra) | [المواصفة](./math_10/semester_2/unit_08/geogebra_lab.lab.md) |
|
||||
| `math_10_semester_2_unit_08_intro_and_project` | الرياضيات | مقدمة ومشروع الوحدة: مستوى الأقارب التعليمي | [المواصفة](./math_10/semester_2/unit_08/intro_and_project.lab.md) |
|
||||
| `math_10_semester_2_unit_08_lesson_01` | الرياضيات | الدرس 1: أشكالُ الانتشارِ (Scatter Plots) | [المواصفة](./math_10/semester_2/unit_08/lesson_01.lab.md) |
|
||||
| `math_10_semester_2_unit_08_lesson_02` | الرياضيات | الدرس 2: المُنحنى التَّكراريُّ التَّراكميُّ (Cumulative Frequency Curve) | [المواصفة](./math_10/semester_2/unit_08/lesson_02.lab.md) |
|
||||
| `math_10_semester_2_unit_08_lesson_03` | الرياضيات | الدرس 3: مقاييسُ التَّشتُّتِ للجداولِ التَّكراريَّةِ ذاتِ الفئاتِ (Measures of Dispersion for Grouped Frequency Tables) | [المواصفة](./math_10/semester_2/unit_08/lesson_03.lab.md) |
|
||||
| `math_10_semester_2_unit_08_lesson_04` | الرياضيات | الدرس 4: احتمالاتُ الحوادثِ المُتنافيةِ (Probability of Mutually Exclusive Events) | [المواصفة](./math_10/semester_2/unit_08/lesson_04.lab.md) |
|
||||
| `math_10_semester_2_unit_08_lesson_05` | الرياضيات | الدرس 5: احتمالاتُ الحوادثِ المُستقلَّةِ والحوادثِ غيرِ المُستقلَّةِ (Probability of Independent and Dependent Events) | [المواصفة](./math_10/semester_2/unit_08/lesson_05.lab.md) |
|
||||
| `math_10_semester_2_unit_08_unit_exam` | الرياضيات | اختبارُ نهايةِ الوحدةِ الثامنة: الإحصاء والاحتمالات | [المواصفة](./math_10/semester_2/unit_08/unit_exam.lab.md) |
|
||||
| `physics_10_semester_1_unit_01_lesson_01` | الفيزياء | الدرس الأول: المتجهات وخصائصها وتمثيلها بيانياً | [المواصفة](./physics_10/semester_1/unit_01/lesson_01.lab.md) |
|
||||
| `physics_10_semester_1_unit_01_lesson_02` | الفيزياء | الدرس الثاني: جمع المتجهات وطرحها وتحليل المتجهات | [المواصفة](./physics_10/semester_1/unit_01/lesson_02.lab.md) |
|
||||
| `physics_10_semester_1_unit_01_unit_review` | الفيزياء | مراجعة واختبار الوحدة الأولى: المتجهات | [المواصفة](./physics_10/semester_1/unit_01/unit_review.lab.md) |
|
||||
| `physics_10_semester_1_unit_02_lesson_01` | الفيزياء | الدرس الأول: الحركة في بعد واحد والسرعة القياسية والمتجهة | [المواصفة](./physics_10/semester_1/unit_02/lesson_01.lab.md) |
|
||||
| `physics_10_semester_1_unit_02_lesson_02` | الفيزياء | الدرس الثاني: الحركة في بعدين وحركة المقذوفات | [المواصفة](./physics_10/semester_1/unit_02/lesson_02.lab.md) |
|
||||
| `physics_10_semester_1_unit_02_lesson_03` | الفيزياء | الدرس الثالث: التسارع الثابت ومعادلات الحركة بتسارع ثابت | [المواصفة](./physics_10/semester_1/unit_02/lesson_03.lab.md) |
|
||||
| `physics_10_semester_1_unit_02_unit_review` | الفيزياء | مراجعة واختبار الوحدة الثانية: الحركة | [المواصفة](./physics_10/semester_1/unit_02/unit_review.lab.md) |
|
||||
| `physics_10_semester_1_unit_03_lesson_01` | الفيزياء | الدرس الأول: مفهوم القوة والقانون الأول لنيوتن والقصور الذاتي | [المواصفة](./physics_10/semester_1/unit_03/lesson_01.lab.md) |
|
||||
| `physics_10_semester_1_unit_03_lesson_02` | الفيزياء | الدرس الثاني: القانون الثاني لنيوتن والقوة المحصلة والتسارع | [المواصفة](./physics_10/semester_1/unit_03/lesson_02.lab.md) |
|
||||
| `physics_10_semester_2_unit_04_lesson_01` | الفيزياء | الدرس الأول: الوزن والقوة العمودية | [المواصفة](./physics_10/semester_2/unit_04/lesson_01.lab.md) |
|
||||
| `physics_10_semester_2_unit_04_lesson_02` | الفيزياء | الدرس الثاني: تطبيقات على القوى (قوة الشد، الاحتكاك، والمستوى المائل) | [المواصفة](./physics_10/semester_2/unit_04/lesson_02.lab.md) |
|
||||
| `physics_10_semester_2_unit_04_lesson_03` | الفيزياء | الدرس الثالث: القوة المركزية والحركة الدائرية المنتظمة | [المواصفة](./physics_10/semester_2/unit_04/lesson_03.lab.md) |
|
||||
| `physics_10_semester_2_unit_04_unit_review` | الفيزياء | مراجعة واختبار الوحدة الرابعة: تطبيقات على قوانين نيوتن | [المواصفة](./physics_10/semester_2/unit_04/unit_review.lab.md) |
|
||||
| `physics_10_semester_2_unit_05_lesson_01` | الفيزياء | الدرس الأول: الموائع الساكنة (ضغط المائع وقاعدة باسكال وقاعدة أرخميدس) | [المواصفة](./physics_10/semester_2/unit_05/lesson_01.lab.md) |
|
||||
| `physics_10_semester_2_unit_05_lesson_02` | الفيزياء | الدرس الثاني: الموائع المتحركة (معادلة الاستمرارية ومعادلة برنولي وتطبيقاتها) | [المواصفة](./physics_10/semester_2/unit_05/lesson_02.lab.md) |
|
||||
| `physics_10_semester_2_unit_05_unit_review` | الفيزياء | مراجعة واختبار الوحدة الخامسة: الموائع | [المواصفة](./physics_10/semester_2/unit_05/unit_review.lab.md) |
|
||||
| `physics_10_semester_2_unit_06_lesson_01` | الفيزياء | الدرس الأول: الموجات وصفاتها وأنواعها (الموجات المستعرضة والطولية) | [المواصفة](./physics_10/semester_2/unit_06/lesson_01.lab.md) |
|
||||
| `physics_10_semester_2_unit_06_lesson_02` | الفيزياء | الدرس الثاني: خصائص الموجات (الانعكاس، الانكسار، التداخل، والحيود) | [المواصفة](./physics_10/semester_2/unit_06/lesson_02.lab.md) |
|
||||
| `physics_10_semester_2_unit_06_unit_review` | الفيزياء | مراجعة واختبار الوحدة السادسة: الحركة الموجية | [المواصفة](./physics_10/semester_2/unit_06/unit_review.lab.md) |
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_01_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_01/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ (قصة كعب بن مالك رضي الله عنه)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_01_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_01/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 8, 9
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ (قصة كعب بن مالك رضي الله عنه)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_01_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_01/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (فن الاعتذار وقيم التسامح)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_01_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_01/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 10, 11, 12
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (فن الاعتذار وقيم التسامح)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_01_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_01/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (سينية البحتري في الاعتذار)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_01_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_01/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 13, 14, 15, 16, 17, 18, 19, 20, 21
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (سينية البحتري في الاعتذار)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_01_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_01/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (كتابة رسالة اعتذار وتسامح)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_01_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_01/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 22, 23, 24
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (كتابة رسالة اعتذار وتسامح)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_01_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_01/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): أسلوبُ الشَّرطِ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_01_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_01/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 25, 26, 27, 28
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): أسلوبُ الشَّرطِ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_01_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_01/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): الأسلوبُ الخَبَريُّ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_01_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_01/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 29, 30, 31
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): الأسلوبُ الخَبَريُّ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_02_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_02
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_02/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_02_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_02/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 34, 35, 36, 37
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_02_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_02
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_02/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (العرض التقديمي)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_02_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_02/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 38
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (العرض التقديمي)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_02_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_02
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_02/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (إلى الصامدين غرب النهر)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_02_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_02/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 39, 40, 41, 42, 43, 44, 45
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (إلى الصامدين غرب النهر)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_02_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_02
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_02/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (تحليل النص الشعري)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_02_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_02/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 46, 47, 48, 49
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (تحليل النص الشعري)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_02_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_02
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_02/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): أسلوبُ النِّداءِ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_02_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_02/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 50, 51, 52, 53, 54, 55
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): أسلوبُ النِّداءِ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_02_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_02
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_02/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): الأسلوبُ الإنشائيُّ (الإنشاء الطلبي)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_02_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_02/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 56, 57, 58, 59
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): الأسلوبُ الإنشائيُّ (الإنشاء الطلبي)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_03_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_03
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_03/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_03_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_03/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 62, 63, 64
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_03_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_03
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_03/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (قراءة الصورة)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_03_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_03/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 65, 66
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (قراءة الصورة)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_03_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_03
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_03/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (اللغة الأم)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_03_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_03/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 67, 68, 69, 70, 71, 72, 73, 74
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (اللغة الأم)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_03_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_03
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_03/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (تحليل لوحة فنية)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_03_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_03/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 75, 76, 77, 78
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (تحليل لوحة فنية)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_03_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_03
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_03/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): معاني الأفعال المزيدة
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_03_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_03/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 79, 80, 81, 82, 83, 84, 85
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): معاني الأفعال المزيدة
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_03_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_03
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_03/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): الأسلوب الإنشائي غير الطلبي
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_03_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_03/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 86, 87, 88, 89
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): الأسلوب الإنشائي غير الطلبي
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_04_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_04
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_04/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_04_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_04/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 92, 93, 94
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_04_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_04
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_04/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (كيف أقدم شخصية أدبية؟)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_04_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_04/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 95, 96
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (كيف أقدم شخصية أدبية؟)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_04_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_04
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_04/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (شغف القراءة وحكايات أخرى)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_04_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_04/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 97, 98, 99, 100, 101, 102, 103
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (شغف القراءة وحكايات أخرى)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_04_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_04
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_04/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (صفحة أولى من سيرتي الذاتية)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_04_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_04/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 104, 105, 106
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (صفحة أولى من سيرتي الذاتية)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_04_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_04
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_04/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): مصادر الأفعال الثلاثية
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_04_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_04/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 107, 108, 109, 110, 111, 112
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): مصادر الأفعال الثلاثية
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_04_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_04
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_04/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_04_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_04/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 113, 114, 115
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_05_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_05
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_05/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_05_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_05/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 118, 119, 120, 121
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_05_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_05
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_05/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (قراءة المشاعر)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_05_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_05/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 122, 123, 124
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (قراءة المشاعر)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_05_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_05
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_05/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (بم التعلل لا أهل ولا وطن)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_05_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_05/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 125, 126, 127, 128, 129, 130, 131
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (بم التعلل لا أهل ولا وطن)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_05_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_05
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_05/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (نص إخباري عن مناسبة أممية)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_05_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_05/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 132, 133, 134
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (نص إخباري عن مناسبة أممية)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_05_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_05
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_05/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): مصادر الأفعال غير الثلاثية
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_05_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_05/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 135, 136, 137, 138, 139, 140
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): مصادر الأفعال غير الثلاثية
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_1_unit_05_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_05
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_1/unit_05/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_1_unit_05_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_1/unit_05/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 141, 142
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_06_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_06
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_06/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_06_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_06/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 10, 11, 12
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_06_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_06
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_06/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (المناقشة الجماعية الحرة)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_06_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_06/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 13, 14, 15, 16, 17
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (المناقشة الجماعية الحرة)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_06_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_06
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_06/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (سينية أحمد شوقي في الحنين للوطن)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_06_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_06/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 18, 19, 20
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (سينية أحمد شوقي في الحنين للوطن)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_06_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_06
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_06/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (مقال تحليلي عن تجربة شعورية)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_06_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_06/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 21, 22, 23, 24
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (مقال تحليلي عن تجربة شعورية)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_06_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_06
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_06/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): الممنوع من الصرف
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_06_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_06/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 25, 26, 27
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): الممنوع من الصرف
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_06_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_06
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_06/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): نوعا التشبيه: المؤكد المفصل والمؤكد المجمل
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_06_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_06/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 28, 29
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): نوعا التشبيه: المؤكد المفصل والمؤكد المجمل
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_07_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_07
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_07/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_07_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_07/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 30, 31, 32
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_07_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_07
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_07/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (فن المناظرة وأدوار المتحدثين)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_07_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_07/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 33, 34, 35
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (فن المناظرة وأدوار المتحدثين)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_07_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_07
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_07/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (عصر المعلومات بعد الإنترنت: قضية إشكالية)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_07_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_07/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 36, 37, 38, 39, 40, 41
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (عصر المعلومات بعد الإنترنت: قضية إشكالية)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_07_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_07
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_07/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (النص الجدلي)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_07_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_07/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 42, 43, 44, 45
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (النص الجدلي)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_07_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_07
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_07/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): تمييز الذات
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_07_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_07/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 46, 47, 48, 49, 50
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): تمييز الذات
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_07_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_07
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_07/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): صيغة المبالغة والصفة المشبهة
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_07_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_07/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 51, 52, 53, 54, 55
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): صيغة المبالغة والصفة المشبهة
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_08_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_08
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_08/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_08_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_08/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 56, 57, 58
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_08_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_08
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_08/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (إدارة الندوة)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_08_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_08/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 59, 60, 61
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (إدارة الندوة)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_08_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_08
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_08/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (مقطوعات من الغزل العذري)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_08_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_08/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 62, 63, 64, 65, 66, 67, 68, 69
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (مقطوعات من الغزل العذري)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_08_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_08
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_08/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (إعداد مخطط مبادرة تطوعية)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_08_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_08/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 70, 71, 72, 73
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (إعداد مخطط مبادرة تطوعية)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_08_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_08
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_08/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): تثنية الاسم المقصور والمنقوص والممدود وجمعه
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_08_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_08/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 74, 75, 76, 77
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): تثنية الاسم المقصور والمنقوص والممدود وجمعه
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_08_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_08
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_08/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها (بحر الهزج)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_08_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_08/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 78, 79, 80, 81, 82, 83
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها (بحر الهزج)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_09_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_09
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_09/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_09_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_09/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 84, 85, 86, 87
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: أستمعُ بانتباهٍ وتركيزٍ
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_09_lesson_02
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_09
|
||||
lesson_key: lesson_02
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_09/lesson_02.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثاني: أتحدثُ بطالقةٍ (العرض الشفوي لقصة نجاح)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_09_lesson_02`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_09/lesson_02.md`
|
||||
- صفحات المصدر المسجلة: 88, 89, 90
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثاني: أتحدثُ بطالقةٍ (العرض الشفوي لقصة نجاح)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_09_lesson_03
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_09
|
||||
lesson_key: lesson_03
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_09/lesson_03.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (المفكر العربي إدوارد سعيد)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_09_lesson_03`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_09/lesson_03.md`
|
||||
- صفحات المصدر المسجلة: 91, 92, 93, 94, 95, 96
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الثالث: أقرأُ بطالقةٍ وفهمٍ (المفكر العربي إدوارد سعيد)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_09_lesson_04
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_09
|
||||
lesson_key: lesson_04
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_09/lesson_04.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الرابع: أكتبُ محتوى (تقرير علمي عن شخصية)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_09_lesson_04`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_09/lesson_04.md`
|
||||
- صفحات المصدر المسجلة: 97, 98, 99, 100
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الرابع: أكتبُ محتوى (تقرير علمي عن شخصية)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_09_lesson_05
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_09
|
||||
lesson_key: lesson_05
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_09/lesson_05.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الخامس: أبني لغتي (1): الأفعال المتعدية إلى مفعولين
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_09_lesson_05`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_09/lesson_05.md`
|
||||
- صفحات المصدر المسجلة: 101, 102, 103, 104, 105
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الخامس: أبني لغتي (1): الأفعال المتعدية إلى مفعولين
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: arabic_10_semester_2_unit_09_lesson_06
|
||||
grade_key: grade_10
|
||||
subject_key: arabic_10
|
||||
semester_key: semester_2
|
||||
unit_key: unit_09
|
||||
lesson_key: lesson_06
|
||||
source_markdown: grade_10/arabic_10/semester_2/unit_09/lesson_06.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها (بحر المتقارب)
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: اللغة العربية
|
||||
- هوية الدرس: `arabic_10_semester_2_unit_09_lesson_06`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/arabic_10/semester_2/unit_09/lesson_06.md`
|
||||
- صفحات المصدر المسجلة: 106, 107, 108, 109, 110
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس السادس: أبني لغتي (2): موسيقا لغتي وإيقاعها (بحر المتقارب)
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مسرح نصي تفاعلي يفكك البنية والمعنى والإيقاع بصرياً.
|
||||
- **حلقة اللعب:** رتّب عناصر النص أو اللغة واستدل على العلاقة.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: biology_10_semester_1_unit_01_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: biology_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/biology_10/semester_1/unit_01/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: النقل عبر الغشاء البلازمي والخاصية الأسموزية والانتشار
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: العلوم الحياتية
|
||||
- هوية الدرس: `biology_10_semester_1_unit_01_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/biology_10/semester_1/unit_01/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: النقل عبر الغشاء البلازمي والخاصية الأسموزية والانتشار
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مجهر أو نظام حيوي متحرك يوضح البنية والعلاقة والعملية.
|
||||
- **حلقة اللعب:** صنّف أو شخّص أو وازن النظام الحيوي.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: biology_10_semester_1_unit_01_unit_review
|
||||
grade_key: grade_10
|
||||
subject_key: biology_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_01
|
||||
lesson_key: unit_review
|
||||
source_markdown: grade_10/biology_10/semester_1/unit_01/unit_review.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — مراجعة واختبار الوحدة الأولى: أنشطة الخلية
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: العلوم الحياتية
|
||||
- هوية الدرس: `biology_10_semester_1_unit_01_unit_review`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/biology_10/semester_1/unit_01/unit_review.md`
|
||||
- صفحات المصدر المسجلة: 18, 19
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- مراجعة واختبار الوحدة الأولى: أنشطة الخلية
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مجهر أو نظام حيوي متحرك يوضح البنية والعلاقة والعملية.
|
||||
- **حلقة اللعب:** صنّف أو شخّص أو وازن النظام الحيوي.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
spec_type: curriculum_virtual_lab
|
||||
spec_version: 1.0.0
|
||||
status: draft_requires_academic_review
|
||||
curriculum_version: jordan-grade10-2026-source-review
|
||||
curriculum_lesson_id: biology_10_semester_1_unit_02_lesson_01
|
||||
grade_key: grade_10
|
||||
subject_key: biology_10
|
||||
semester_key: semester_1
|
||||
unit_key: unit_02
|
||||
lesson_key: lesson_01
|
||||
source_markdown: grade_10/biology_10/semester_1/unit_02/lesson_01.md
|
||||
source_extraction_review_status: needs_human_review
|
||||
---
|
||||
|
||||
# مواصفة المختبر التفاعلي — الدرس الأول: المادة الوراثية وتضاعف الحمض النووي DNA وبناء البروتين
|
||||
|
||||
> **حالة الملف:** مسودة هندسية مشتقة من ملف القبول المنهجي. لا تُعرض للطالب ولا تُسجّل كمختبر منشور قبل مراجعة المحتوى، وربطها بحزمة نشر خادمية معتمدة وإصدار أصل واضح.
|
||||
|
||||
## 1. بطاقة الارتباط المنهجي
|
||||
|
||||
- المبحث: العلوم الحياتية
|
||||
- هوية الدرس: `biology_10_semester_1_unit_02_lesson_01`
|
||||
- المصدر الوحيد لهذه المسودة: `grade_10/biology_10/semester_1/unit_02/lesson_01.md`
|
||||
- صفحات المصدر المسجلة: 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33
|
||||
- حالة مراجعة الاستخراج: `needs_human_review`
|
||||
- يمنع الربط بعنوان الدرس أو `lesson_01` وحده؛ الربط التشغيلي يكون بهوية الدرس وإصدار الحزمة المنشورة.
|
||||
|
||||
## 2. ما الذي يجب أن يفهمه الطالب؟
|
||||
|
||||
### النتاجات المستخرجة
|
||||
|
||||
- يحددها المراجع الأكاديمي من المصدر قبل التنفيذ؛ لا تكفي صياغة عنوان الدرس.
|
||||
|
||||
### محاور الدرس التي يجب تمثيلها
|
||||
|
||||
- الدرس الأول: المادة الوراثية وتضاعف الحمض النووي DNA وبناء البروتين
|
||||
|
||||
إذا كانت النتاجات أعلاه عامة أو ناقصة، يستخرج المراجع الأكاديمي نتاجات قابلة للقياس من الصفحات المشار إليها قبل البرمجة. لا يخترع المطور قانوناً أو تعريفاً أو ترجمة أو مثالاً غير مثبت في المصدر.
|
||||
|
||||
## 3. فكرة اللعبة التعليمية
|
||||
|
||||
- **الاستعارة البصرية:** مجهر أو نظام حيوي متحرك يوضح البنية والعلاقة والعملية.
|
||||
- **حلقة اللعب:** صنّف أو شخّص أو وازن النظام الحيوي.
|
||||
- **المهمة الأولى — اكتشف:** يبدأ الطالب بمشهد واضح وتعليمة واحدة، ويتنبأ بما سيحدث قبل لمس التحكم.
|
||||
- **المهمة الثانية — جرّب:** يغيّر متغيراً واحداً فقط، ويرى أثره فوراً في الرسم والقراءة والتفسير المختصر.
|
||||
- **المهمة الثالثة — ابنِ:** يرتب العناصر أو يضبط القيم للوصول إلى حالة هدف مأخوذة من مفهوم الدرس.
|
||||
- **المهمة الرابعة — فسّر:** يختار تفسيراً للدليل المرئي، لا تعريفاً محفوظاً.
|
||||
- **المهمة الخامسة — انقل الفهم:** يواجه حالة جديدة بالقانون أو العلاقة نفسها مع قيم/سياق مختلفين.
|
||||
|
||||
لا توجد نقاط أو كؤوس لمجرد النقر. المكافأة البصرية تُمنح عند إظهار دليل فهم، ولا تعني إتقاناً معتمداً؛ الإتقان يُحتسب على الخادم وفق سياسة منفصلة.
|
||||
|
||||
## 4. المشهد المتحرك
|
||||
|
||||
- يرسم بالكامل عبر Flutter `CustomPainter` دون صور PNG/SVG/WebView.
|
||||
- يحول كل محور في قسم «محاور الدرس» إلى طبقة مرئية قابلة للتشغيل والإيقاف.
|
||||
- يعرض السبب والنتيجة في اللحظة نفسها: المتغير المتحكم، أثره في المشهد، والقراءة الرقمية بوحدتها إن وجدت.
|
||||
- تبقى مساحة التجربة ظاهرة أثناء تعديل القيم. على العرض الواسع: كانفاس بمرونة 2 ولوحة تحكم 1 بعرض 320–360px. على الهاتف: كانفاس 300–340px ثم تحكم قابل للتمرير.
|
||||
- الحركة مستمرة عند الحاجة فقط، ويفضل 60fps مع دعم شاشات 120Hz من دون ربط صحة الحساب بعدد الإطارات.
|
||||
- عند تفعيل تقليل الحركة في النظام، تستبدل الحركة المستمرة بانتقالات قصيرة ولقطات حالة مفهومة.
|
||||
|
||||
## 5. نموذج الحالة والتحكم
|
||||
|
||||
ينشئ المطور بعد مراجعة المصدر جدول متغيرات صريحاً:
|
||||
|
||||
| المتغير | معناه في الدرس | النوع/الوحدة | المجال المسموح | القيمة الابتدائية | أثره المرئي |
|
||||
|---|---|---|---|---|---|
|
||||
| `focusParameter` | العامل الرئيس المثبت في المصدر | يحدد من الدرس | يحدد من أمثلة المصدر | حالة محايدة | يغير طبقة واحدة بوضوح |
|
||||
| `comparisonParameter` | عامل المقارنة أو الحالة الثانية | يحدد من الدرس | يحدد من المصدر | مغلق | يظهر فرقاً قابلاً للتفسير |
|
||||
| `timeOrStep` | الزمن أو ترتيب الخطوات عند انطباقه | ثانية/خطوة | بحسب الظاهرة | 0 | يحرك العملية دون تغيير النتيجة حسابياً |
|
||||
|
||||
لوحة التحكم لا تتجاوز ثلاثة عناصر أساسية في المهمة الواحدة. الخيارات المتقدمة تظهر تدريجياً بعد نجاح الطالب في اكتشاف العلاقة الأولى.
|
||||
|
||||
## 6. قواعد المحاكاة والدقة
|
||||
|
||||
- كل معادلة أو ترتيب أو علاقة سببية تُسجل هنا مع مرجع الصفحة/القسم قبل كتابتها في Dart.
|
||||
- تفصل طبقة الحسابات عن `CustomPainter`، وتغطى باختبارات وحدات للقيم الحدية والحالات غير الصالحة.
|
||||
- لا تستخدم عشوائية تؤثر في الإجابة إلا ببذرة محفوظة وقيم ضمن نطاق المصدر.
|
||||
- لا تعرض قيماً علمية أو تاريخية أو لغوية تقريبية بوصفها دقيقة. عند غياب الدليل تعرض «يحتاج مراجعة» في بيئة التأليف فقط.
|
||||
- النص والكانفاس ولوحة التحكم تتشارك الحالة نفسها؛ لا يجوز أن تخالف القراءة الرقمية الرسم.
|
||||
|
||||
## 7. محطات فحص الفهم
|
||||
|
||||
تنفذ عبر `LabCheckpointCallback` بعد ظهور الدليل للطالب، وبالترتيب التالي:
|
||||
|
||||
1. **توقّع:** ما الحالة المتوقعة قبل تشغيل التغيير؟ الخيارات تمثل أخطاء شائعة مثبتة أو يراجعها المعلم.
|
||||
2. **استدلال:** أي دليل مرئي/عددي يثبت ما حدث؟ لا يُسأل الطالب قبل ظهور الدليل.
|
||||
3. **تفسير:** لماذا تغيّرت النتيجة عند تغيير العامل المحدد؟
|
||||
4. **نقل:** ماذا يحدث في حالة جديدة تستخدم المفهوم نفسه؟
|
||||
|
||||
لكل محطة يسجل المنفذ: `checkpoint_id`، المحور المنهجي، شرط الإطلاق، السؤال، الخيارات، الإجابة وتعليلها، مرجع الصفحة/القسم، والتلميح. لا تُرسل الإجابة الصحيحة مسبقاً إلى العميل عندما تدخل النتيجة في الإتقان.
|
||||
|
||||
## 8. التغذية الراجعة ومنع التخمين
|
||||
|
||||
- الإجابة الخاطئة لا تعيد المشهد عشوائياً؛ تضيء المتغير والدليل المرتبطين بالخطأ وتقدم تلميحاً واحداً.
|
||||
- بعد محاولتين، تفكك المهمة إلى خطوة أصغر ثم تعيد حالة نقل مختلفة.
|
||||
- يمنع زر «التالي» حتى يلمس الطالب المتغير المقصود ويرى الأثر، لكن يتوفر دائماً زر إعادة الضبط والخروج.
|
||||
- لا تستخدم مؤثر نجاح مبالغاً فيه؛ اهتزاز خفيف وصوت اختياري وحركة قصيرة تكفي.
|
||||
|
||||
## 9. النظام البصري وإمكانية الوصول
|
||||
|
||||
- الخلفية `#07111F`، السطح `#0B1728`/`#102035`، الحدود `Colors.white10`، والتركيز الأساسي `#00F5D4` أو `#0071E3`.
|
||||
- الخط من خط النظام SF Arabic/Geeza Pro؛ لا يعتمد المختبر على تنزيل خط خارجي.
|
||||
- أقل هدف لمس 44×44، وتباين النص الأساسي 4.5:1، وتسمية دلالية لكل تحكم وقراءة.
|
||||
- اللون ليس الدليل الوحيد: كل حالة لها شكل/رمز/نص، مع دعم RTL وترتيب قراءة صحيح لقارئ الشاشة.
|
||||
- يوفر وصفاً نصياً متجدداً للمشهد للطالب الذي لا يرى الرسم، ويعلن التغيرات المهمة دون إغراق قارئ الشاشة.
|
||||
|
||||
## 10. بنية Flutter المطلوبة
|
||||
|
||||
- ملف مستقل باسم مشتق من هوية الدرس، وWidget من نوع `StatefulWidget`.
|
||||
- يستخدم `SaqelLabScaffold` بعد تنقيحه ليحمل هوية الدرس وإصدار الحزمة، لا مساراً نصياً مختلقاً.
|
||||
- `CustomPainter` للرسم، وطبقة domain مستقلة للحساب، وcontroller للحالة، واختبارات widget للمقاسين 390×844 و1024×768.
|
||||
- التسجيل في `labs_registry.dart` يكون بمفتاح `curriculum_lesson_id` صريح وارد من الخادم فقط. ممنوع fallback حسب المبحث أو كلمات العنوان.
|
||||
- المختبر غير المتاح في حزمة منشورة لا يظهر في تجربة الطالب؛ يمكن معاينته فقط في بيئة التأليف المعلّمة بوضوح.
|
||||
|
||||
## 11. سيناريوهات التحقق الإلزامية
|
||||
|
||||
- فتح الدرس الصحيح يعرض هذا المختبر فقط بعد تحقق الحزمة المنشورة والصلاحية.
|
||||
- درس يحمل عنواناً مشابهاً وهوية مختلفة لا يفتح هذا المختبر.
|
||||
- الهاتف واللوحي لا يعانيان overflow، والكانفاس يبقى مرئياً أثناء التحكم.
|
||||
- إعادة الضبط تعيد كل المتغيرات والمؤقتات ومحطات الفهم إلى الحالة الابتدائية.
|
||||
- كل حد أدنى/أقصى يعطي رسماً وقراءة متطابقين ولا ينتج NaN/Infinity.
|
||||
- تقليل الحركة وقارئ الشاشة والتنقل بلوحة المفاتيح يعملون.
|
||||
- لا توجد صور خارجية، بيانات ديمو، أرقام نجاح، أو ادعاء إتقان محلي.
|
||||
|
||||
## 12. تعريف الاكتمال
|
||||
|
||||
- [ ] راجع مختص المادة النتاجات والمحاور والمصطلحات والأمثلة.
|
||||
- [ ] وثقت المعادلات/العلاقات مع الصفحات أو الأقسام.
|
||||
- [ ] اعتمد سيناريو اللعب وأخطاء الطلاب الشائعة.
|
||||
- [ ] نُفذت المحاكاة المتجهية والتحكم المتجاوب.
|
||||
- [ ] نُفذت محطات الفهم بأدلة وتعليلات وتلميحات.
|
||||
- [ ] نجحت اختبارات domain وwidget والوصول والحالات الحدية.
|
||||
- [ ] رُبط المختبر بحزمة خادم منشورة عبر هوية وإصدار صريحين.
|
||||
- [ ] راجع الطالب/المعلم التجربة ولم تُسجل كمكتملة قبل الدليل.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user