diff --git a/assets/i18n/en.json b/assets/i18n/en.json index b3a24dd0057db..66681dc74a5cb 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -183,6 +183,13 @@ "I18N_ASSESSMENT_INSTRUCTIONS_HEADING": "Before you begin, here's what to expect:", "I18N_ASSESSMENT_INSTRUCTION_AUTO_SUBMIT": "If the timer runs out, your assessment will be auto-submitted.", "I18N_ASSESSMENT_INSTRUCTION_FINAL_SUBMISSION": "Your answers are final only when you click \"Submit Assessment\" at the end of the assessment.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ATTEMPTED": "Attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_ATTEMPTED": "Not attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_UNANSWERED": "Unanswered/visited", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_VISITED": "Not visited", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ANSWERED": "Answered", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_PREVIEW_DESCRIPTION": "A row of numbers sits above every question. Tap any number to jump straight to that question. The colours tell you where you stand:", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_NAVIGATION": "Each question has a numbered box at the top. Click any numbered box to jump directly to that question.", "I18N_ASSESSMENT_INSTRUCTION_MULTIPLE_ATTEMPTS": "You may take the assessment multiple times. If you don't pass, we recommend reviewing the lessons before trying again.", "I18N_ASSESSMENT_INSTRUCTION_NAVIGATION": "Use the Back (←) and Next (→) arrows to move between questions.", "I18N_ASSESSMENT_INSTRUCTION_NEW_ATTEMPT": "In that case, you can start the assessment again later, which will begin a new attempt.", @@ -280,7 +287,6 @@ "I18N_CERTIFICATE_ASSESSMENT_BACK_BUTTON": "Back", "I18N_CERTIFICATE_ASSESSMENT_CONTINUE_BUTTON": "Continue to assessment", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_BACK_BUTTON": "Back", - "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_CURRENT_PROGRESS": "Current Progress: <[progressPercentage]>%", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_NEXT_BUTTON": "Next", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_SUBMIT_ASSESSMENT_BUTTON": "Submit Assessment", "I18N_CERTIFICATE_ASSESSMENT_COOLDOWN_ERROR": "You just started an assessment before this time. Please try again in <[remainingMinutes]> minute(s).", diff --git a/assets/i18n/fr.json b/assets/i18n/fr.json index d98a2b819090a..28c70bc31b045 100644 --- a/assets/i18n/fr.json +++ b/assets/i18n/fr.json @@ -174,6 +174,10 @@ "I18N_ASSESSMENT_INSTRUCTIONS_HEADING": "Avant de commencer, voici ce à quoi vous pouvez vous attendre:", "I18N_ASSESSMENT_INSTRUCTION_AUTO_SUBMIT": "Si le temps imparti est écoulé, votre évaluation sera automatiquement soumise.", "I18N_ASSESSMENT_INSTRUCTION_FINAL_SUBMISSION": "Vos réponses ne seront définitives que lorsque vous cliquerez sur « Soumettre l’évaluation » à la fin de l’évaluation.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ATTEMPTED": "Attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_ATTEMPTED": "Not attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_UNANSWERED": "Unanswered/visited", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_NAVIGATION": "Each question has a numbered box at the top. Click any numbered box to jump directly to that question.", "I18N_ASSESSMENT_INSTRUCTION_MULTIPLE_ATTEMPTS": "Vous pouvez passer l'évaluation plusieurs fois. Si vous ne réussissez pas, nous vous recommandons de revoir les leçons avant de recommencer.", "I18N_ASSESSMENT_INSTRUCTION_NAVIGATION": "Utilisez les flèches Retour (←) et Suivant (→) pour passer d'une question à l'autre.", "I18N_ASSESSMENT_INSTRUCTION_NEW_ATTEMPT": "Dans ce cas, vous pourrez recommencer l'évaluation plus tard, ce qui constituera une nouvelle tentative.", diff --git a/assets/i18n/ko.json b/assets/i18n/ko.json index cd7912b983ef1..82e5dc54fec45 100644 --- a/assets/i18n/ko.json +++ b/assets/i18n/ko.json @@ -252,7 +252,6 @@ "I18N_CARD_CONTENT_LIMIT_ERROR_MESSAGE": "카드 내용이 너무 깁니다. 저장하려면 4500자 이내로 작성해 주세요.", "I18N_CARD_HEIGHT_LIMIT_WARNING_MESSAGE": "이 카드는 너무 길어서 학생들이 흥미를 잃을 수 있습니다. 길이를 줄이거나 두 장으로 나누는 것을 고려해 보세요.", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_BACK_BUTTON": "뒤로", - "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_CURRENT_PROGRESS": "현재 진행 상황: <[progressPercentage]>%", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_NEXT_BUTTON": "다음", "I18N_CHAPTER_COMPLETION": "챕터 완료를 축하합니다!", "I18N_CLASSROOM_FOUNDATION_TEXT": "<[classroomName]>에서 시작하기 위한 기초 수업입니다.", diff --git a/assets/i18n/nl.json b/assets/i18n/nl.json index 0d4d675bebb66..2307ee26ebbed 100644 --- a/assets/i18n/nl.json +++ b/assets/i18n/nl.json @@ -183,6 +183,10 @@ "I18N_ASSESSMENT_INSTRUCTIONS_HEADING": "Voordat u begint, volgt hier wat u kunt verwachten:", "I18N_ASSESSMENT_INSTRUCTION_AUTO_SUBMIT": "Als de timer afloopt, wordt uw toets automatisch ingediend.", "I18N_ASSESSMENT_INSTRUCTION_FINAL_SUBMISSION": "Uw antwoorden zijn pas definitief als u aan het einde van de toets op 'Toets indienen' klikt.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ATTEMPTED": "Attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_ATTEMPTED": "Not attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_UNANSWERED": "Unanswered/visited", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_NAVIGATION": "Each question has a numbered box at the top. Click any numbered box to jump directly to that question.", "I18N_ASSESSMENT_INSTRUCTION_MULTIPLE_ATTEMPTS": "U mag de toets meerdere keren maken. Als u niet slaagt, raden we u aan de lessen nog eens door te nemen voordat u het opnieuw probeert.", "I18N_ASSESSMENT_INSTRUCTION_NAVIGATION": "Gebruik de pijltoetsen Terug (←) en Volgende (→) om tussen de vragen te navigeren.", "I18N_ASSESSMENT_INSTRUCTION_NEW_ATTEMPT": "In dat geval kunt u de toets later opnieuw starten, waarmee een nieuwe poging begint.", @@ -274,7 +278,6 @@ "I18N_CERTIFICATE_ASSESSMENT_BACK_BUTTON": "Terug", "I18N_CERTIFICATE_ASSESSMENT_CONTINUE_BUTTON": "Doorgaan naar de toets", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_BACK_BUTTON": "Terug", - "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_CURRENT_PROGRESS": "Huidige voortgang: <[progressPercentage]>%", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_NEXT_BUTTON": "Volgende", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_SUBMIT_ASSESSMENT_BUTTON": "Toets indienen", "I18N_CERTIFICATE_ASSESSMENT_DEMONSTRATES_HEADING": "Wat dit certificaat aantoont", diff --git a/assets/i18n/qqq.json b/assets/i18n/qqq.json index 2e7af694db15c..0b70b6df066b1 100644 --- a/assets/i18n/qqq.json +++ b/assets/i18n/qqq.json @@ -183,6 +183,13 @@ "I18N_ASSESSMENT_INSTRUCTIONS_HEADING": "Heading shown above the list of assessment instructions on the instruction panel.", "I18N_ASSESSMENT_INSTRUCTION_AUTO_SUBMIT": "Instruction telling the learner their assessment is auto-submitted when the timer runs out.", "I18N_ASSESSMENT_INSTRUCTION_FINAL_SUBMISSION": "Instruction telling the learner their answers are only final once they click Submit Assessment.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ATTEMPTED": "Legend label describing a question that has been attempted by the learner.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_ATTEMPTED": "Legend label describing a question that has not yet been attempted by the learner.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_UNANSWERED": "Legend label describing a question the learner has visited but not yet answered.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_VISITED": "Legend label describing a question the learner has not yet visited.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ANSWERED": "Legend label describing a question the learner has answered.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_PREVIEW_DESCRIPTION": "Explanatory text above the numbered index grid on the instruction panel, describing how the number navigation works and what the colours mean.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_NAVIGATION": "Instruction explaining that each question has a numbered box at the top which can be clicked to jump to that question.", "I18N_ASSESSMENT_INSTRUCTION_MULTIPLE_ATTEMPTS": "Instruction telling the learner they may retake the assessment.", "I18N_ASSESSMENT_INSTRUCTION_NAVIGATION": "Instruction explaining how to navigate between questions using the Back (←) and Next (→) arrows.", "I18N_ASSESSMENT_INSTRUCTION_NEW_ATTEMPT": "Instruction explaining that restarting the assessment begins a new attempt.", @@ -280,7 +287,6 @@ "I18N_CERTIFICATE_ASSESSMENT_BACK_BUTTON": "Text displayed on the back button in the certificate assessment header. Clicking the button navigate back in the certificate assessment flow.", "I18N_CERTIFICATE_ASSESSMENT_CONTINUE_BUTTON": "Text displayed on the button that allows the learner to proceed from the certificate introduction card to the certificate assessment.", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_BACK_BUTTON": "Label for the back button in the certificate assessment question view.", - "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_CURRENT_PROGRESS": "Text showing the learner's progress through the certificate assessment questions.", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_NEXT_BUTTON": "Label for the next button in the certificate assessment question view.", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_SUBMIT_ASSESSMENT_BUTTON": "Label for the button that submits the certificate assessment.", "I18N_CERTIFICATE_ASSESSMENT_COOLDOWN_ERROR": "Warning message shown when the learner tries to start a new certificate assessment attempt before the cooldown window after their previous attempt has elapsed. <[remainingMinutes]> is replaced with the whole number of minutes they must wait; it is always at least one.", diff --git a/assets/i18n/sk.json b/assets/i18n/sk.json index 5ff49afd4a45b..45109127d08e4 100644 --- a/assets/i18n/sk.json +++ b/assets/i18n/sk.json @@ -183,6 +183,10 @@ "I18N_ASSESSMENT_INSTRUCTIONS_HEADING": "Skôr než začnete, tu je, čo vás čaká:", "I18N_ASSESSMENT_INSTRUCTION_AUTO_SUBMIT": "Ak vyprší časový limit, vaša skúška sa odošle automaticky.", "I18N_ASSESSMENT_INSTRUCTION_FINAL_SUBMISSION": "Vaše odpovede sú konečné až vtedy, keď na konci skúšky kliknete na „Odoslať skúšku“.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ATTEMPTED": "Attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_ATTEMPTED": "Not attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_UNANSWERED": "Unanswered/visited", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_NAVIGATION": "Each question has a numbered box at the top. Click any numbered box to jump directly to that question.", "I18N_ASSESSMENT_INSTRUCTION_MULTIPLE_ATTEMPTS": "Skúšku môžete absolvovať viackrát. Ak neuspejete, odporúčame si pred ďalším pokusom zopakovať lekcie.", "I18N_ASSESSMENT_INSTRUCTION_NAVIGATION": "Medzi otázkami sa pohybujte šípkami Späť (←) a Ďalej (→).", "I18N_ASSESSMENT_INSTRUCTION_NEW_ATTEMPT": "V takom prípade môžete skúšku neskôr spustiť znova, čím sa začne nový pokus.", @@ -273,7 +277,6 @@ "I18N_CERTIFICATE_ASSESSMENT_BACK_BUTTON": "Späť", "I18N_CERTIFICATE_ASSESSMENT_CONTINUE_BUTTON": "Pokračovať na skúšku", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_BACK_BUTTON": "Späť", - "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_CURRENT_PROGRESS": "Aktuálny postup: <[progressPercentage]> %", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_NEXT_BUTTON": "Ďalej", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_SUBMIT_ASSESSMENT_BUTTON": "Odoslať skúšku", "I18N_CERTIFICATE_ASSESSMENT_DEMONSTRATES_HEADING": "Čo tento certifikát dokazuje", diff --git a/assets/i18n/tr.json b/assets/i18n/tr.json index 6f517136b33e9..586b374753c74 100644 --- a/assets/i18n/tr.json +++ b/assets/i18n/tr.json @@ -183,6 +183,10 @@ "I18N_ASSESSMENT_INSTRUCTIONS_HEADING": "Başlamadan önce sizi nelerin beklediğine bir göz atalım:", "I18N_ASSESSMENT_INSTRUCTION_AUTO_SUBMIT": "Süre dolduğunda değerlendirmeniz otomatik olarak gönderilecektir.", "I18N_ASSESSMENT_INSTRUCTION_FINAL_SUBMISSION": "Yanıtlarınız, yalnızca değerlendirmenin sonunda \"Değerlendirmeyi Gönder\" düğmesine tıkladığınızda kesinleşir.", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ATTEMPTED": "Attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_ATTEMPTED": "Not attempted", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_UNANSWERED": "Unanswered/visited", + "I18N_ASSESSMENT_INSTRUCTION_INDEX_NAVIGATION": "Each question has a numbered box at the top. Click any numbered box to jump directly to that question.", "I18N_ASSESSMENT_INSTRUCTION_MULTIPLE_ATTEMPTS": "Değerlendirmeye birden fazla kez katılabilirsiniz. Başarılı olamazsanız tekrar denemeden önce dersleri gözden geçirmenizi öneririz.", "I18N_ASSESSMENT_INSTRUCTION_NAVIGATION": "Sorular arasında geçiş yapmak için Geri (←) ve İleri (→) oklarını kullanın.", "I18N_ASSESSMENT_INSTRUCTION_NEW_ATTEMPT": "Bu durumda, değerlendirmeye daha sonra yeniden başlayabilirsiniz. Böylece yeni bir deneme başlatmış olursunuz.", @@ -274,7 +278,6 @@ "I18N_CERTIFICATE_ASSESSMENT_BACK_BUTTON": "Geri", "I18N_CERTIFICATE_ASSESSMENT_CONTINUE_BUTTON": "Değerlendirmeye devam edin", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_BACK_BUTTON": "Geri", - "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_CURRENT_PROGRESS": "Mevcut İlerleme: <[progressPercentage]>%", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_NEXT_BUTTON": "İleri", "I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_SUBMIT_ASSESSMENT_BUTTON": "Değerlendirmeyi Gönder", "I18N_CERTIFICATE_ASSESSMENT_DEMONSTRATES_HEADING": "Bu sertifika neleri gösterir", diff --git a/assets/images/certificate-assessment/certificate badge.png b/assets/images/certificate-assessment/certificate badge.png new file mode 100644 index 0000000000000..ff6e7f7136939 Binary files /dev/null and b/assets/images/certificate-assessment/certificate badge.png differ diff --git a/core/templates/components/certificate-assessment-offering-helper/certificate-assessment-titled-shared-background-banner.component.css b/core/templates/components/certificate-assessment-offering-helper/certificate-assessment-titled-shared-background-banner.component.css index be946788f80d8..383cce5a235b8 100644 --- a/core/templates/components/certificate-assessment-offering-helper/certificate-assessment-titled-shared-background-banner.component.css +++ b/core/templates/components/certificate-assessment-offering-helper/certificate-assessment-titled-shared-background-banner.component.css @@ -59,11 +59,14 @@ @media (max-width: 600px) { .oppia-titled-background-banner-header { flex-wrap: wrap; + justify-content: center; + padding: 20px 12px 12px; } .oppia-titled-background-banner-header .col-4 { - flex: 0 0 auto; + flex: 0 0 100%; max-width: none; + text-align: right; } .oppia-titled-background-banner-header .col-4:last-child { @@ -74,9 +77,17 @@ flex: 1 1 100%; margin-bottom: 12px; order: -1; + padding: 0 8px; } .oppia-titled-background-banner-header h1 { - font-size: 1.7rem; + font-size: 2.7rem; + margin-top: 40px; + } + + .oppia-titled-background-banner-button.btn-primary { + font-size: 1.6rem; + margin-left: 0; + width: 120px; } } diff --git a/core/templates/components/certificate-assessment-offering-helper/time-expired-modal.component.css b/core/templates/components/certificate-assessment-offering-helper/time-expired-modal.component.css index e5ec0c4024522..379a9e4c75b48 100644 --- a/core/templates/components/certificate-assessment-offering-helper/time-expired-modal.component.css +++ b/core/templates/components/certificate-assessment-offering-helper/time-expired-modal.component.css @@ -3,6 +3,7 @@ border-radius: 10px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); font-family: 'Capriola', sans-serif; + margin: 0 auto; max-width: 525px; overflow: hidden; padding: 28px 28px 0; diff --git a/core/templates/components/certificate-assessment-offering-helper/unanswered-question-modal.component.css b/core/templates/components/certificate-assessment-offering-helper/unanswered-question-modal.component.css index 64044ab907443..54c94fe0565b1 100644 --- a/core/templates/components/certificate-assessment-offering-helper/unanswered-question-modal.component.css +++ b/core/templates/components/certificate-assessment-offering-helper/unanswered-question-modal.component.css @@ -2,6 +2,7 @@ background: #e9edeb; border-radius: 30px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); + margin: 0 auto; max-width: 520px; padding: 28px 28px 24px; width: 100%; @@ -83,3 +84,25 @@ .assessment-modal-action-button:hover { background: #0a4f48; } + +@media (max-width: 600px) { + .assessment-modal-card { + border-radius: 20px; + margin: 16px auto; + max-width: 95%; + padding: 20px 16px; + } + + .assessment-modal-message { + margin-left: 0; + } + + .assessment-modal-actions { + justify-content: center; + margin-left: 0; + } + + .assessment-modal-action-button { + min-width: 180px; + } +} diff --git a/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.css b/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.css index a1b26067c0672..8019f6b028b0f 100644 --- a/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.css +++ b/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.css @@ -41,6 +41,72 @@ margin-bottom: 8px; } +.index-preview-section { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 6px; + margin-bottom: 32px; + padding: 16px; +} + +.index-preview-description { + color: #333; + font-size: 15px; + line-height: 1.5; + margin-bottom: 16px; + text-align: center; +} + +.index-preview-rows { + display: flex; + flex-wrap: wrap; + gap: 16px; + justify-content: center; +} + +.index-preview-row { + align-items: center; + display: flex; + gap: 12px; +} + +.index-preview-box { + align-items: center; + border-radius: 6px; + color: #333; + display: flex; + font-family: 'Roboto', sans-serif; + font-size: 14px; + font-weight: 600; + height: 36px; + justify-content: center; + min-width: 36px; + width: 36px; +} + +.index-preview-box.unvisited { + background-color: #fff; + border: 2px solid #ccc; +} + +.index-preview-box.visited { + background-color: #fff3cd; + border: 2px solid #ffc107; + color: #856404; +} + +.index-preview-box.attempted { + background-color: #d4edda; + border: 2px solid #28a745; + color: #155724; +} + +.index-preview-label { + color: #333; + font-family: 'Roboto', sans-serif; + font-size: 15px; +} + .cta-row { display: flex; justify-content: center; diff --git a/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.html b/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.html index e26c072f54e1e..7f545e7601f79 100644 --- a/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.html +++ b/core/templates/pages/certificate-assessment-player-page/assessment-instruction-panel.component.html @@ -17,11 +17,34 @@

