@magmamath/students-features 1.3.26-rc.6 → 1.3.26-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/commonjs/features/fluency/assets/SparkleIcon.js +30 -0
  2. package/dist/commonjs/features/fluency/assets/SparkleIcon.js.map +1 -0
  3. package/dist/commonjs/features/fluency/components/FluencySolving.js +6 -27
  4. package/dist/commonjs/features/fluency/components/FluencySolving.js.map +1 -1
  5. package/dist/commonjs/features/fluency/components/day-streak-counter/DayStreak.js +48 -42
  6. package/dist/commonjs/features/fluency/components/day-streak-counter/DayStreak.js.map +1 -1
  7. package/dist/commonjs/features/fluency/components/day-streak-counter/DayStreakCounter.js +8 -7
  8. package/dist/commonjs/features/fluency/components/day-streak-counter/DayStreakCounter.js.map +1 -1
  9. package/dist/commonjs/features/fluency/components/fluency-solving/components/EquationContent.js +8 -5
  10. package/dist/commonjs/features/fluency/components/fluency-solving/components/EquationContent.js.map +1 -1
  11. package/dist/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgress.js +50 -34
  12. package/dist/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgress.js.map +1 -1
  13. package/dist/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.js +69 -0
  14. package/dist/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.js.map +1 -0
  15. package/dist/commonjs/features/fluency/components/matrix/components/CellTooltip.js +1 -1
  16. package/dist/commonjs/features/fluency/components/matrix/components/CellTooltip.js.map +1 -1
  17. package/dist/commonjs/features/fluency/components/matrix/components/MatrixCell.js +12 -6
  18. package/dist/commonjs/features/fluency/components/matrix/components/MatrixCell.js.map +1 -1
  19. package/dist/commonjs/features/fluency/components/select-math-operator/ProgressBar.js +32 -14
  20. package/dist/commonjs/features/fluency/components/select-math-operator/ProgressBar.js.map +1 -1
  21. package/dist/commonjs/features/fluency/components/select-math-operator/ProgressBarSparkles.js +127 -0
  22. package/dist/commonjs/features/fluency/components/select-math-operator/ProgressBarSparkles.js.map +1 -0
  23. package/dist/commonjs/features/fluency/components/select-math-operator/SelectMathOperator.js +2 -1
  24. package/dist/commonjs/features/fluency/components/select-math-operator/SelectMathOperator.js.map +1 -1
  25. package/dist/commonjs/features/fluency/components/session-results/FluencySessionResults.js +24 -14
  26. package/dist/commonjs/features/fluency/components/session-results/FluencySessionResults.js.map +1 -1
  27. package/dist/commonjs/features/fluency/components/start-banner/StartBanner.js +17 -7
  28. package/dist/commonjs/features/fluency/components/start-banner/StartBanner.js.map +1 -1
  29. package/dist/commonjs/features/fluency/fluency.constants.js +1 -0
  30. package/dist/commonjs/features/fluency/fluency.constants.js.map +1 -1
  31. package/dist/commonjs/features/fluency/fluency.helpers.js +6 -1
  32. package/dist/commonjs/features/fluency/fluency.helpers.js.map +1 -1
  33. package/dist/commonjs/features/fluency/hooks/useFluencyNumpad.js +3 -0
  34. package/dist/commonjs/features/fluency/hooks/useFluencyNumpad.js.map +1 -1
  35. package/dist/commonjs/features/fluency/hooks/usePreventPageLeave.js +18 -0
  36. package/dist/commonjs/features/fluency/hooks/usePreventPageLeave.js.map +1 -0
  37. package/dist/module/features/fluency/assets/SparkleIcon.js +22 -0
  38. package/dist/module/features/fluency/assets/SparkleIcon.js.map +1 -0
  39. package/dist/module/features/fluency/components/FluencySolving.js +6 -26
  40. package/dist/module/features/fluency/components/FluencySolving.js.map +1 -1
  41. package/dist/module/features/fluency/components/day-streak-counter/DayStreak.js +48 -42
  42. package/dist/module/features/fluency/components/day-streak-counter/DayStreak.js.map +1 -1
  43. package/dist/module/features/fluency/components/day-streak-counter/DayStreakCounter.js +9 -8
  44. package/dist/module/features/fluency/components/day-streak-counter/DayStreakCounter.js.map +1 -1
  45. package/dist/module/features/fluency/components/fluency-solving/components/EquationContent.js +8 -5
  46. package/dist/module/features/fluency/components/fluency-solving/components/EquationContent.js.map +1 -1
  47. package/dist/module/features/fluency/components/fluency-solving/components/FluencySolvingProgress.js +51 -35
  48. package/dist/module/features/fluency/components/fluency-solving/components/FluencySolvingProgress.js.map +1 -1
  49. package/dist/module/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.js +62 -0
  50. package/dist/module/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.js.map +1 -0
  51. package/dist/module/features/fluency/components/matrix/components/CellTooltip.js +1 -1
  52. package/dist/module/features/fluency/components/matrix/components/CellTooltip.js.map +1 -1
  53. package/dist/module/features/fluency/components/matrix/components/MatrixCell.js +13 -7
  54. package/dist/module/features/fluency/components/matrix/components/MatrixCell.js.map +1 -1
  55. package/dist/module/features/fluency/components/select-math-operator/ProgressBar.js +32 -14
  56. package/dist/module/features/fluency/components/select-math-operator/ProgressBar.js.map +1 -1
  57. package/dist/module/features/fluency/components/select-math-operator/ProgressBarSparkles.js +121 -0
  58. package/dist/module/features/fluency/components/select-math-operator/ProgressBarSparkles.js.map +1 -0
  59. package/dist/module/features/fluency/components/select-math-operator/SelectMathOperator.js +2 -1
  60. package/dist/module/features/fluency/components/select-math-operator/SelectMathOperator.js.map +1 -1
  61. package/dist/module/features/fluency/components/session-results/FluencySessionResults.js +24 -14
  62. package/dist/module/features/fluency/components/session-results/FluencySessionResults.js.map +1 -1
  63. package/dist/module/features/fluency/components/start-banner/StartBanner.js +17 -7
  64. package/dist/module/features/fluency/components/start-banner/StartBanner.js.map +1 -1
  65. package/dist/module/features/fluency/fluency.constants.js +1 -0
  66. package/dist/module/features/fluency/fluency.constants.js.map +1 -1
  67. package/dist/module/features/fluency/fluency.helpers.js +6 -1
  68. package/dist/module/features/fluency/fluency.helpers.js.map +1 -1
  69. package/dist/module/features/fluency/hooks/useFluencyNumpad.js +3 -0
  70. package/dist/module/features/fluency/hooks/useFluencyNumpad.js.map +1 -1
  71. package/dist/module/features/fluency/hooks/usePreventPageLeave.js +13 -0
  72. package/dist/module/features/fluency/hooks/usePreventPageLeave.js.map +1 -0
  73. package/dist/typescript/commonjs/features/fluency/assets/SparkleIcon.d.ts +4 -0
  74. package/dist/typescript/commonjs/features/fluency/assets/SparkleIcon.d.ts.map +1 -0
  75. package/dist/typescript/commonjs/features/fluency/components/FluencySolving.d.ts.map +1 -1
  76. package/dist/typescript/commonjs/features/fluency/components/day-streak-counter/DayStreak.d.ts +1 -5
  77. package/dist/typescript/commonjs/features/fluency/components/day-streak-counter/DayStreak.d.ts.map +1 -1
  78. package/dist/typescript/commonjs/features/fluency/components/day-streak-counter/DayStreakCounter.d.ts.map +1 -1
  79. package/dist/typescript/commonjs/features/fluency/components/fluency-solving/components/EquationContent.d.ts.map +1 -1
  80. package/dist/typescript/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgress.d.ts +1 -8
  81. package/dist/typescript/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgress.d.ts.map +1 -1
  82. package/dist/typescript/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.d.ts +3 -0
  83. package/dist/typescript/commonjs/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.d.ts.map +1 -0
  84. package/dist/typescript/commonjs/features/fluency/components/matrix/components/MatrixCell.d.ts +1 -1
  85. package/dist/typescript/commonjs/features/fluency/components/matrix/components/MatrixCell.d.ts.map +1 -1
  86. package/dist/typescript/commonjs/features/fluency/components/select-math-operator/ProgressBar.d.ts.map +1 -1
  87. package/dist/typescript/commonjs/features/fluency/components/select-math-operator/ProgressBarSparkles.d.ts +6 -0
  88. package/dist/typescript/commonjs/features/fluency/components/select-math-operator/ProgressBarSparkles.d.ts.map +1 -0
  89. package/dist/typescript/commonjs/features/fluency/components/session-results/FluencySessionResults.d.ts +1 -2
  90. package/dist/typescript/commonjs/features/fluency/components/session-results/FluencySessionResults.d.ts.map +1 -1
  91. package/dist/typescript/commonjs/features/fluency/components/start-banner/StartBanner.d.ts.map +1 -1
  92. package/dist/typescript/commonjs/features/fluency/fluency.constants.d.ts +2 -1
  93. package/dist/typescript/commonjs/features/fluency/fluency.constants.d.ts.map +1 -1
  94. package/dist/typescript/commonjs/features/fluency/fluency.helpers.d.ts.map +1 -1
  95. package/dist/typescript/commonjs/features/fluency/hooks/useFluencyNumpad.d.ts +1 -1
  96. package/dist/typescript/commonjs/features/fluency/hooks/useFluencyNumpad.d.ts.map +1 -1
  97. package/dist/typescript/commonjs/features/fluency/hooks/usePreventPageLeave.d.ts +2 -0
  98. package/dist/typescript/commonjs/features/fluency/hooks/usePreventPageLeave.d.ts.map +1 -0
  99. package/dist/typescript/module/features/fluency/assets/SparkleIcon.d.ts +4 -0
  100. package/dist/typescript/module/features/fluency/assets/SparkleIcon.d.ts.map +1 -0
  101. package/dist/typescript/module/features/fluency/components/FluencySolving.d.ts.map +1 -1
  102. package/dist/typescript/module/features/fluency/components/day-streak-counter/DayStreak.d.ts +1 -5
  103. package/dist/typescript/module/features/fluency/components/day-streak-counter/DayStreak.d.ts.map +1 -1
  104. package/dist/typescript/module/features/fluency/components/day-streak-counter/DayStreakCounter.d.ts.map +1 -1
  105. package/dist/typescript/module/features/fluency/components/fluency-solving/components/EquationContent.d.ts.map +1 -1
  106. package/dist/typescript/module/features/fluency/components/fluency-solving/components/FluencySolvingProgress.d.ts +1 -8
  107. package/dist/typescript/module/features/fluency/components/fluency-solving/components/FluencySolvingProgress.d.ts.map +1 -1
  108. package/dist/typescript/module/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.d.ts +3 -0
  109. package/dist/typescript/module/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.d.ts.map +1 -0
  110. package/dist/typescript/module/features/fluency/components/matrix/components/MatrixCell.d.ts +1 -1
  111. package/dist/typescript/module/features/fluency/components/matrix/components/MatrixCell.d.ts.map +1 -1
  112. package/dist/typescript/module/features/fluency/components/select-math-operator/ProgressBar.d.ts.map +1 -1
  113. package/dist/typescript/module/features/fluency/components/select-math-operator/ProgressBarSparkles.d.ts +6 -0
  114. package/dist/typescript/module/features/fluency/components/select-math-operator/ProgressBarSparkles.d.ts.map +1 -0
  115. package/dist/typescript/module/features/fluency/components/session-results/FluencySessionResults.d.ts +1 -2
  116. package/dist/typescript/module/features/fluency/components/session-results/FluencySessionResults.d.ts.map +1 -1
  117. package/dist/typescript/module/features/fluency/components/start-banner/StartBanner.d.ts.map +1 -1
  118. package/dist/typescript/module/features/fluency/fluency.constants.d.ts +2 -1
  119. package/dist/typescript/module/features/fluency/fluency.constants.d.ts.map +1 -1
  120. package/dist/typescript/module/features/fluency/fluency.helpers.d.ts.map +1 -1
  121. package/dist/typescript/module/features/fluency/hooks/useFluencyNumpad.d.ts +1 -1
  122. package/dist/typescript/module/features/fluency/hooks/useFluencyNumpad.d.ts.map +1 -1
  123. package/dist/typescript/module/features/fluency/hooks/usePreventPageLeave.d.ts +2 -0
  124. package/dist/typescript/module/features/fluency/hooks/usePreventPageLeave.d.ts.map +1 -0
  125. package/package.json +1 -1
  126. package/src/features/fluency/assets/SparkleIcon.tsx +14 -0
  127. package/src/features/fluency/components/FluencySolving.tsx +7 -31
  128. package/src/features/fluency/components/day-streak-counter/DayStreak.tsx +35 -35
  129. package/src/features/fluency/components/day-streak-counter/DayStreakCounter.tsx +16 -12
  130. package/src/features/fluency/components/fluency-solving/components/EquationContent.tsx +15 -7
  131. package/src/features/fluency/components/fluency-solving/components/FluencySolvingProgress.tsx +50 -35
  132. package/src/features/fluency/components/fluency-solving/components/FluencySolvingProgressDot.tsx +71 -0
  133. package/src/features/fluency/components/matrix/components/CellTooltip.tsx +1 -1
  134. package/src/features/fluency/components/matrix/components/MatrixCell.tsx +19 -6
  135. package/src/features/fluency/components/select-math-operator/ProgressBar.tsx +27 -8
  136. package/src/features/fluency/components/select-math-operator/ProgressBarSparkles.tsx +114 -0
  137. package/src/features/fluency/components/select-math-operator/SelectMathOperator.tsx +1 -0
  138. package/src/features/fluency/components/session-results/FluencySessionResults.tsx +17 -11
  139. package/src/features/fluency/components/start-banner/StartBanner.tsx +15 -6
  140. package/src/features/fluency/fluency.constants.ts +1 -0
  141. package/src/features/fluency/fluency.helpers.ts +9 -1
  142. package/src/features/fluency/hooks/useFluencyNumpad.tsx +3 -0
  143. package/src/features/fluency/hooks/usePreventPageLeave.ts +11 -0
  144. package/src/i18n/.generated/schema.json +9 -1
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef } from 'react'
1
+ import React, { useRef } from 'react'
2
2
  import { View, StyleSheet, TextInput, Platform } from 'react-native'
