AptlyDigital commited on
Commit
6d39c95
ยท
verified ยท
1 Parent(s): dcca588

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +455 -437
app.py CHANGED
@@ -618,420 +618,454 @@ def generate_performance_html():
618
  <div class="stat-percent">success</div>
619
  </div>
620
  </div>
621
-
622
- <!-- Recent Words -->
623
- <div class="section-title">
624
- <span class="section-icon">๐Ÿ“‹</span>
625
- <h4 class="section-text">RECENT</h4>
626
- </div>
627
-
628
- <div class="recent-words-container">
629
- """
630
-
631
- if recent_words:
632
- for word_data in recent_words:
633
- status_class = "perfect" if word_data['status'] == 'perfect' else "hint"
634
- status_icon = "โœจ" if word_data['status'] == 'perfect' else "๐Ÿ’ก"
635
-
636
- html += f"""
637
- <div class="word-item {status_class}">
638
- <div class="word-main">
639
- <span class="word-icon">{status_icon}</span>
640
- <span class="word-text">{word_data['word'][:15]}{'...' if len(word_data['word']) > 15 else ''}</span>
641
- </div>
642
- <div class="word-details">
643
- <span class="word-points">+{word_data['points']}</span>
644
- </div>
645
- </div>
646
- """
647
- else:
648
- html += '<div class="empty-state">No words yet</div>'
649
-
650
- html += """
651
- </div>
652
-
653
- <!-- Performance Analytics -->
654
- <div class="section-title">
655
- <span class="section-icon">๐Ÿ“ˆ</span>
656
- <h4 class="section-text">ANALYTICS</h4>
657
- </div>
658
-
659
- <div class="analytics-grid">
660
- <div class="analytics-item">
661
- <div class="analytics-label">Streak</div>
662
- <div class="analytics-value streak-display">
663
- """
664
-
665
- # Current streak
666
- streak = tracker['analytics']['perfect_streak']
667
- for i in range(min(5, streak)):
668
- html += '<span class="streak-dot active"></span>'
669
- if streak > 5:
670
- html += f'<span class="streak-count">+{streak-5}</span>'
671
-
672
- html += f"""
673
- </div>
674
- </div>
675
- <div class="analytics-item">
676
- <div class="analytics-label">Hints</div>
677
- <div class="analytics-value">{tracker['analytics']['total_hints']}</div>
678
- </div>
679
- </div>
680
  </div>
681
  """
682
 
683
  return html
684
 
685
  # -----------------------------
686
- # SIMPLE CSS - NO SCROLLING
687
  # -----------------------------
688
  css = """
689
- /* Reset everything */
690
  * {
691
  margin: 0;
692
  padding: 0;
693
  box-sizing: border-box;
694
  }
695
 
696
- /* Force no scroll */
697
- html, body {
698
  height: 100%;
699
  width: 100%;
700
- overflow: hidden !important;
701
- position: fixed;
702
  }
703
 
704
  /* Main container */
705
  .gradio-container {
 
 
 
706
  height: 100vh !important;
707
- width: 100vw !important;
708
- overflow: hidden !important;
709
- position: fixed !important;
710
- top: 0 !important;
711
- left: 0 !important;
712
- right: 0 !important;
713
- bottom: 0 !important;
714
  background: #f8fafc !important;
715
  }
716
 
717
- /* Simple two-column layout */
718
- .app-container {
719
- display: flex !important;
720
- height: 100vh !important;
721
- width: 100vw !important;
722
- overflow: hidden !important;
 
 
 
723
  }
724
 
725
- /* Sidebar - fixed width */
726
- .sidebar {
727
- width: 350px !important;
728
- min-width: 350px !important;
729
- height: 100vh !important;
730
- background: white !important;
731
- border-right: 1px solid #e2e8f0 !important;
732
- padding: 20px !important;
733
- overflow-y: auto !important;
734
- position: fixed !important;
735
- left: 0 !important;
736
- top: 0 !important;
737
- bottom: 0 !important;
738
  }
739
 
740
- /* Main content */
741
- .main-content {
742
- flex: 1 !important;
743
- height: 100vh !important;
744
- margin-left: 350px !important;
745
- padding: 20px !important;
746
- overflow-y: auto !important;
747
- background: #f8fafc !important;
748
  }
749
 