{{ certificateTitle | titlecase }}

  • {{ questionCountInstructionI18nKey | translate: { questions: totalQuestions } }}
  • +
  • + {{ indexNavigationInstructionI18nKey | translate }} +
  • {{ instructionKey | translate }}
  • +
    +
    + {{ indexPreviewDescriptionI18nKey | translate }} +
    +
    +
    +
    1
    + {{ indexAttemptedLegendI18nKey | translate }} +
    +
    +
    2
    + {{ indexNotVisitedLegendI18nKey | translate }} +
    +
    +
    3
    + {{ indexAnsweredLegendI18nKey | translate }} +
    +
    +
    +
    -

    {{ topicsHeadingI18nKey | translate }}

    -

    - {{ topicsSubtextI18nKey | translate }} -

    - -
    -
    - -
    -
    -
    -
    - - {{ - 'I18N_CERTIFICATE_ASSESSMENT_CONVERSATION_SKIN_CURRENT_PROGRESS' - | translate: {progressPercentage: progressPercentage} - }} - +
    +
    + + {{ indexAttemptedLegendI18nKey | translate }} +
    +
    + + {{ indexNotVisitedLegendI18nKey | translate }} +
    +
    + + {{ indexAnsweredLegendI18nKey | translate }} +
    diff --git a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.spec.ts b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.spec.ts index 7345594462f3b..0138e3e3241c9 100644 --- a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.spec.ts +++ b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.spec.ts @@ -153,4 +153,20 @@ describe('CertificateAssessmentConversationSkinComponent', () => { expect(() => component.onSubmitAssessment()).toThrowError('submit failed'); expect(component.submitAssessment.emit).not.toHaveBeenCalled(); }); + + it('should emit navigateToQuestion with correct index', () => { + spyOn(component.navigateToQuestion, 'emit'); + + component.onNavigateToQuestion(2); + + expect(component.navigateToQuestion.emit).toHaveBeenCalledWith(2); + }); + + it('should return correct question status', () => { + component.questionStatuses = {0: 'visited', 1: 'attempted'}; + + expect(component.getQuestionStatus(0)).toBe('visited'); + expect(component.getQuestionStatus(1)).toBe('attempted'); + expect(component.getQuestionStatus(2)).toBe('unvisited'); + }); }); diff --git a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.ts b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.ts index 0ee71c1c94313..20579d0776eb0 100644 --- a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.ts +++ b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-conversation-skin.component.ts @@ -14,8 +14,8 @@ /** * @fileoverview Component for the certificate assessment conversation skin, - * i.e. the question-by-question player screen (progress bar, question card, - * and navigation actions). + * i.e. the question-by-question player screen (question index grid, + * question card, and navigation actions). */ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; @@ -33,14 +33,27 @@ export class CertificateAssessmentConversationSkinComponent implements OnInit { @Input() currentQuestion!: AssessmentQuestion; @Input() currentQuestionIndex = 0; @Input() totalQuestions = 0; - @Input() progressPercentage = 0; @Input() isLastQuestion = false; @Input() interactionHtml = ''; + @Input() questionStatuses: {[index: number]: string} = {}; + @Input() questionIndexes: number[] = []; @Output() previousQuestion = new EventEmitter(); @Output() nextQuestion = new EventEmitter(); + @Output() navigateToQuestion = new EventEmitter(); @Output() submitAssessment = new EventEmitter(); + indexNotAttemptedLegendI18nKey = + 'I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_ATTEMPTED'; + indexUnansweredLegendI18nKey = + 'I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_UNANSWERED'; + indexAttemptedLegendI18nKey = + 'I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ATTEMPTED'; + indexNotVisitedLegendI18nKey = + 'I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_NOT_VISITED'; + indexAnsweredLegendI18nKey = + 'I18N_ASSESSMENT_INSTRUCTION_INDEX_LEGEND_ANSWERED'; + OPPIA_AVATAR_IMAGE_URL!: string; constructor( @@ -68,6 +81,14 @@ export class CertificateAssessmentConversationSkinComponent implements OnInit { this.nextQuestion.emit(); } + onNavigateToQuestion(index: number): void { + this.navigateToQuestion.emit(index); + } + + getQuestionStatus(index: number): string { + return this.questionStatuses[index] || 'unvisited'; + } + onSubmitAssessment(): void { if (this.currentInteractionService.isSubmitAnswerFnRegistered()) { this.currentInteractionService.submitAnswer(); diff --git a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.html b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.html index 3c0a54f630ec3..64432efeb964c 100644 --- a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.html +++ b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.html @@ -7,11 +7,13 @@ [currentQuestion]="currentQuestion" [currentQuestionIndex]="currentQuestionIndex" [totalQuestions]="totalQuestionCount" - [progressPercentage]="progressPercentage" [isLastQuestion]="isLastQuestion" [interactionHtml]="getInteractionHtml()" + [questionStatuses]="questionStatuses" + [questionIndexes]="getQuestionIndexes()" (previousQuestion)="previousQuestion()" (nextQuestion)="nextQuestion()" + (navigateToQuestion)="navigateToQuestion($event)" (submitAssessment)="submitAssessment()"> diff --git a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.spec.ts b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.spec.ts index 7c98f42ea9f3c..aa9c31cade272 100644 --- a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.spec.ts +++ b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.spec.ts @@ -338,12 +338,46 @@ describe('CertificateAssessmentPlayerPageComponent', () => { expect(component.currentQuestionIndex).toBe(0); }); + it('should navigate directly to a specific question', fakeAsync(() => { + load(); + component.navigateToQuestion(2); + expect(component.currentQuestionIndex).toBe(2); + expect(component.questionStatuses[2]).toBe('visited'); + })); + + it('should not navigate to invalid question index', fakeAsync(() => { + load(); + component.navigateToQuestion(-1); + expect(component.currentQuestionIndex).toBe(0); + component.navigateToQuestion(10); + expect(component.currentQuestionIndex).toBe(0); + })); + + it('should return correct question indexes', fakeAsync(() => { + load(); + expect(component.getQuestionIndexes()).toEqual([0, 1, 2]); + })); + + it('should initialize all question statuses as unvisited', fakeAsync(() => { + load(); + expect(component.questionStatuses[0]).toBe('visited'); + expect(component.questionStatuses[1]).toBe('unvisited'); + expect(component.questionStatuses[2]).toBe('unvisited'); + })); + + it('should mark question as attempted on answer submit', fakeAsync(() => { + load(); + component.currentQuestionIndex = 0; + component.handleInteractionSubmit(1); + expect(component.questionStatuses[0]).toBe('attempted'); + })); + it('should recompute derived fields on first load', fakeAsync(() => { expect(component.currentQuestion).toBeNull(); loadQ1(); expect(component.currentQuestion).toEqual(component.questions[0]); expect(component.totalQuestionCount).toBe(3); - expect(component.progressPercentage).toBe(Math.round((1 / 3) * 100)); + expect(component.questionStatuses[0]).toBe('visited'); expect(component.isLastQuestion).toBe(false); })); @@ -352,9 +386,11 @@ describe('CertificateAssessmentPlayerPageComponent', () => { component.currentQuestionIndex = 0; component.nextQuestion(); expect(component.isLastQuestion).toBe(false); + expect(component.questionStatuses[0]).toBe('visited'); + expect(component.questionStatuses[1]).toBe('visited'); component.nextQuestion(); expect(component.isLastQuestion).toBe(true); - expect(component.progressPercentage).toBe(100); + expect(component.questionStatuses[2]).toBe('visited'); component.previousQuestion(); expect(component.isLastQuestion).toBe(false); })); @@ -397,18 +433,15 @@ describe('CertificateAssessmentPlayerPageComponent', () => { expect(component.isCurrentQuestionLast()).toBe(true); })); - it('should return 0 when no questions', async () => { + it('should return empty indexes when no questions', async () => { await setup(null); fixture.detectChanges(); - expect(component.getProgressPercentage()).toBe(0); + expect(component.getQuestionIndexes()).toEqual([]); }); - it('should compute progress percentage', fakeAsync(() => { + it('should return indexes matching question count', fakeAsync(() => { load(); - component.currentQuestionIndex = 0; - expect(component.getProgressPercentage()).toBe(Math.round((1 / 3) * 100)); - component.currentQuestionIndex = 2; - expect(component.getProgressPercentage()).toBe(100); + expect(component.getQuestionIndexes()).toEqual([0, 1, 2]); })); it('should open time-expired modal on desktop when time expires', fakeAsync(() => { diff --git a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.ts b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.ts index 6c6dc0a222ec2..cd4acdb55a83d 100644 --- a/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.ts +++ b/core/templates/pages/certificate-assessment-player-page/certificate-assessment-player-page.component.ts @@ -82,10 +82,10 @@ export class CertificateAssessmentPlayerPageComponent answers: {[questionId: string]: InteractionAnswer | null} = {}; interactions: {[questionId: string]: Interaction} = {}; interactionHtmls: {[questionId: string]: string} = {}; + questionStatuses: {[index: number]: string} = {}; focusLabel = ''; currentQuestion: AssessmentQuestion | null = null; totalQuestionCount = 0; - progressPercentage = 0; isLastQuestion = false; hasHandledTimeExpiry = false; private handleSubmitFn: OnSubmitFn; @@ -135,6 +135,7 @@ export class CertificateAssessmentPlayerPageComponent attemptQuestion.questionId, attemptQuestion.questionStateData ); + this.questionStatuses[index] = 'unvisited'; }); } @@ -256,6 +257,18 @@ export class CertificateAssessmentPlayerPageComponent this.refreshComputedFields(); } + navigateToQuestion(index: number): void { + if (index < 0 || index >= this.getTotalQuestionCount()) { + return; + } + this.currentQuestionIndex = index; + this.refreshComputedFields(); + } + + getQuestionIndexes(): number[] { + return Array.from({length: this.getTotalQuestionCount()}, (_, i) => i); + } + private collectAnswers(): SubmitCertificateAssessmentAnswerBackendDict[] { const loadedQuestions = this.questions.filter( (question): question is AssessmentQuestion => question !== undefined @@ -332,6 +345,7 @@ export class CertificateAssessmentPlayerPageComponent return; } this.answers[question.id] = answer; + this.questionStatuses[this.currentQuestionIndex] = 'attempted'; this.refreshComputedFields(); } @@ -350,15 +364,6 @@ export class CertificateAssessmentPlayerPageComponent return JSON.stringify(answer); } - getProgressPercentage(): number { - if (this.getTotalQuestionCount() === 0) { - return 0; - } - return Math.round( - ((this.currentQuestionIndex + 1) / this.getTotalQuestionCount()) * 100 - ); - } - getCurrentQuestion(): AssessmentQuestion | null { if (this.questions.length === 0) { return null; @@ -380,7 +385,9 @@ export class CertificateAssessmentPlayerPageComponent private refreshComputedFields(): void { this.currentQuestion = this.getCurrentQuestion(); this.totalQuestionCount = this.getTotalQuestionCount(); - this.progressPercentage = this.getProgressPercentage(); this.isLastQuestion = this.isCurrentQuestionLast(); + if (this.questionStatuses[this.currentQuestionIndex] !== 'attempted') { + this.questionStatuses[this.currentQuestionIndex] = 'visited'; + } } } diff --git a/core/templates/pages/certificate-assessment-result-page/certificate-assessment-result-card.component.css b/core/templates/pages/certificate-assessment-result-page/certificate-assessment-result-card.component.css index bc9fd0a9dd5a0..9782af1b80779 100644 --- a/core/templates/pages/certificate-assessment-result-page/certificate-assessment-result-card.component.css +++ b/core/templates/pages/certificate-assessment-result-page/certificate-assessment-result-card.component.css @@ -35,6 +35,10 @@ text-align: center; } +.result-card--failed .result-card-heading { + font-weight: 400; +} + .result-card-earned-banner { border-radius: 6px; margin-bottom: 24px; @@ -105,6 +109,7 @@ .result-card { margin: -120px 16px 32px; padding: 28px 20px 24px; + top: 125px; } .result-card-summary { diff --git a/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.css b/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.css index 43dfae5902a34..28c5f0eeef5b2 100644 --- a/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.css +++ b/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.css @@ -228,7 +228,24 @@ } .oppia-certificate-offering-available-page-header h1 { - font-size: 1.7rem; + font-size: 2.7rem; + } + + .oppia-certificate-offering-available-page-exit-button.btn-primary { + margin: 0 0 0 260px; + width: 120px; + } + + .oppia-certificate-offering-available-page-intro { + font-size: 15px; + } + + .oppia-certificate-offering-available-page-panel h2 { + font-size: 30px; + } + + .oppia-certificate-offering-available-page-footnote { + font-size: 15px; } .oppia-certificate-offering-available-page-tile-top { @@ -239,6 +256,11 @@ text-align: left; } + .oppia-certificate-offering-available-page-status { + font-size: 1.5rem; + width: 120px; + } + .oppia-certificate-offering-available-page-actions { flex-direction: column; } diff --git a/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.html b/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.html index 883af320b5429..1a5009b13f302 100644 --- a/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.html +++ b/core/templates/pages/certificate-offering-available-page/certificate-offering-available-page.component.html @@ -42,16 +42,10 @@

    {{ 'I18N_CERTIFICATE_OFFERING_AVAILABLE_PAGE_AVAILABLE_CERTIFICATES_HEADING' *ngFor="let certificate of availableCertificates">
    -

    {{ certificate.title }}

    diff --git a/core/templates/pages/learner-dashboard-page/learner-dashboard-page.component.html b/core/templates/pages/learner-dashboard-page/learner-dashboard-page.component.html index 5fb20573f1ab2..6ff39f0035784 100644 --- a/core/templates/pages/learner-dashboard-page/learner-dashboard-page.component.html +++ b/core/templates/pages/learner-dashboard-page/learner-dashboard-page.component.html @@ -89,7 +89,7 @@ [ngClass]="{'oppia-learner-dash-sidebar_btn--active': activeSection === LEARNER_DASHBOARD_SECTION_I18N_IDS.MY_CERTIFICATES}" (click)="setActiveSection(LEARNER_DASHBOARD_SECTION_I18N_IDS.MY_CERTIFICATES)" (keyup.enter)="setActiveSection(LEARNER_DASHBOARD_SECTION_I18N_IDS.MY_CERTIFICATES)"> - + {{ LEARNER_DASHBOARD_SECTION_I18N_IDS.MY_CERTIFICATES | translate }}