3
3
  import {
4
4
  COLORS,
@@ -7,6 +7,7 @@ import {
7
7
  SPACING,
8
8
  Typography,
9
9
  } from '@magmamath/react-native-ui'
10
+ import { useText } from '../../../../../i18n/i18n'
10
11
  import { useFluencySolvingPreset } from '../../../hooks/useFluencySolvingPreset'
11
12
  import { usePreventWebKeys } from '../../../hooks/usePreventWebKeys'
12
13
  import { MAX_INPUT_LENGTH } from '../../../fluency.constants'
@@ -28,6 +29,7 @@ export const EquationContent = ({
28
29
  readOnly = false,
29
30
  onChange,
30
31
  }: EquationContentProps) => {
32
+ const t = useText()
31
33
  const modePreset = useFluencySolvingPreset()
32
34
 
33
35
  const inputRef = useRef<TextInput>(null)
@@ -36,7 +38,7 @@ export const EquationContent = ({
36
38
  return (
37
39
  <>
38
40
  <Typography variant={HeadingVariants.H9} style={styles.cardLabel}>
39
- Type your answer
41
+ {t('student.fluency.typeYourAnswer')}
40
42
  </Typography>
41
43
  <View style={styles.equationRow}>
42
44
  <Typography style={styles.number}>{leftOperand}</Typography>
@@ -45,7 +47,12 @@ export const EquationContent = ({
45
47
  </Typography>
46
48
  <Typography style={styles.number}>{rightOperand}</Typography>
47
49
  <Typography style={styles.equals}>=</Typography>
48
- <View style={[styles.answerBox, value.length > 0 && { borderBottomColor: modePreset.colors.accent }]}>
50
+ <View
51
+ style={[
52
+ styles.answerBox,
53
+ value.length > 0 && { borderBottomColor: modePreset.colors.accent },
54
+ ]}
55
+ >
49
56
  {!readOnly ? (
50
57
  <TextInput
51
58
  ref={inputRef}
@@ -73,7 +80,7 @@ const styles = StyleSheet.create({
73
80
  fontSize: 11,
74
81
  fontWeight: '700',
75
82
  letterSpacing: 1.3,
76
- marginBottom: SPACING[300],
83
+ marginBottom: SPACING[500],
77
84
  color: COLORS.NEUTRAL_7,
78
85
  },
79
86
  equationRow: {
@@ -83,20 +90,21 @@ const styles = StyleSheet.create({
83
90
  marginTop: SPACING[600],
84
91
  },
85
92
  number: {
86
- fontSize: 60,
93
+ fontSize: 75,
87
94
  color: COLORS.NEUTRAL_10,
88
95
  fontWeight: '700',
89
96
  fontFamily: FONT_FAMILY.buenosAires,
90
97
  },
91
98
  equals: {
92
- fontSize: 60,
99
+ fontSize: 75,
93
100
  color: COLORS.NEUTRAL_7,
94
101
  fontWeight: '700',
95
102
  fontFamily: FONT_FAMILY.buenosAires,
96
103
  },
97
104
  answerBox: {
98
- width: 110,
105
+ width: 130,
99
106
  height: 44,
107
+ marginBottom: -SPACING[800],
100
108
  alignItems: 'center',
101
109
  borderBottomWidth: 8,
102
110
  borderBottomColor: COLORS.NEUTRAL_10,
@@ -1,32 +1,47 @@
1
1
  import React from 'react'
2
2
  import { View, StyleSheet } from 'react-native'
3
3
  import { COLORS, SPACING } from '@magmamath/react-native-ui'
4
- import { AttemptStatus } from '../../../fluency.constants'
4
+ import { AttemptStatus, SESSION_LENGTH } from '../../../fluency.constants'
5
5
  import type { QuestionOutcome } from '../fluencySolving.types'
6
+ import { FluencySolvingProgressDot } from './FluencySolvingProgressDot'
7
+ import { useFluency } from '../../../context/FluencyContext'
8
+ import { useUnit } from 'effector-react'
6
9
 
7
- type FluencySolvingProgressProps = {
8
- total: number
10
+ const GROUP_SIZE = 5
11
+
12
+ type ProgressDotProps = {
13
+ index: number
9
14
  activeIndex: number
10
- outcomes: QuestionOutcome[]
15
+ outcome: QuestionOutcome | undefined
16
+ }
17
+
18
+ const ProgressDot = ({ index, activeIndex, outcome }: ProgressDotProps) => {
19
+ if (index === activeIndex) return <FluencySolvingProgressDot />
20
+ if (index < activeIndex && outcome) return <View style={DOT_STYLE_BY_OUTCOME[outcome.status]} />
21
+ return <View style={styles.dot} />
11
22
  }
12
23
 
13
- export const FluencySolvingProgress = ({
14
- total,
15
- activeIndex,
16
- outcomes,
17
- }: FluencySolvingProgressProps) => {
24
+ export const FluencySolvingProgress = () => {
25
+ const { model } = useFluency()
26
+ const outcomes = useUnit(model.solving.$outcomes)
27
+
18
28
  return (
19
29
  <View style={styles.container}>
20
- {Array.from({ length: total }).map((_, index) => {
21
- if (index === activeIndex) return <View key={index} style={styles.dotActive} />
22
- if (index < activeIndex) {
23
- const outcome = outcomes[index]
24
- return (
25
- <View key={index} style={outcome ? DOT_STYLE_BY_OUTCOME[outcome.status] : styles.dot} />
26
- )
27
- }
28
- return <View key={index} style={styles.dot} />
29
- })}
30
+ {Array.from({ length: SESSION_LENGTH / GROUP_SIZE }, (_, groupIndex) => (
31
+ <View key={groupIndex} style={styles.group}>
32
+ {Array.from({ length: GROUP_SIZE }, (_, dotIndex) => {
33
+ const index = groupIndex * GROUP_SIZE + dotIndex
34
+ return (
35
+ <ProgressDot
36
+ key={index}
37
+ index={index}
38
+ activeIndex={outcomes.length}
39
+ outcome={outcomes[index]}
40
+ />
41
+ )
42
+ })}
43
+ </View>
44
+ ))}
30
45
  </View>
31
46
  )
32
47
  }
@@ -34,33 +49,33 @@ export const FluencySolvingProgress = ({
34
49
  const styles = StyleSheet.create({
35
50
  container: {
36
51
  flexDirection: 'row',
37
- gap: SPACING[200],
52
+ gap: SPACING[500],
38
53
  paddingHorizontal: SPACING[600],
39
54
  paddingVertical: 14,
40
55
  justifyContent: 'center',
56
+ alignItems: 'center',
57
+ },
58
+ group: {
59
+ flexDirection: 'row',
60
+ gap: 8,
61
+ alignItems: 'center',
41
62
  },
42
63
  dot: {
43
- width: 12,
44
- height: 12,
45
- borderRadius: 6,
64
+ width: 10,
65
+ height: 10,
66
+ borderRadius: 5,
46
67
  backgroundColor: COLORS.NEUTRAL_4,
47
68
  },
48
- dotActive: {
49
- width: 34,
50
- height: 12,
51
- backgroundColor: COLORS.NEUTRAL_10,
52
- borderRadius: 9999,
53
- },
54
69
  dotCorrect: {
55
- width: 12,
56
- height: 12,
57
- borderRadius: 6,
70
+ width: 10,
71
+ height: 10,
72
+ borderRadius: 5,
58
73
  backgroundColor: COLORS.PRIMARY_GREEN,
59
74
  },
60
75
  dotWrong: {
61
- width: 12,
62
- height: 12,
63
- borderRadius: 6,
76
+ width: 10,
77
+ height: 10,
78
+ borderRadius: 5,
64
79
  backgroundColor: COLORS.PRIMARY_RED,
65
80
  },
66
81
  })
@@ -0,0 +1,71 @@
1
+ import React, { useEffect } from 'react'
2
+ import { View, StyleSheet } from 'react-native'
3
+ import Animated, {
4
+ cancelAnimation,
5
+ Easing,
6
+ interpolate,
7
+ useAnimatedStyle,
8
+ useSharedValue,
9
+ withRepeat,
10
+ withSequence,
11
+ withTiming,
12
+ } from 'react-native-reanimated'
13
+ import { COLORS } from '@magmamath/react-native-ui'
14
+
15
+ const DOT_SIZE = 16
16
+ const RING_SPREAD_MIN = 4
17
+ const RING_SPREAD_MAX = 8
18
+ const RING_SIZE = DOT_SIZE + RING_SPREAD_MAX * 2
19
+ const RING_SCALE_MIN = (DOT_SIZE + RING_SPREAD_MIN * 2) / RING_SIZE
20
+ const RING_OPACITY_MIN = 0.06
21
+ const RING_OPACITY_MAX = 0.12
22
+ const PULSE_DURATION_MS = 800
23
+
24
+ export const FluencySolvingProgressDot = () => {
25
+ const progress = useSharedValue(0)
26
+
27
+ useEffect(() => {
28
+ progress.value = withRepeat(
29
+ withSequence(
30
+ withTiming(1, { duration: PULSE_DURATION_MS, easing: Easing.inOut(Easing.ease) }),
31
+ withTiming(0, { duration: PULSE_DURATION_MS, easing: Easing.inOut(Easing.ease) })
32
+ ),
33
+ -1,
34
+ false
35
+ )
36
+ return () => cancelAnimation(progress)
37
+ }, [progress])
38
+
39
+ const ringStyle = useAnimatedStyle(() => {
40
+ const scale = interpolate(progress.value, [0, 1], [RING_SCALE_MIN, 1])
41
+ const opacity = interpolate(progress.value, [0, 1], [RING_OPACITY_MAX, RING_OPACITY_MIN])
42
+ return {
43
+ opacity,
44
+ transform: [{ scale }],
45
+ }
46
+ }, [progress])
47
+
48
+ return (
49
+ <View style={styles.dot}>
50
+ <Animated.View style={[styles.ring, ringStyle]} />
51
+ </View>
52
+ )
53
+ }
54
+
55
+ const styles = StyleSheet.create({
56
+ dot: {
57
+ width: DOT_SIZE,
58
+ height: DOT_SIZE,
59
+ borderRadius: DOT_SIZE / 2,
60
+ backgroundColor: COLORS.NEUTRAL_10,
61
+ },
62
+ ring: {
63
+ position: 'absolute',
64
+ top: -RING_SPREAD_MAX,
65
+ left: -RING_SPREAD_MAX,
66
+ width: RING_SIZE,
67
+ height: RING_SIZE,
68
+ borderRadius: RING_SIZE / 2,
69
+ backgroundColor: COLORS.NEUTRAL_10,
70
+ },
71
+ })
@@ -28,13 +28,13 @@ const styles = StyleSheet.create({
28
28
  },
29
29
  label: {
30
30
  fontSize: 13,
31
- lineHeight: 16,
32
31
  color: COLORS.NEUTRAL_9,
33
32
  textAlign: 'center',
34
33
  },
35
34
  arrow: {
36
35
  width: 0,
37
36
  height: 0,
37
+ marginTop: -4,
38
38
  borderLeftWidth: 9,
39
39
  borderRightWidth: 9,
40
40
  borderTopWidth: 12,
@@ -1,6 +1,7 @@
1
1
  import React, { useState } from 'react'
2
2
  import { Pressable, View, Text, StyleSheet } from 'react-native'
3
- import { BORDER_RADIUS, COLORS, SPACING } from '@magmamath/react-native-ui'
3
+ import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
4
+ import { BORDER_RADIUS, COLORS, FONT_FAMILY, SPACING } from '@magmamath/react-native-ui'
4
5
  import { getMasteryLevelByBox } from '../../../fluency.helpers'
5
6
  import { FluencyMasteryLevel } from '../../../fluency.constants'
6
7
  import { StarFillIcon } from '../../../assets/StarFillIcon'
@@ -13,7 +14,13 @@ type MatrixCellProps = Omit<GridCell, 'label'> & {
13
14
  tooltipLabel: string | null
14
15
  }
15
16
 
16
- export const MatrixCell = ({ label, box, isHeader, levelColors, tooltipLabel }: MatrixCellProps) => {
17
+ export const MatrixCell = ({
18
+ label,
19
+ box,
20
+ isHeader,
21
+ levelColors,
22
+ tooltipLabel,
23
+ }: MatrixCellProps) => {
17
24
  const [isActive, setIsActive] = useState(false)
18
25
  const masteryLevel = box ? getMasteryLevelByBox(box) : null
19
26
  const isMastered = masteryLevel === FluencyMasteryLevel.MASTERED
@@ -32,9 +39,13 @@ export const MatrixCell = ({ label, box, isHeader, levelColors, tooltipLabel }:
32
39
  return (
33
40
  <View style={[styles.wrapper, showTooltip && styles.wrapperActive]}>
34
41
  {showTooltip && (
35
- <View style={styles.tooltipContainer} pointerEvents="none">
42
+ <Animated.View
43
+ entering={FadeIn.duration(200)}
44
+ exiting={FadeOut.duration(200)}
45
+ style={[styles.tooltipContainer, { pointerEvents: 'none' }]}
46
+ >
36
47
  <CellTooltip label={tooltipLabel} />
37
- </View>
48
+ </Animated.View>
38
49
  )}
39
50
  <Pressable
40
51
  style={[styles.cell, { backgroundColor: bgColor }]}
@@ -70,16 +81,18 @@ const styles = StyleSheet.create({
70
81
  },
71
82
  tooltipContainer: {
72
83
  position: 'absolute',
73
- bottom: '100%',
84
+ bottom: '80%',
74
85
  left: 0,
75
86
  right: 0,
76
87
  alignItems: 'center',
77
88
  paddingBottom: SPACING[50],
89
+ zIndex: 1,
78
90
  },
79
91
  headerText: {
80
92
  fontSize: 12,
81
93
  fontWeight: '600',
82
- color: COLORS.NEUTRAL_8,
94
+ color: COLORS.NEUTRAL_2,
95
+ fontFamily: FONT_FAMILY.buenosAires,
83
96
  },
84
97
  symbolText: {
85
98
  fontSize: 24,
@@ -8,6 +8,7 @@ import {
8
8
  SPACING,
9
9
  Typography,
10
10
  } from '@magmamath/react-native-ui'
11
+ import { ProgressBarSparkles } from './ProgressBarSparkles'
11
12
 
12
13
  const PRIMARY_COLOR_MAP: Record<SelectOptionColor, string> = {
13
14
  [SelectOptionColor.GREEN]: COLORS.PRIMARY_GREEN,
@@ -20,22 +21,38 @@ const PRIMARY_COLOR_MAP: Record<SelectOptionColor, string> = {
20
21
  [SelectOptionColor.SUNSET]: COLORS.PRIMARY_RED,
21
22
  }
22
23
 
24
+ const SPARKLE_COLOR_MAP: Record<SelectOptionColor, string> = {
25
+ [SelectOptionColor.GREEN]: COLORS.GREEN_3,
26
+ [SelectOptionColor.ORANGE]: COLORS.ORANGE_3,
27
+ [SelectOptionColor.BLUE]: COLORS.BLUE_3,
28
+ [SelectOptionColor.RED]: COLORS.RED_3,
29
+ [SelectOptionColor.YELLOW]: COLORS.YELLOW_3,
30
+ [SelectOptionColor.PURPLE]: COLORS.PURPLE_3,
31
+ [SelectOptionColor.GREY]: COLORS.NEUTRAL_4,
32
+ [SelectOptionColor.SUNSET]: COLORS.RED_3,
33
+ }
34
+
23
35
  export type ProgressBarProps = {
24
36
  progress: number
25
37
  color: SelectOptionColor
26
38
  }
27
39
 
28
40
  export const ProgressBar = ({ progress, color = SelectOptionColor.GREEN }: ProgressBarProps) => {
29
- const normalized = Math.round(Math.min(100, Math.max(0, progress)))
30
- const fillColor = PRIMARY_COLOR_MAP[color]
31
- const textColor = PRIMARY_COLOR_MAP[color]
41
+ const progressRounded = Math.round(Math.min(100, Math.max(0, progress)))
42
+ const variantColor = PRIMARY_COLOR_MAP[color]
43
+ const isComplete = progressRounded === 100
32
44
  return (
33
45
  <View style={styles.container}>
34
- <View style={styles.track}>
35
- <View style={[styles.fill, { backgroundColor: fillColor, width: `${normalized}%` }]} />
46
+ <View style={styles.trackWrapper}>
47
+ <View style={styles.track}>
48
+ <View
49
+ style={[styles.fill, { backgroundColor: variantColor, width: `${progressRounded}%` }]}
50
+ />
51
+ </View>
52
+ {isComplete && <ProgressBarSparkles color={SPARKLE_COLOR_MAP[color]} />}
36
53
  </View>
37
- <Typography variant={HeadingVariants.H10} style={{ color: textColor }}>
38
- {normalized}%
54
+ <Typography variant={HeadingVariants.H10} style={{ color: variantColor }}>
55
+ {progressRounded}%
39
56
  </Typography>
40
57
  </View>
41
58
  )
@@ -48,8 +65,10 @@ const styles = StyleSheet.create({
48
65
  gap: SPACING[200],
49
66
  alignItems: 'center',
50
67
  },
51
- track: {
68
+ trackWrapper: {
52
69
  flex: 1,
70
+ },
71
+ track: {
53
72
  height: 18,
54
73
  backgroundColor: COLORS.NEUTRAL_3,
55
74
  borderRadius: BORDER_RADIUS[450],
@@ -0,0 +1,114 @@
1
+ import React, { useEffect } from 'react'
2
+ import { StyleSheet } from 'react-native'
3
+ import Animated, {
4
+ cancelAnimation,
5
+ Easing,
6
+ useAnimatedStyle,
7
+ useSharedValue,
8
+ withDelay,
9
+ withRepeat,
10
+ withSequence,
11
+ withTiming,
12
+ } from 'react-native-reanimated'
13
+ import { SparkleIcon } from '../../assets/SparkleIcon'
14
+
15
+ // QUICK easing from Figma ≈ cubic-bezier(0.2, 0, 0, 1)
16
+ const QUICK = Easing.bezier(0.2, 0, 0, 1)
17
+
18
+ // Hold duration at End state before dissolving back to Start
19
+ const END_HOLD_MS = 800
20
+
21
+ type SparkleItemConfig = {
22
+ leftPercent: number
23
+ topOffset: number
24
+ size: number
25
+ triggerDelay: number
26
+ appearDuration: number
27
+ dissolveDuration: number
28
+ }
29
+
30
+ // Positions derived from Figma (15,73,87,135,204 px within ~230 px container)
31
+ // Timing values from Figma interaction table
32
+ const SPARKLE_CONFIGS: SparkleItemConfig[] = [
33
+ { leftPercent: 6.5, topOffset: -3, size: 14, triggerDelay: 550, appearDuration: 868, dissolveDuration: 744 },
34
+ { leftPercent: 31.7, topOffset: 7, size: 14, triggerDelay: 550, appearDuration: 868, dissolveDuration: 744 },
35
+ { leftPercent: 37.8, topOffset: -5, size: 14, triggerDelay: 250, appearDuration: 744, dissolveDuration: 868 },
36
+ { leftPercent: 58.7, topOffset: 5, size: 20, triggerDelay: 50, appearDuration: 868, dissolveDuration: 868 },
37
+ { leftPercent: 88.7, topOffset: -5, size: 14, triggerDelay: 250, appearDuration: 744, dissolveDuration: 868 },
38
+ ]
39
+
40
+ type SparkleItemProps = {
41
+ config: SparkleItemConfig
42
+ color: string
43
+ }
44
+
45
+ const SparkleItem = ({ config, color }: SparkleItemProps) => {
46
+ const loop = useSharedValue(0)
47
+
48
+ useEffect(() => {
49
+ // Loop value: 0 = Start (invisible), 1 = End (visible), 2 = dissolved back to Start
50
+ // Cycle: wait → SMART_ANIMATE to End → hold → DISSOLVE back → instant reset → repeat
51
+ loop.value = withRepeat(
52
+ withSequence(
53
+ withDelay(config.triggerDelay, withTiming(1, { duration: config.appearDuration, easing: QUICK })),
54
+ withDelay(END_HOLD_MS, withTiming(2, { duration: config.dissolveDuration, easing: QUICK })),
55
+ withTiming(0, { duration: 0 })
56
+ ),
57
+ -1,
58
+ false
59
+ )
60
+ return () => cancelAnimation(loop)
61
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
+ }, [])
63
+
64
+ const animatedStyle = useAnimatedStyle(() => {
65
+ const v = loop.value
66
+ if (v <= 1) {
67
+ // SMART_ANIMATE phase: scale + rotation morph from Start → End
68
+ return {
69
+ opacity: v,
70
+ transform: [{ scale: v }, { rotate: `${(-90 + v * 90).toFixed(1)}deg` }],
71
+ }
72
+ }
73
+ // DISSOLVE phase: fade out only, shape stays at End
74
+ return {
75
+ opacity: 2 - v,
76
+ transform: [{ scale: 1 }, { rotate: '0deg' }],
77
+ }
78
+ }, [loop])
79
+
80
+ return (
81
+ <Animated.View
82
+ style={[
83
+ styles.sparkle,
84
+ animatedStyle,
85
+ {
86
+ left: `${config.leftPercent}%`,
87
+ top: config.topOffset,
88
+ width: config.size,
89
+ height: config.size,
90
+ },
91
+ ]}
92
+ >
93
+ <SparkleIcon size={config.size} color={color} />
94
+ </Animated.View>
95
+ )
96
+ }
97
+
98
+ export type ProgressBarSparklesProps = {
99
+ color: string
100
+ }
101
+
102
+ export const ProgressBarSparkles = ({ color }: ProgressBarSparklesProps) => (
103
+ <>
104
+ {SPARKLE_CONFIGS.map((config, index) => (
105
+ <SparkleItem key={index} config={config} color={color} />
106
+ ))}
107
+ </>
108
+ )
109
+
110
+ const styles = StyleSheet.create({
111
+ sparkle: {
112
+ position: 'absolute',
113
+ },
114
+ })
@@ -65,6 +65,7 @@ const styles = StyleSheet.create({
65
65
  symbol: {
66
66
  fontSize: 32,
67
67
  fontWeight: '100',
68
+ color: COLORS.NEUTRAL_9,
68
69
  },
69
70
  symbolSelected: {
70
71
  color: COLORS.NEUTRAL_1,
@@ -15,6 +15,7 @@ import {
15
15
  } from '@magmamath/react-native-ui'
16
16
  import { useUnit } from 'effector-react'
17
17
  import { CloseIcon } from '@magmamath/students-features'
18
+ import { useText } from '../../../../i18n/i18n'
18
19
  import { useFluency } from '../../context/FluencyContext'
19
20
  import { AttemptStatus } from '../../fluency.constants'
20
21
  import {
@@ -27,23 +28,26 @@ import { SnurrisnurrBg } from '../../assets/SnurrisnurrBg'
27
28
  import { TrophyBg } from '../../assets/TrophyBg'
28
29
 
29
30
  type FluencySessionResultsProps = {
30
- studentName: string
31
31
  onEndModalPress?: () => void
32
32
  onClosePress?: () => void
33
33
  }
34
34
 
35
35
  export const FluencySessionResults = ({
36
- studentName,
37
36
  onClosePress,
38
37
  onEndModalPress,
39
38
  }: FluencySessionResultsProps) => {
40
39
  const [isOpen, setIsOpen] = useState(true)
40
+ const t = useText()
41
41
  const { model } = useFluency()
42
- const { outcomes, completedDurationMs } = useUnit({
42
+ const { outcomes, completedDurationMs, isSessionComplete, config } = useUnit({
43
43
  outcomes: model.solving.$outcomes,
44
44
  completedDurationMs: model.solving.$completedDurationMs,
45
+ isSessionComplete: model.solving.$isSessionComplete,
46
+ config: model.$config,
45
47
  })
46
48
 
49
+ if (!isSessionComplete) return null
50
+
47
51
  const correctCount = outcomes.filter((o) => o.status === AttemptStatus.CORRECT).length
48
52
  const maxStreak = calculateMaxStreak(outcomes)
49
53
  const durationDisplay = formatSessionDuration(completedDurationMs)
@@ -52,10 +56,10 @@ export const FluencySessionResults = ({
52
56
  <Modal visible={isOpen} transparent animationType="fade">
53
57
  <View style={styles.overlay}>
54
58
  <View style={styles.container}>
55
- <View style={styles.snurrisnurrLayer} pointerEvents="none">
59
+ <View style={[styles.snurrisnurrLayer, { pointerEvents: 'none' }]}>
56
60
  <SnurrisnurrBg />
57
61
  </View>
58
- <View style={styles.trophyLayer} pointerEvents="none">
62
+ <View style={[styles.trophyLayer, { pointerEvents: 'none' }]}>
59
63
  <TrophyBg />
60
64
  </View>
61
65
  <View style={styles.close}>
@@ -70,20 +74,22 @@ export const FluencySessionResults = ({
70
74
  </View>
71
75
 
72
76
  <View style={styles.titleContainer}>
73
- <Text style={styles.title}>Checked and done</Text>
74
- <Text style={styles.subtitle}>Nice job {studentName}!</Text>
77
+ <Text style={styles.title}>{t('student.fluency.resultTitle')}</Text>
78
+ <Text style={styles.subtitle}>
79
+ {t('student.fluency.resultSubtitle', { name: config?.name ?? '' })}
80
+ </Text>
75
81
  </View>
76
82
 
77
83
  <View style={styles.resultsContainer}>
78
84
  <SessionStatCard
79
85
  value={String(correctCount)}
80
- label="Wow there!"
86
+ label={t('student.fluency.resultCorrectCardLabel')}
81
87
  icon={<CorrectIcon size={30} color={COLORS.NEUTRAL_1} />}
82
88
  color={COLORS.PRIMARY_GREEN}
83
89
  />
84
90
  <SessionStatCard
85
91
  value={durationDisplay}
86
- label="Fast!"
92
+ label={t('student.fluency.fast')}
87
93
  icon={
88
94
  <View style={styles.timerIconOffset}>
89
95
  <TimerIcon size={30} color={COLORS.NEUTRAL_1} />
@@ -93,7 +99,7 @@ export const FluencySessionResults = ({
93
99
  />
94
100
  <SessionStatCard
95
101
  value={String(maxStreak)}
96
- label="Nice streak!"
102
+ label={t('student.fluency.resultStreakCardLabel')}
97
103
  icon={<FlameIcon size={30} color={COLORS.NEUTRAL_1} />}
98
104
  color={COLORS.PRIMARY_ORANGE}
99
105
  />
@@ -110,7 +116,7 @@ export const FluencySessionResults = ({
110
116
  colorScheme={ButtonColor.YELLOW}
111
117
  raiseLevel={6}
112
118
  >
113
- CLAIM PROGRESS
119
+ {t('student.fluency.claimProgress')}
114
120
  </Button>
115
121
  </View>
116
122
  </View>
@@ -21,10 +21,12 @@ import { FlashIcon } from '../../assets/FlashIcon'
21
21
  const GradientBackground = Platform.select({
22
22
  web: () => (
23
23
  <View
24
- pointerEvents="none"
25
24
  style={[
26
25
  StyleSheet.absoluteFill,
27
- { backgroundImage: 'linear-gradient(90deg, #FFFFFF 23.1%, #FFF7E9 81.73%)' } as any,
26
+ {
27
+ backgroundImage: 'linear-gradient(90deg, #FFFFFF 23.1%, #FFF7E9 81.73%)',
28
+ pointerEvents: 'none',
29
+ } as any,
28
30
  ]}
29
31
  />
30
32
  ),
@@ -36,8 +38,7 @@ const GradientBackground = Platform.select({
36
38
  locations={[0.231, 0.8173]}
37
39
  start={{ x: 0, y: 0 }}
38
40
  end={{ x: 1, y: 0 }}
39
- style={StyleSheet.absoluteFill}
40
- pointerEvents="none"
41
+ style={[StyleSheet.absoluteFill, { pointerEvents: 'none' }]}
41
42
  />
42
43
  )
43
44
  },
@@ -49,7 +50,9 @@ export const StartBanner = () => {
49
50
 
50
51
  return (
51
52
  <View style={styles.container}>
52
- <GradientBackground />
53
+ <View style={styles.gradientClip}>
54
+ <GradientBackground />
55
+ </View>
53
56
 
54
57
  <View style={styles.buttonContainer}>
55
58
  <View style={styles.buttonGroup}>
@@ -97,6 +100,11 @@ const styles = StyleSheet.create({
97
100
  borderRadius: BORDER_RADIUS[400],
98
101
  ...SHADOWS[1],
99
102
  },
103
+ gradientClip: {
104
+ ...StyleSheet.absoluteFillObject,
105
+ borderRadius: BORDER_RADIUS[400],
106
+ overflow: 'hidden',
107
+ },
100
108
  buttonContainer: {
101
109
  alignItems: 'flex-start',
102
110
  zIndex: 4,
@@ -112,9 +120,10 @@ const styles = StyleSheet.create({
112
120
  },
113
121
  buttonSubtitle: {
114
122
  fontSize: 13,
115
- fontWeight: '400',
123
+ fontWeight: '700',
116
124
  color: COLORS.BLACK,
117
125
  fontFamily: FONT_FAMILY.buenosAires,
126
+ textTransform: 'uppercase',
118
127
  },
119
128
  flashcard: {
120
129
  position: 'absolute',