750
- /* Header */
751
- .header {
752
- background: linear-gradient(135deg, #4f46e5, #6366f1) !important;
753
- border-radius: 16px !important;
754
- padding: 24px !important;
755
- margin-bottom: 20px !important;
756
- color: white !important;
 
757
  }
758
 
759
- .header h1 {
760
- font-size: 1.8rem !important;
761
- font-weight: 700 !important;
762
- margin-bottom: 4px !important;
 
763
  }
764
 
765
- .header p {
766
- opacity: 0.9 !important;
767
- font-size: 0.95rem !important;
768
  }
769
 
770
- /* Cards */
771
- .card {
772
- background: white !important;
773
- border-radius: 12px !important;
774
- padding: 20px !important;
775
- border: 1px solid #e2e8f0 !important;
776
- box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
777
- margin-bottom: 16px !important;
 
778
  }
779
 
780
- /* Word display */
781
  .word-display-container {
782
- text-align: center !important;
783
- margin: 20px 0 !important;
784
  }
785
 
786
  .word-display {
787
- font-family: monospace !important;
788
- font-size: 2.5rem !important;
789
- font-weight: 700 !important;
790
- letter-spacing: 0.3rem !important;
791
- padding: 20px !important;
792
- background: white !important;
793
- border: 2px solid #e2e8f0 !important;
794
- border-radius: 12px !important;
795
- display: inline-block !important;
796
- min-width: 300px !important;
 
797
  }
798
 
799
  .word-display.correct {
800
- color: #10b981 !important;
801
- border-color: #10b981 !important;
802
- background: rgba(16, 185, 129, 0.05) !important;
 
803
  }
804
 
805
  .word-display.incorrect {
806
- color: #ef4444 !important;
807
- border-color: #ef4444 !important;
808
- background: rgba(239, 68, 68, 0.05) !important;
 
 
 
 
 
809
  }
810
 
811
- /* Stats row */
812
  .stats-row {
813
- display: flex !important;
814
- gap: 12px !important;
815
- margin: 16px 0 !important;
816
  }
817
 
818
  .stat-box {
819
- flex: 1 !important;
820
- text-align: center !important;
821
- padding: 16px !important;
822
- background: white !important;
823
- border: 1px solid #e2e8f0 !important;
824
- border-radius: 8px !important;
 
825
  }
826
 
827
  .stat-label {
828
- font-size: 0.8rem !important;
829
- color: #64748b !important;
830
- margin-bottom: 4px !important;
 
831
  }
832
 
833
  .stat-value {
834
- font-size: 1.5rem !important;
835
- font-weight: 700 !important;
836
- color: #4f46e5 !important;
837
  }
838
 
839
- /* Buttons */
840
- .btn {
841
- padding: 12px 20px !important;
842
- border-radius: 8px !important;
843
- font-weight: 600 !important;
844
- border: none !important;
845
- cursor: pointer !important;
846
- transition: all 0.2s !important;
847
  }
848
 
849
- .btn-primary {
850
- background: #4f46e5 !important;
851
- color: white !important;
 
 
 
852
  }
853
 
854
- .btn-success {
855
- background: #10b981 !important;
856
- color: white !important;
 
 
 
 
 
 
 
 
 
 
857
  }
858
 
859
- .btn-warning {
860
- background: #f59e0b !important;
861
- color: white !important;
 
862
  }
863
 
864
- .btn-secondary {
865
- background: white !important;
866
- color: #1e293b !important;
867
- border: 2px solid #e2e8f0 !important;
868
  }
869
 
870
- /* Audio controls */
871
- .audio-controls {
872
- display: flex !important;
873
- gap: 12px !important;
874
- margin: 20px 0 !important;
875
  }
876
 
877
- .audio-btn {
878
- flex: 1 !important;
879
- display: flex !important;
880
- flex-direction: column !important;
881
- align-items: center !important;
882
- gap: 8px !important;
883
- padding: 16px !important;
 
884
  }
885
 
886
- /* Input */
887
- .input-field {
888
- width: 100% !important;
889
- padding: 12px 16px !important;
890
- border: 2px solid #e2e8f0 !important;
891
- border-radius: 8px !important;
892
- font-size: 1rem !important;
893
  }
894
 
895
- .input-field:focus {
896
- outline: none !important;
897
- border-color: #4f46e5 !important;
 
898
  }
899
 
900
- /* Action buttons */
901
  .action-buttons {
902
- display: flex !important;
903
- gap: 12px !important;
904
- margin: 20px 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
905
  }
906
 
907
- .action-buttons .btn {
908
- flex: 1 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
909
  }
910
 
911
- /* Performance dashboard */
912
  .performance-dashboard {
913
- background: white !important;
914
- border-radius: 12px !important;
915
- padding: 20px !important;
916
- border: 1px solid #e2e8f0 !important;
917
- margin-top: 20px !important;
918
  }
919
 
920
  .dashboard-header {
921
- display: flex !important;
922
- align-items: center !important;
923
- gap: 12px !important;
924
- margin-bottom: 20px !important;
925
- padding-bottom: 16px !important;
926
- border-bottom: 2px solid #6366f1 !important;
927
  }
928
 
929
  .header-text {
930
- font-size: 1.25rem !important;
931
- font-weight: 700 !important;
932
- color: #1e293b !important;
933
  }
934
 
935
  .stats-grid {
936
- display: grid !important;
937
- grid-template-columns: repeat(3, 1fr) !important;
938
- gap: 12px !important;
939
- margin-bottom: 20px !important;
940
  }
941
 
942
  .stat-card {
943
- text-align: center !important;
944
- padding: 12px !important;
 
 
 
945
  }
946
 
947
  .stat-label-small {
948
- font-size: 0.7rem !important;
949
- color: #64748b !important;
950
- margin-bottom: 4px !important;
 
951
  }
952
 
953
  .stat-value-small {
954
- font-size: 1.25rem !important;
955
- font-weight: 700 !important;
956
- color: #4f46e5 !important;
957
  }
958
 
959
- .recent-words-container {
960
- margin: 16px 0 !important;
 
 
 
 
 
 
961
  }
962
 
963
- .word-item {
964
- display: flex !important;
965
- justify-content: space-between !important;
966
- align-items: center !important;
967
- padding: 12px !important;
968
- margin-bottom: 8px !important;
969
- background: #f8fafc !important;
970
- border-radius: 8px !important;
971
- border-left: 4px solid #e2e8f0 !important;
972
  }
973
 
974
- .word-item.perfect {
975
- border-left-color: #10b981 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  }
977
 
978
- .word-item.hint {
979
- border-left-color: #4f46e5 !important;
 
 
 
 
 
 
 
 
 
 
 
 
980
  }
981
 
982
- .analytics-grid {
983
- display: grid !important;
984
- grid-template-columns: 1fr 1fr !important;
985
- gap: 12px !important;
986
  }
987
 
988
- .analytics-item {
989
- padding: 12px !important;
990
- background: #f8fafc !important;
991
- border-radius: 8px !important;
992
  }
993
 
994
- /* Responsive */
995
- @media (max-width: 1024px) {
996
- .app-container {
997
- flex-direction: column !important;
998
- }
999
-
1000
- .sidebar {
1001
- width: 100% !important;
1002
- height: auto !important;
1003
- position: relative !important;
1004
- border-right: none !important;
1005
- border-bottom: 1px solid #e2e8f0 !important;
1006
- }
1007
-
1008
- .main-content {
1009
- margin-left: 0 !important;
1010
- height: auto !important;
1011
- flex: 1 !important;
1012
- }
1013
  }
1014
 
1015
- /* Hide scrollbars */
1016
- .sidebar::-webkit-scrollbar,
1017
- .main-content::-webkit-scrollbar {
1018
- width: 6px !important;
1019
  }
1020
 
1021
- .sidebar::-webkit-scrollbar-track,
1022
- .main-content::-webkit-scrollbar-track {
1023
- background: #f1f5f9 !important;
1024
  }
1025
 
1026
- .sidebar::-webkit-scrollbar-thumb,
1027
- .main-content::-webkit-scrollbar-thumb {
1028
- background: #cbd5e1 !important;
1029
- border-radius: 3px !important;
1030
  }
1031
  """
1032
 
1033
  # -----------------------------
1034
- # Gradio Interface - SIMPLE FIXED LAYOUT
1035
  # -----------------------------
1036
  with gr.Blocks(
1037
  title="Spelling Trainer Pro",
@@ -1052,104 +1086,78 @@ with gr.Blocks(
1052
  )
1053
  ) as app:
1054
 
1055
- # Main container
1056
- with gr.Row(elem_classes="app-container"):
1057
 
1058
- # ============ SIDEBAR ============
1059
- with gr.Column(elem_classes="sidebar"):
1060
- # Title
1061
  gr.HTML("""
1062
- <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 24px;">
1063
- <div style="font-size: 2rem;">๐Ÿ“š</div>
1064
- <div>
1065
- <h2 style="color: #4f46e5; margin: 0; font-size: 1.5rem;">Spelling Trainer</h2>
1066
- <p style="color: #64748b; margin-top: 4px; font-size: 0.9rem;">Master spelling skills</p>
1067
- </div>
1068
- </div>
1069
  """)
1070
-
1071
- # Player Profile
1072
- user_name_input = gr.Textbox(
1073
- label="๐Ÿ‘ค Your Name",
1074
- placeholder="Enter your name...",
1075
- value="",
1076
- elem_classes="input-field"
1077
- )
1078
-
1079
- # Training Mode
1080
- mode_select = gr.Radio(
1081
- choices=["Learning (Ordered)", "Practice (Random)", "Sentence Practice"],
1082
- value="Learning (Ordered)",
1083
- label="๐ŸŽฏ Training Mode",
1084
- elem_classes="card"
1085
- )
1086
-
1087
- # Word Lists
1088
- grade_select = gr.Dropdown(
1089
- label="๐Ÿ“š Word List",
1090
- choices=available_lists,
1091
- value=default_list if default_list else None,
1092
- elem_classes="input-field"
1093
- )
1094
-
1095
- # Start Session
1096
- start_session_btn = gr.Button(
1097
- "๐Ÿš€ Start Session",
1098
- variant="primary",
1099
- elem_classes="btn btn-primary"
1100
- )
1101
 
1102
  with gr.Row():
1103
- save_btn = gr.Button(
1104
- "๐Ÿ’พ Save",
1105
- variant="secondary",
1106
- elem_classes="btn btn-secondary",
1107
- scale=1
1108
- )
1109
- new_session_btn = gr.Button(
1110
- "๐Ÿ”„ Restart",
1111
- variant="secondary",
1112
- elem_classes="btn btn-secondary",
1113
- scale=1
1114
- )
1115
-
1116
- # Voice Settings
1117
- with gr.Accordion("๐ŸŽค Voice Settings", open=False):
1118
- voice_select = gr.Dropdown(
1119
- choices=list(voice_options.keys()),
1120
- value="๐Ÿ‘จโ€๐Ÿซ US Male (Guy)",
1121
- label="Select Voice",
1122
- elem_classes="input-field"
1123
- )
1124
- test_voice_btn = gr.Button(
1125
- "Test Voice",
1126
- variant="secondary",
1127
- elem_classes="btn btn-secondary"
1128
- )
1129
- test_audio = gr.Audio(label="Test Audio", visible=True)
1130
 
1131
- # Performance Dashboard
1132
- performance_html = gr.HTML(
1133
- value=generate_performance_html(),
1134
- elem_classes="performance-dashboard"
1135
- )
 
 
 
1136
 
1137
- # ============ MAIN CONTENT ============
1138
- with gr.Column(elem_classes="main-content"):
1139
- # Header
1140
- with gr.Column(elem_classes="header"):
1141
- gr.HTML("""
1142
- <h1>Spelling Trainer Pro</h1>
1143
- <p>AI-powered spelling practice for all ages</p>
1144
- """)
1145
-
1146
  # Progress
1147
- with gr.Column(elem_classes="card"):
1148
  progress_text = gr.Textbox(
1149
  label="",
1150
  value="Word 1 of 10",
1151
  interactive=False,
1152
- elem_classes="input-field"
1153
  )
1154
  progress_bar = gr.Slider(
1155
  minimum=0,
@@ -1190,70 +1198,107 @@ with gr.Blocks(
1190
  with gr.Row(elem_classes="audio-controls"):
1191
  hear_word_btn = gr.Button(
1192
  "๐Ÿ”Š Hear Word",
1193
- elem_classes="btn btn-primary audio-btn"
1194
  )
1195
  context_btn = gr.Button(
1196
  "๐ŸŽง Context",
1197
- elem_classes="btn btn-secondary audio-btn"
 
 
 
 
 
1198
  )
1199
 
1200
  # Audio outputs
1201
  word_audio = gr.Audio(label="Word Audio", visible=True)
1202
  context_audio = gr.Audio(label="Context Audio", visible=True)
 
1203
 
1204
  # Answer Input
1205
- with gr.Column(elem_classes="card"):
1206
  answer_input = gr.Textbox(
1207
  label="โœ๏ธ Type Your Answer",
1208
  placeholder="Type the word exactly as you hear it...",
1209
  lines=1,
1210
- elem_classes="input-field"
1211
  )
1212
 
1213
  # Action Buttons
1214
  with gr.Row(elem_classes="action-buttons"):
1215
  check_btn = gr.Button(
1216
  "โœ… Check Answer",
1217
- variant="primary",
1218
- elem_classes="btn btn-success"
1219
  )
1220
  hint_btn = gr.Button(
1221
  "๐Ÿ’ก Get Hint",
1222
- variant="secondary",
1223
- elem_classes="btn btn-warning"
1224
  )
1225
  next_btn = gr.Button(
1226
  "Next Word โ†’",
1227
- variant="secondary",
1228
- elem_classes="btn btn-primary"
1229
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1230
 
1231
- # Feedback
1232
- feedback_display = gr.Markdown("", elem_classes="card")
1233
 
1234
- # Finish Button
1235
- finish_btn = gr.Button(
1236
- "๐Ÿ† Finish Session",
1237
- variant="stop",
1238
- visible=False,
1239
- elem_classes="btn btn-primary"
1240
- )
1241
 
1242
- # Instructions
1243
- with gr.Accordion("๐Ÿ“š How to Play", open=False):
1244
- gr.Markdown("""
1245
- **How to Play:**
1246
- 1. Click "Hear Word" to listen to the pronunciation
1247
- 2. Type the word you hear in the input box
1248
- 3. Click "Check Answer" to see if you're correct
1249
- 4. Use "Get Hint" if you need help
1250
- 5. Click "Next Word" to continue
1251
-
1252
- **Scoring:**
1253
- - Correct on first try: 10 points
1254
- - Correct with hints: 5 points
1255
- - Build streaks for bonus points!
1256
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1257
 
1258
  # -----------------------------
1259
  # Event Handlers
@@ -1292,41 +1337,19 @@ with gr.Blocks(
1292
  ]
1293
  )
1294
 
1295
- # New session button
1296
- def on_new_session(voice_name):
1297
  if game_state.grade_file:
1298
  result = initialize_session(
1299
  game_state.grade_file,
1300
  game_state.mode,
1301
  game_state.user_name
1302
  )
1303
- handle_voice_change(voice_name)
1304
  streak = update_streak_display()
1305
  return result + (streak,)
1306
  ui_updates = update_ui()
1307
  streak = update_streak_display()
1308
- return ui_updates + (False, "Please select a grade level first") + (streak,)
1309
-
1310
- new_session_btn.click(
1311
- fn=on_new_session,
1312
- inputs=[voice_select],
1313
- outputs=[
1314
- word_display,
1315
- progress_bar,
1316
- progress_text,
1317
- attempts_display,
1318
- score_display,
1319
- hint_btn,
1320
- next_btn,
1321
- check_btn,
1322
- context_btn,
1323
- answer_input,
1324
- feedback_display,
1325
- gr.Checkbox(visible=False),
1326
- feedback_display,
1327
- streak_display
1328
- ]
1329
- )
1330
 
1331
  # Check answer
1332
  def on_check_answer(answer):
@@ -1483,11 +1506,6 @@ with gr.Blocks(
1483
  outputs=[performance_html]
1484
  )
1485
 
1486
- new_session_btn.click(
1487
- fn=update_dashboard,
1488
- outputs=[performance_html]
1489
- )
1490
-
1491
  # Finish button visibility
1492
  def update_finish_button():
1493
  return gr.update(visible=game_state.is_finished())
@@ -1534,7 +1552,7 @@ with gr.Blocks(
1534
  return streak
1535
 
1536
  # Add streak updates to all interactions
1537
- for btn in [check_btn, hint_btn, next_btn, start_session_btn, new_session_btn]:
1538
  btn.click(
1539
  fn=update_all_streaks,
1540
  outputs=[streak_display]
 
618
  <div class="stat-percent">success</div>
619
  </div>
620
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  </div>
622
  """
623
 
624
  return html
625
 
626
  # -----------------------------
627
+ # SIMPLE CSS - NO SIDEBAR, NO SCROLLING
628
  # -----------------------------
629
  css = """
630
+ /* Reset and basic styling */
631
  * {
632
  margin: 0;
633
  padding: 0;
634
  box-sizing: border-box;
635
  }
636
 
637
+ body, html {
 
638
  height: 100%;
639
  width: 100%;
640
+ overflow: hidden;
 
641
  }
642
 
643
  /* Main container */
644
  .gradio-container {
645
+ max-width: 1000px !important;
646
+ margin: 0 auto !important;
647
+ padding: 20px !important;
648
  height: 100vh !important;
649
+ overflow-y: auto !important;
 
 
 
 
 
 
650
  background: #f8fafc !important;
651
  }
652
 
653
+ /* Header */
654
+ .app-header {
655
+ text-align: center;
656
+ margin-bottom: 30px;
657
+ padding: 20px;
658
+ background: linear-gradient(135deg, #4f46e5, #6366f1);
659
+ border-radius: 16px;
660
+ color: white;
661
+ box-shadow: 0 4px 20px rgba(79, 70, 229, 0.2);
662
  }
663
 
664
+ .app-title {
665
+ font-size: 2.2rem;
666
+ font-weight: 800;
667
+ margin-bottom: 8px;
 
 
 
 
 
 
 
 
 
668
  }
669
 
670
+ .app-subtitle {
671
+ font-size: 1rem;
672
+ opacity: 0.9;
 
 
 
 
 
673
  }
674
 
675
+ /* Control Panel */
676
+ .control-panel {
677
+ background: white;
678
+ border-radius: 12px;
679
+ padding: 20px;
680
+ margin-bottom: 20px;
681
+ border: 1px solid #e2e8f0;
682
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
683
  }
684
 
685
+ .control-row {
686
+ display: flex;
687
+ gap: 15px;
688
+ margin-bottom: 15px;
689
+ flex-wrap: wrap;
690
  }
691
 
692
+ .control-item {
693
+ flex: 1;
694
+ min-width: 200px;
695
  }
696
 
697
+ /* Word Display Area */
698
+ .game-area {
699
+ background: white;
700
+ border-radius: 16px;
701
+ padding: 30px;
702
+ margin-bottom: 20px;
703
+ border: 1px solid #e2e8f0;
704
+ box-shadow: 0 4px 12px rgba(0,0,0,0.05);
705
+ text-align: center;
706
  }
707
 
 
708
  .word-display-container {
709
+ margin: 20px 0;
 
710
  }
711
 
712
  .word-display {
713
+ font-family: 'JetBrains Mono', monospace;
714
+ font-size: 3rem;
715
+ font-weight: 700;
716
+ letter-spacing: 0.3rem;
717
+ padding: 25px;
718
+ background: #f8fafc;
719
+ border: 3px solid #e2e8f0;
720
+ border-radius: 12px;
721
+ display: inline-block;
722
+ min-width: 300px;
723
+ margin: 10px 0;
724
  }
725
 
726
  .word-display.correct {
727
+ color: #10b981;
728
+ border-color: #10b981;
729
+ background: rgba(16, 185, 129, 0.05);
730
+ animation: pulse 2s infinite;
731
  }
732
 
733
  .word-display.incorrect {
734
+ color: #ef4444;
735
+ border-color: #ef4444;
736
+ background: rgba(239, 68, 68, 0.05);
737
+ }
738
+
739
+ @keyframes pulse {
740
+ 0%, 100% { opacity: 1; }
741
+ 50% { opacity: 0.8; }
742
  }
743
 
744
+ /* Stats Row */
745
  .stats-row {
746
+ display: flex;
747
+ gap: 15px;
748
+ margin: 20px 0;
749
  }
750
 
751
  .stat-box {
752
+ flex: 1;
753
+ background: white;
754
+ border: 1px solid #e2e8f0;
755
+ border-radius: 10px;
756
+ padding: 15px;
757
+ text-align: center;
758
+ box-shadow: 0 2px 6px rgba(0,0,0,0.03);
759
  }
760
 
761
  .stat-label {
762
+ font-size: 0.85rem;
763
+ color: #64748b;
764
+ margin-bottom: 5px;
765
+ font-weight: 600;
766
  }
767
 
768
  .stat-value {
769
+ font-size: 1.8rem;
770
+ font-weight: 800;
771
+ color: #4f46e5;
772
  }
773
 
774
+ /* Progress Bar */
775
+ .progress-container {
776
+ background: white;
777
+ border-radius: 10px;
778
+ padding: 15px;
779
+ margin: 15px 0;
780
+ border: 1px solid #e2e8f0;
 
781
  }
782
 
783
+ /* Audio Controls */
784
+ .audio-controls {
785
+ display: flex;
786
+ gap: 15px;
787
+ margin: 20px 0;
788
+ justify-content: center;
789
  }
790
 
791
+ .audio-btn {
792
+ display: flex;
793
+ flex-direction: column;
794
+ align-items: center;
795
+ justify-content: center;
796
+ gap: 8px;
797
+ padding: 15px 25px;
798
+ background: white;
799
+ border: 2px solid #e2e8f0;
800
+ border-radius: 10px;
801
+ cursor: pointer;
802
+ transition: all 0.2s;
803
+ min-width: 120px;
804
  }
805
 
806
+ .audio-btn:hover {
807
+ border-color: #4f46e5;
808
+ transform: translateY(-2px);
809
+ box-shadow: 0 4px 12px rgba(79, 70, 229, 0.1);
810
  }
811
 
812
+ .audio-icon {
813
+ font-size: 1.5rem;
 
 
814
  }
815
 
816
+ .audio-label {
817
+ font-size: 0.9rem;
818
+ font-weight: 600;
819
+ color: #1e293b;
 
820
  }
821
 
822
+ /* Answer Input */
823
+ .answer-container {
824
+ background: white;
825
+ border-radius: 12px;
826
+ padding: 25px;
827
+ margin: 20px 0;
828
+ border: 1px solid #e2e8f0;
829
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
830
  }
831
 
832
+ .answer-input {
833
+ width: 100%;
834
+ padding: 15px 20px;
835
+ font-size: 1.2rem;
836
+ border: 2px solid #e2e8f0;
837
+ border-radius: 10px;
838
+ transition: all 0.2s;
839
  }
840
 
841
+ .answer-input:focus {
842
+ outline: none;
843
+ border-color: #4f46e5;
844
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
845
  }
846
 
847
+ /* Action Buttons */
848
  .action-buttons {
849
+ display: flex;
850
+ gap: 15px;
851
+ margin: 25px 0;
852
+ }
853
+
854
+ .action-btn {
855
+ flex: 1;
856
+ padding: 16px 24px;
857
+ font-size: 1.1rem;
858
+ font-weight: 600;
859
+ border: none;
860
+ border-radius: 10px;
861
+ cursor: pointer;
862
+ transition: all 0.2s;
863
+ display: flex;
864
+ align-items: center;
865
+ justify-content: center;
866
+ gap: 10px;
867
+ }
868
+
869
+ .action-btn-primary {
870
+ background: linear-gradient(135deg, #4f46e5, #6366f1);
871
+ color: white;
872
+ }
873
+
874
+ .action-btn-primary:hover {
875
+ background: linear-gradient(135deg, #4338ca, #4f46e5);
876
+ transform: translateY(-2px);
877
+ box-shadow: 0 6px 20px rgba(79, 70, 229, 0.3);
878
+ }
879
+
880
+ .action-btn-success {
881
+ background: linear-gradient(135deg, #10b981, #34d399);
882
+ color: white;
883
  }
884
 
885
+ .action-btn-warning {
886
+ background: linear-gradient(135deg, #f59e0b, #fbbf24);
887
+ color: white;
888
+ }
889
+
890
+ .action-btn-secondary {
891
+ background: white;
892
+ color: #1e293b;
893
+ border: 2px solid #e2e8f0;
894
+ }
895
+
896
+ .action-btn-secondary:hover {
897
+ border-color: #4f46e5;
898
+ background: #f8fafc;
899
+ }
900
+
901
+ /* Performance Dashboard */
902
+ .performance-container {
903
+ background: white;
904
+ border-radius: 12px;
905
+ padding: 20px;
906
+ margin: 20px 0;
907
+ border: 1px solid #e2e8f0;
908
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
909
  }
910
 
 
911
  .performance-dashboard {
912
+ padding: 10px;
 
 
 
 
913
  }
914
 
915
  .dashboard-header {
916
+ display: flex;
917
+ align-items: center;
918
+ gap: 10px;
919
+ margin-bottom: 20px;
920
+ padding-bottom: 15px;
921
+ border-bottom: 2px solid #6366f1;
922
  }
923
 
924
  .header-text {
925
+ font-size: 1.3rem;
926
+ font-weight: 700;
927
+ color: #1e293b;
928
  }
929
 
930
  .stats-grid {
931
+ display: grid;
932
+ grid-template-columns: repeat(3, 1fr);
933
+ gap: 15px;
 
934
  }
935
 
936
  .stat-card {
937
+ background: #f8fafc;
938
+ border-radius: 10px;
939
+ padding: 15px;
940
+ text-align: center;
941
+ border: 1px solid #e2e8f0;
942
  }
943
 
944
  .stat-label-small {
945
+ font-size: 0.8rem;
946
+ color: #64748b;
947
+ margin-bottom: 5px;
948
+ font-weight: 600;
949
  }
950
 
951
  .stat-value-small {
952
+ font-size: 1.5rem;
953
+ font-weight: 800;
954
+ color: #4f46e5;
955
  }
956
 
957
+ /* Feedback */
958
+ .feedback-container {
959
+ background: white;
960
+ border-radius: 12px;
961
+ padding: 20px;
962
+ margin: 20px 0;
963
+ border: 1px solid #e2e8f0;
964
+ min-height: 80px;
965
  }
966
 
967
+ /* Instructions */
968
+ .instructions-container {
969
+ background: white;
970
+ border-radius: 12px;
971
+ padding: 20px;
972
+ margin: 20px 0;
973
+ border: 1px solid #e2e8f0;
974
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
 
975
  }
976
 
977
+ /* Responsive Design */
978
+ @media (max-width: 768px) {
979
+ .gradio-container {
980
+ padding: 10px !important;
981
+ }
982
+
983
+ .app-title {
984
+ font-size: 1.8rem;
985
+ }
986
+
987
+ .word-display {
988
+ font-size: 2.2rem;
989
+ min-width: 250px;
990
+ padding: 20px;
991
+ }
992
+
993
+ .control-row {
994
+ flex-direction: column;
995
+ }
996
+
997
+ .control-item {
998
+ min-width: 100%;
999
+ }
1000
+
1001
+ .audio-controls {
1002
+ flex-direction: column;
1003
+ }
1004
+
1005
+ .audio-btn {
1006
+ width: 100%;
1007
+ }
1008
+
1009
+ .action-buttons {
1010
+ flex-direction: column;
1011
+ }
1012
+
1013
+ .stats-grid {
1014
+ grid-template-columns: repeat(2, 1fr);
1015
+ }
1016
  }
1017
 
1018
+ @media (max-width: 480px) {
1019
+ .word-display {
1020
+ font-size: 1.8rem;
1021
+ min-width: 200px;
1022
+ padding: 15px;
1023
+ }
1024
+
1025
+ .stats-grid {
1026
+ grid-template-columns: 1fr;
1027
+ }
1028
+
1029
+ .stat-value {
1030
+ font-size: 1.5rem;
1031
+ }
1032
  }
1033
 
1034
+ /* Gradio overrides */
1035
+ .gr-textbox, .gr-dropdown, .gr-radio, .gr-slider, .gr-button {
1036
+ font-family: inherit !important;
 
1037
  }
1038
 
1039
+ .gr-button {
1040
+ font-weight: 600 !important;
 
 
1041
  }
1042
 
1043
+ .gr-form {
1044
+ max-height: none !important;
1045
+ }
1046
+
1047
+ /* Custom scrollbar */
1048
+ .gradio-container::-webkit-scrollbar {
1049
+ width: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
1050
  }
1051
 
1052
+ .gradio-container::-webkit-scrollbar-track {
1053
+ background: #f1f5f9;
1054
+ border-radius: 4px;
 
1055
  }
1056
 
1057
+ .gradio-container::-webkit-scrollbar-thumb {
1058
+ background: #cbd5e1;
1059
+ border-radius: 4px;
1060
  }
1061
 
1062
+ .gradio-container::-webkit-scrollbar-thumb:hover {
1063
+ background: #94a3b8;
 
 
1064
  }
1065
  """
1066
 
1067
  # -----------------------------
1068
+ # Gradio Interface - CLEAN SINGLE COLUMN LAYOUT
1069
  # -----------------------------
1070
  with gr.Blocks(
1071
  title="Spelling Trainer Pro",
 
1086
  )
1087
  ) as app:
1088
 
1089
+ # Main container - simple single column
1090
+ with gr.Column():
1091
 
1092
+ # Header
1093
+ with gr.Column(elem_classes="app-header"):
 
1094
  gr.HTML("""
1095
+ <h1 class="app-title">Spelling Trainer Pro</h1>
1096
+ <p class="app-subtitle">Master spelling skills with AI-powered practice</p>
 
 
 
 
 
1097
  """)
1098
+
1099
+ # Control Panel
1100
+ with gr.Column(elem_classes="control-panel"):
1101
+ with gr.Row(elem_classes="control-row"):
1102
+ with gr.Column(elem_classes="control-item"):
1103
+ user_name_input = gr.Textbox(
1104
+ label="๐Ÿ‘ค Your Name",
1105
+ placeholder="Enter your name...",
1106
+ value="",
1107
+ scale=2
1108
+ )
1109
+
1110
+ with gr.Column(elem_classes="control-item"):
1111
+ mode_select = gr.Radio(
1112
+ choices=["Learning (Ordered)", "Practice (Random)", "Sentence Practice", "Trini Mode", "Time Mode"],
1113
+ value="Learning (Ordered)",
1114
+ label="๐ŸŽฏ Training Mode",
1115
+ scale=2
1116
+ )
1117
+
1118
+ with gr.Column(elem_classes="control-item"):
1119
+ grade_select = gr.Dropdown(
1120
+ label="๐Ÿ“š Word List",
1121
+ choices=available_lists,
1122
+ value=default_list if default_list else None,
1123
+ scale=2
1124
+ )
 
 
 
 
1125
 
1126
  with gr.Row():
1127
+ with gr.Column(scale=2):
1128
+ voice_select = gr.Dropdown(
1129
+ choices=list(voice_options.keys()),
1130
+ value="๐Ÿ‘จโ€๐Ÿซ US Male (Guy)",
1131
+ label="๐ŸŽค Select Voice",
1132
+ scale=1
1133
+ )
1134
+
1135
+ with gr.Column(scale=1):
1136
+ start_session_btn = gr.Button(
1137
+ "๐Ÿš€ Start Session",
1138
+ variant="primary",
1139
+ size="lg",
1140
+ scale=1
1141
+ )
 
 
 
 
 
 
 
 
 
 
 
 
1142
 
1143
+ with gr.Row():
1144
+ with gr.Column(scale=1):
1145
+ test_voice_btn = gr.Button(
1146
+ "Test Voice",
1147
+ variant="secondary",
1148
+ scale=1
1149
+ )
1150
+ test_audio = gr.Audio(label="Test Audio", visible=True, scale=1)
1151
 
1152
+ # Game Area
1153
+ with gr.Column(elem_classes="game-area"):
 
 
 
 
 
 
 
1154
  # Progress
1155
+ with gr.Column(elem_classes="progress-container"):
1156
  progress_text = gr.Textbox(
1157
  label="",
1158
  value="Word 1 of 10",
1159
  interactive=False,
1160
+ elem_classes="stat-box"
1161
  )
1162
  progress_bar = gr.Slider(
1163
  minimum=0,
 
1198
  with gr.Row(elem_classes="audio-controls"):
1199
  hear_word_btn = gr.Button(
1200
  "๐Ÿ”Š Hear Word",
1201
+ elem_classes="audio-btn"
1202
  )
1203
  context_btn = gr.Button(
1204
  "๐ŸŽง Context",
1205
+ elem_classes="audio-btn"
1206
+ )
1207
+ spell_out_btn = gr.Button(
1208
+ "๐Ÿ”ค Spell Out",
1209
+ elem_classes="audio-btn",
1210
+ visible=False
1211
  )
1212
 
1213
  # Audio outputs
1214
  word_audio = gr.Audio(label="Word Audio", visible=True)
1215
  context_audio = gr.Audio(label="Context Audio", visible=True)
1216
+ spell_out_audio = gr.Audio(label="Spell Out Audio", visible=True)
1217
 
1218
  # Answer Input
1219
+ with gr.Column(elem_classes="answer-container"):
1220
  answer_input = gr.Textbox(
1221
  label="โœ๏ธ Type Your Answer",
1222
  placeholder="Type the word exactly as you hear it...",
1223
  lines=1,
1224
+ elem_classes="answer-input"
1225
  )
1226
 
1227
  # Action Buttons
1228
  with gr.Row(elem_classes="action-buttons"):
1229
  check_btn = gr.Button(
1230
  "โœ… Check Answer",
1231
+ elem_classes="action-btn action-btn-success"
 
1232
  )
1233
  hint_btn = gr.Button(
1234
  "๐Ÿ’ก Get Hint",
1235
+ elem_classes="action-btn action-btn-warning"
 
1236
  )
1237
  next_btn = gr.Button(
1238
  "Next Word โ†’",
1239
+ elem_classes="action-btn action-btn-primary"
 
1240
  )
1241
+
1242
+ # Performance Dashboard
1243
+ with gr.Column(elem_classes="performance-container"):
1244
+ performance_html = gr.HTML(
1245
+ value=generate_performance_html(),
1246
+ elem_classes="performance-dashboard"
1247
+ )
1248
+
1249
+ # Feedback Area
1250
+ with gr.Column(elem_classes="feedback-container"):
1251
+ feedback_display = gr.Markdown("")
1252
+
1253
+ # Finish Button
1254
+ finish_btn = gr.Button(
1255
+ "๐Ÿ† Finish Session",
1256
+ variant="primary",
1257
+ visible=False,
1258
+ elem_classes="action-btn action-btn-primary"
1259
+ )
1260
+
1261
+ # Instructions
1262
+ with gr.Accordion("๐Ÿ“š How to Play & Features", open=False, elem_classes="instructions-container"):
1263
+ gr.Markdown("""
1264
+ ## ๐ŸŽฏ Your Mission
1265
 
1266
+ **Spelling Trainer Pro** helps you master spelling through interactive learning:
 
1267
 
1268
+ 1. **๐ŸŽง Listen** - Click "Hear Word" to listen to the pronunciation
1269
+ 2. **โœ๏ธ Type** - Enter the word exactly as you hear it
1270
+ 3. **โœ… Check** - Get instant feedback on your spelling
1271
+ 4. **๐Ÿš€ Advance** - Move to the next word when correct
 
 
 
1272
 
1273
+ ## ๐Ÿ’ก Features
1274
+
1275
+ ### Audio Support
1276
+ - ๐Ÿ”Š **Hear Word**: Listen to pronunciation
1277
+ - ๐ŸŽง **Context**: Hear words in sentences (when available)
1278
+ - ๐Ÿ”ค **Spell Out**: Hear letters individually (enable in settings)
1279
+
1280
+ ### Learning Modes
1281
+ - ๐Ÿ“š **Learning (Ordered)**: Progressive learning
1282
+ - ๐ŸŽฒ **Practice (Random)**: Random word order
1283
+ - ๐Ÿ“ **Sentence Practice**: Full sentences with punctuation
1284
+ - ๐Ÿ‡น๐Ÿ‡น **Trini Mode**: Trinidad & Tobago words
1285
+ - โฐ **Time Mode**: Time-related vocabulary
1286
+
1287
+ ### Scoring System
1288
+ - โญ **Perfect First Try**: 10 points (earns bonus streak!)
1289
+ - ๐Ÿ’ก **With Hints**: 5 points (still a win!)
1290
+ - ๐Ÿ”ฅ **Streak Bonus**: Extra points for consecutive perfect words
1291
+
1292
+ ## ๐ŸŽฎ Tips for Success
1293
+
1294
+ 1. **Listen carefully** - Pay attention to pronunciation
1295
+ 2. **Use context clues** - When available, context sentences help
1296
+ 3. **Start with hints** - Use hints when stuck, but try first!
1297
+ 4. **Build streaks** - Consecutive perfect scores earn bonuses
1298
+ 5. **Practice regularly** - Consistency is key to improvement
1299
+
1300
+ **Made with โค๏ธ for young learners โ€ข Inspired by AMARI**
1301
+ """)
1302
 
1303
  # -----------------------------
1304
  # Event Handlers
 
1337
  ]
1338
  )
1339
 
1340
+ # New session button (reusing start session button)
1341
+ def on_new_session():
1342
  if game_state.grade_file:
1343
  result = initialize_session(
1344
  game_state.grade_file,
1345
  game_state.mode,
1346
  game_state.user_name
1347
  )
 
1348
  streak = update_streak_display()
1349
  return result + (streak,)
1350
  ui_updates = update_ui()
1351
  streak = update_streak_display()
1352
+ return ui_updates + (False, "Please select a word list first") + (streak,)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1353
 
1354
  # Check answer
1355
  def on_check_answer(answer):
 
1506
  outputs=[performance_html]
1507
  )
1508
 
 
 
 
 
 
1509
  # Finish button visibility
1510
  def update_finish_button():
1511
  return gr.update(visible=game_state.is_finished())
 
1552
  return streak
1553
 
1554
  # Add streak updates to all interactions
1555
+ for btn in [check_btn, hint_btn, next_btn, start_session_btn]:
1556
  btn.click(
1557
  fn=update_all_streaks,
1558
  outputs=[streak_display]