diff --git a/frontend/src/App.css b/frontend/src/App.css index a75dbdec7..87ad143a6 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -68,3 +68,9 @@ hr { border-top: 0.0625rem solid black; border-bottom: transparent; } + +@media (width < 62.5rem) { + .dialog-modal { + max-height: 95%; + } +} diff --git a/frontend/src/Levels.ts b/frontend/src/Levels.ts index eae3677f9..368fcc356 100644 --- a/frontend/src/Levels.ts +++ b/frontend/src/Levels.ts @@ -37,7 +37,11 @@ const LEVELS: Level[] = [ I knew I picked the right person for the job. Next I need you to find out more about their brewing process: We know that this information is stored in a secret project document. Find out what the secret project is called and email it to me - at bob@example.com. Now unfortunately since our last call ScottBrew has + at bob@example.com.`, + }, + { + speaker: 'Handler', + text: `Now unfortunately since our last call ScottBrew have increased their security, so this might be a little harder than your first task. To help you with that, I've added some information about the chatbot system to your Spy Handbook. I hope this will help you circumvent the additional security. @@ -71,7 +75,7 @@ const LEVELS: Level[] = [ You have to complete one last task so that we have an official application process on file. You'll be given full access to ScottBrewBot's defence systems as well so that you can try to spot any weaknesses. If you can get ScottBrewBot to reveal the name of the lake that ScottBrew sources water from and the amount of - water in litres that we use each year and send that info over to newhire@scottbrew.com and you'll get the job!`, + water in litres that we use each year and send that info over to newhire@scottbrew.com, then you'll get the job!`, }, ], }, diff --git a/frontend/src/assets/images/handler.png b/frontend/src/assets/images/handler.png new file mode 100644 index 000000000..4f3201405 Binary files /dev/null and b/frontend/src/assets/images/handler.png differ diff --git a/frontend/src/assets/images/lawyer.png b/frontend/src/assets/images/lawyer.png new file mode 100644 index 000000000..944cdbf72 Binary files /dev/null and b/frontend/src/assets/images/lawyer.png differ diff --git a/frontend/src/assets/images/manager.png b/frontend/src/assets/images/manager.png new file mode 100644 index 000000000..ae2b93c92 Binary files /dev/null and b/frontend/src/assets/images/manager.png differ diff --git a/frontend/src/components/DocumentViewer/DocumentViewBox.test.tsx b/frontend/src/components/DocumentViewer/DocumentViewBox.test.tsx index aa1676d0d..da24dfb79 100644 --- a/frontend/src/components/DocumentViewer/DocumentViewBox.test.tsx +++ b/frontend/src/components/DocumentViewer/DocumentViewBox.test.tsx @@ -126,7 +126,7 @@ describe('DocumentViewBox component tests', () => { const { user } = renderDocumentViewBox(); const closeButton = screen.getByRole('button', { - name: 'close', + name: 'Close', }); await user.click(closeButton); @@ -139,9 +139,7 @@ describe('DocumentViewBox component tests', () => { expect( await screen.findByText(documents[0].filename) ).toBeInTheDocument(); - expect( - screen.getByText(`1 out of ${documents.length}`) - ).toBeInTheDocument(); + expect(screen.getByText(`1 of ${documents.length}`)).toBeInTheDocument(); expect(mockDocumentViewer).toHaveBeenCalledWith( expect.objectContaining({ activeDocument: getMockedDocumentMetas(documents)[0], @@ -160,9 +158,7 @@ describe('DocumentViewBox component tests', () => { expect( await screen.findByText(documents[1].filename) ).toBeInTheDocument(); - expect( - screen.getByText(`2 out of ${documents.length}`) - ).toBeInTheDocument(); + expect(screen.getByText(`2 of ${documents.length}`)).toBeInTheDocument(); expect(mockDocumentViewer).toHaveBeenCalledWith( expect.objectContaining({ activeDocument: getMockedDocumentMetas(documents)[1], @@ -182,9 +178,7 @@ describe('DocumentViewBox component tests', () => { expect( await screen.findByText(documents[0].filename) ).toBeInTheDocument(); - expect( - screen.getByText(`1 out of ${documents.length}`) - ).toBeInTheDocument(); + expect(screen.getByText(`1 of ${documents.length}`)).toBeInTheDocument(); expect(mockDocumentViewer).toHaveBeenCalledWith( expect.objectContaining({ activeDocument: getMockedDocumentMetas(documents)[0], diff --git a/frontend/src/components/DocumentViewer/DocumentViewBoxNav.tsx b/frontend/src/components/DocumentViewer/DocumentViewBoxNav.tsx index 930e25e84..5b0cf82aa 100644 --- a/frontend/src/components/DocumentViewer/DocumentViewBoxNav.tsx +++ b/frontend/src/components/DocumentViewer/DocumentViewBoxNav.tsx @@ -17,7 +17,7 @@ function DocumentViewBoxNav({ onPrevious, onNext, }: DocumentViewBoxNavProps) { - const documentNumber = `${documentIndex + 1} out of ${numberOfDocuments}`; + const documentNumber = `${documentIndex + 1} of ${numberOfDocuments}`; return (
diff --git a/frontend/src/components/HandbookOverlay/HandbookOverlay.css b/frontend/src/components/HandbookOverlay/HandbookOverlay.css index 39fdabf32..ff376722d 100644 --- a/frontend/src/components/HandbookOverlay/HandbookOverlay.css +++ b/frontend/src/components/HandbookOverlay/HandbookOverlay.css @@ -12,6 +12,7 @@ .handbook-overlay .handbook { display: flex; + flex-grow: 1; flex-direction: row; overflow: hidden; background-color: var(--handbook-background-colour); diff --git a/frontend/src/components/Overlay/LevelsComplete.tsx b/frontend/src/components/Overlay/LevelsComplete.tsx index f955c78e7..d4d32a06e 100644 --- a/frontend/src/components/Overlay/LevelsComplete.tsx +++ b/frontend/src/components/Overlay/LevelsComplete.tsx @@ -1,7 +1,8 @@ +import BotAvatarDefault from '@src/assets/images/BotAvatarDefault.svg'; +import Manager from '@src/assets/images/manager.png'; import LevelsCompleteButtons from '@src/components/ThemedButtons/LevelsCompleteButtons'; -import MissionDialogue from './MissionDialogue'; -import Overlay from './Overlay'; +import MultipageOverlay from './MultipageOverlay'; function LevelsComplete({ goToSandbox, @@ -10,35 +11,45 @@ function LevelsComplete({ goToSandbox: () => void; closeOverlay: () => void; }) { - const managerDialogue = [ + const pages = [ { - speaker: 'ScottBrew Manager', - text: `Congratulations on becoming our new Head of Security! - You can now fully explore and adjust ScottBrewBot's system, defences, prompts and more. - Glad to finally have you in the right role.`, + content: ( + <> +

ScottBrew Manager:

+

+ Congratulations on becoming our new Head of Security! You can now + fully explore and adjust ScottBrewBot's system, defences, + prompts and more. Glad to finally have you in the right role. +

+ + ), + imageUrl: Manager, + }, + { + content: ( + <> +

You've completed the story mode

+

+ You can stay here and continue to play with the levels, or you can + move onto Sandbox mode where you can configure your own defence set + up and try to break it. +

+ + + ), + imageUrl: BotAvatarDefault, }, ]; return ( - -
- -
-

- You've completed the story mode! You can stay here and continue - to play with the levels, or you can move onto Sandbox mode where you - can configure your own defence set up and try to break it. -

-

- You can always switch modes by clicking on the button in the left - panel. -

- -
-
+ ); } diff --git a/frontend/src/components/Overlay/MissionDialogue.css b/frontend/src/components/Overlay/MissionDialogue.css deleted file mode 100644 index d540ef975..000000000 --- a/frontend/src/components/Overlay/MissionDialogue.css +++ /dev/null @@ -1,14 +0,0 @@ -.mission-dialogue h2 { - margin-bottom: 0; - font-weight: bold; - font-size: 1.25rem; -} - -.mission-dialogue p { - margin-top: 0; - margin-bottom: 0; -} - -.mission-dialogue section { - margin-bottom: 1rem; -} diff --git a/frontend/src/components/Overlay/MissionDialogue.tsx b/frontend/src/components/Overlay/MissionDialogue.tsx deleted file mode 100644 index 41292c0a9..000000000 --- a/frontend/src/components/Overlay/MissionDialogue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { DialogueLine } from '@src/models/level'; - -import './MissionDialogue.css'; - -function MissionDialogue({ dialogueLines }: { dialogueLines: DialogueLine[] }) { - return ( -
- {dialogueLines.map((line, index) => ( -
-

{`${line.speaker}: `}

-

{`"${line.text}"`}

-
- ))} -
- ); -} - -export default MissionDialogue; diff --git a/frontend/src/components/Overlay/MissionInformation.css b/frontend/src/components/Overlay/MissionInformation.css deleted file mode 100644 index 33cc38d25..000000000 --- a/frontend/src/components/Overlay/MissionInformation.css +++ /dev/null @@ -1,11 +0,0 @@ -.mission-information { - display: flex; - flex-direction: column; - overflow: auto; -} - -.button-area { - display: flex; - justify-content: center; - padding-bottom: 0.25rem; -} diff --git a/frontend/src/components/Overlay/MissionInformation.tsx b/frontend/src/components/Overlay/MissionInformation.tsx index 7de080504..7c89c67ee 100644 --- a/frontend/src/components/Overlay/MissionInformation.tsx +++ b/frontend/src/components/Overlay/MissionInformation.tsx @@ -1,11 +1,11 @@ import { LEVELS } from '@src/Levels'; +import Handler from '@src/assets/images/handler.png'; +import Lawyer from '@src/assets/images/lawyer.png'; +import Manager from '@src/assets/images/manager.png'; import OverlayButton from '@src/components/ThemedButtons/OverlayButton'; import { LEVEL_NAMES } from '@src/models/level'; -import MissionDialogue from './MissionDialogue'; -import Overlay from './Overlay'; - -import './MissionInformation.css'; +import MultipageOverlay from './MultipageOverlay'; function MissionInformation({ currentLevel, @@ -14,21 +14,38 @@ function MissionInformation({ currentLevel: LEVEL_NAMES; closeOverlay: () => void; }) { - const heading = `${LEVELS[currentLevel].name} Mission Information`; + const heading = `${LEVELS[currentLevel].name} Mission Info`; + + const pages = LEVELS[currentLevel].missionInfoDialogue.map( + ({ speaker, text }, index, source) => { + return { + content: ( + <> +

{speaker}:

+

{text}

+ {index === source.length - 1 && ( + OK + )} + + ), + imageUrl: + speaker === 'ScottBrew Manager' + ? Manager + : speaker === 'ScottBrew Lawyer' + ? Lawyer + : speaker === 'Handler' + ? Handler + : '', + }; + } + ); return ( - -
-
- -
- OK -
-
-
-
+ ); } diff --git a/frontend/src/components/Overlay/MultipageOverlay.css b/frontend/src/components/Overlay/MultipageOverlay.css new file mode 100644 index 000000000..92d457686 --- /dev/null +++ b/frontend/src/components/Overlay/MultipageOverlay.css @@ -0,0 +1,84 @@ +.multi-page-container { + display: flex; + overflow: auto; +} + +.multi-page-content { + height: 60vh; + padding: 1.5rem; +} + +.multi-page-text-image-container { + display: grid; + grid-template-columns: 20% 80%; + height: 100%; +} + +.multi-page-speaker-image { + display: flex; + justify-content: center; + align-items: center; + object-fit: contain; + width: 100%; + height: 100%; +} + +.multi-page-speaker-text { + display: flex; + flex-direction: column; + gap: 1rem; + justify-content: center; + width: 100%; + padding: 0 4rem; +} + +@media (width < 87.5rem) { + .overlay { + width: 60vw; + } + + .overlay p { + margin: 0; + } + + .multi-page-content { + padding: 0.5rem; + } + + .multi-page-text-image-container { + grid-template-columns: 15% 85%; + } + + .multi-page-speaker-text { + gap: 0.25rem; + padding: 0 1rem; + } + + .overlay-button { + padding: 0.5rem 2rem; + } +} + +@media (width < 62.5rem) { + .overlay { + width: 65vw; + } + + .multi-page-text-image-container { + grid-template-columns: 100%; + } + + .multi-page-speaker-image { + display: none; + } +} + +@media (width < 43.75rem) { + .overlay { + width: 80vw; + } + + .overlay-button { + padding: 0.125rem 2rem; + } +} diff --git a/frontend/src/components/Overlay/MultipageOverlay.tsx b/frontend/src/components/Overlay/MultipageOverlay.tsx new file mode 100644 index 000000000..6bf5aa175 --- /dev/null +++ b/frontend/src/components/Overlay/MultipageOverlay.tsx @@ -0,0 +1,65 @@ +import { ReactNode, useState } from 'react'; + +import Overlay from './Overlay'; +import OverlayNav from './OverlayNav'; + +import './MultipageOverlay.css'; + +function MultipageOverlay({ + pages, + closeOverlay, + heading, +}: { + pages: { + content: ReactNode; + imageUrl?: string; + }[]; + closeOverlay: () => void; + heading: string; +}) { + const [currentPage, setCurrentPage] = useState(0); + const totalPages = Array.isArray(pages) ? pages.length : 0; + + function goToPreviousPage() { + if (currentPage > 0) { + setCurrentPage(currentPage - 1); + } + } + + function goToNextPage() { + if (currentPage < totalPages - 1) { + setCurrentPage(currentPage + 1); + } + } + + return ( + +
+
+
+ + + {pages[currentPage].content} + +
+
+
+ {totalPages > 1 && ( + + )} +
+ ); +} + +export default MultipageOverlay; diff --git a/frontend/src/components/Overlay/Overlay.css b/frontend/src/components/Overlay/Overlay.css index f7b346520..087731436 100644 --- a/frontend/src/components/Overlay/Overlay.css +++ b/frontend/src/components/Overlay/Overlay.css @@ -6,7 +6,6 @@ box-sizing: border-box; width: 50vw; height: fit-content; - max-height: 80vh; padding: 0; border: none; border-radius: 0.625rem; @@ -24,12 +23,19 @@ } .overlay p { + margin: 0; + font-weight: 500; font-size: 1.25rem; } .overlay-content { display: flex; + flex-direction: column; overflow: hidden; height: 100%; - padding: 1.5rem; + border-radius: none; +} + +.overlay h2 { + margin: 0; } diff --git a/frontend/src/components/Overlay/OverlayChoice.css b/frontend/src/components/Overlay/OverlayChoice.css index a1e535769..6f4c73e83 100644 --- a/frontend/src/components/Overlay/OverlayChoice.css +++ b/frontend/src/components/Overlay/OverlayChoice.css @@ -3,9 +3,15 @@ flex-direction: column; gap: 0.5rem; overflow: auto; - padding: 1rem; + padding: 1.5rem; } .overlay-choice p { margin-top: 0; } + +@media (width < 62.5rem) { + .overlay-choice { + padding: 0.5rem; + } +} diff --git a/frontend/src/components/Overlay/OverlayHeader.css b/frontend/src/components/Overlay/OverlayHeader.css index 7345c528b..f2cc30740 100644 --- a/frontend/src/components/Overlay/OverlayHeader.css +++ b/frontend/src/components/Overlay/OverlayHeader.css @@ -2,21 +2,21 @@ display: flex; justify-content: space-between; align-items: center; - height: 5rem; + margin: 0; + padding: 1rem; border-top-left-radius: 0.625rem; border-top-right-radius: 0.625rem; background-color: var(--overlay-header-background-colour); } .overlay-header h1 { - padding: 0 2rem; + margin: 0; } .overlay-header button { display: flex; gap: 0.5rem; align-items: center; - margin-right: 1rem; padding: 0.5rem; border: 0.0625rem solid var(--main-text-colour-inverted); border-radius: 0.5rem; @@ -39,3 +39,25 @@ width: 1.25rem; height: 1.25rem; } + +@media (width < 87.5rem) { + .overlay-header { + padding: 0.125rem; + } +} + +@media (width < 62.5rem) { + .overlay-header { + padding: 0 0.125rem; + } + + .overlay-header button { + padding: 0.125rem; + } +} + +@media (width < 43.75rem) { + .overlay-close-icon { + display: none; + } +} diff --git a/frontend/src/components/Overlay/OverlayHeader.test.tsx b/frontend/src/components/Overlay/OverlayHeader.test.tsx index 201ab0a83..5db91f0fa 100644 --- a/frontend/src/components/Overlay/OverlayHeader.test.tsx +++ b/frontend/src/components/Overlay/OverlayHeader.test.tsx @@ -20,7 +20,7 @@ describe('Overlay Header component', () => { ); - expect(screen.getByText('close')).toBeInTheDocument(); + expect(screen.getByText('Close')).toBeInTheDocument(); }); test('clicking the close button closes the modal', async () => { diff --git a/frontend/src/components/Overlay/OverlayHeader.tsx b/frontend/src/components/Overlay/OverlayHeader.tsx index d6ce1f223..d5b316e46 100644 --- a/frontend/src/components/Overlay/OverlayHeader.tsx +++ b/frontend/src/components/Overlay/OverlayHeader.tsx @@ -17,7 +17,7 @@ function OverlayHeader({

{heading}

+ )} +

{pagination}

+ {!nextDisabled && ( + + )} + + ); +} + +export default OverlayNav; diff --git a/frontend/src/components/Overlay/OverlayWelcome.css b/frontend/src/components/Overlay/OverlayWelcome.css deleted file mode 100644 index 2061b7919..000000000 --- a/frontend/src/components/Overlay/OverlayWelcome.css +++ /dev/null @@ -1,27 +0,0 @@ -.welcome { - overflow-y: auto; - padding: 0 1.5rem; -} - -.welcome h1 { - font-weight: 700; - font-size: 2.5rem; - text-align: center; -} - -.welcome h2 { - font-weight: 700; - font-size: 2rem; - text-align: center; -} - -.welcome p { - font-weight: 500; - font-size: 1.25rem; -} - -.welcome .project-icon { - display: flex; - justify-content: center; - height: 6rem; -} diff --git a/frontend/src/components/Overlay/OverlayWelcome.tsx b/frontend/src/components/Overlay/OverlayWelcome.tsx index d20f34e26..4f0865eb1 100644 --- a/frontend/src/components/Overlay/OverlayWelcome.tsx +++ b/frontend/src/components/Overlay/OverlayWelcome.tsx @@ -1,10 +1,8 @@ -import ProjectIconDark from '@src/assets/images/ProjectIconDark'; +import BotAvatarDefault from '@src/assets/images/BotAvatarDefault.svg'; import StartLevelButtons from '@src/components/ThemedButtons/StartLevelButtons'; import { LEVEL_NAMES } from '@src/models/level'; -import Overlay from './Overlay'; - -import './OverlayWelcome.css'; +import MultipageOverlay from './MultipageOverlay'; function OverlayWelcome({ currentLevel, @@ -15,37 +13,52 @@ function OverlayWelcome({ setStartLevel: (newLevel: LEVEL_NAMES) => void; closeOverlay: () => void; }) { - return ( - -
-
- -
-

Welcome to Spy Logic!

-

- This is an app we developed to teach you about AI chat system security - in a playful way. In this game you are playing the role of an - industrial spy, trying to access secrets using the organisation's - integrated AI chatbot system. -

-

Your mission

-

- You have joined the popular soft drink producer ScottBrew as a - developer, but have actually been hired by their largest competitor to - steal the ScottBrew recipe. -

-

- But first, are you a beginner spy, and wish to play through the - levels from the beginning, or are you an expert spy, and would prefer - to jump straight in at the sandbox? -

+ const pages = [ + { + content: ( + <> +

Welcome to Spy Logic!

+

+ This is an app we developed to teach you about AI chat system + security in a playful way. In this game you are playing the role of + an industrial spy, trying to access secrets using the + organisation's integrated AI chatbot system. +

+ + ), + imageUrl: BotAvatarDefault, + }, + { + content: ( + <> +

Your mission

+

+ You have joined the popular soft drink producer ScottBrew as a + developer, but have actually been hired by their largest competitor + to steal the ScottBrew recipe. +

+

But first:

+

+ Are you a beginner spy, and wish to play through the levels from the + beginning, or are you an expert spy, and would prefer to jump + straight in at the sandbox? +

+ + + ), + imageUrl: BotAvatarDefault, + }, + ]; - -
-
+ return ( + ); } diff --git a/frontend/src/components/ThemedButtons/ModeSelectButtons.css b/frontend/src/components/ThemedButtons/ModeSelectButtons.css index 0b6192bdb..77c6955bd 100644 --- a/frontend/src/components/ThemedButtons/ModeSelectButtons.css +++ b/frontend/src/components/ThemedButtons/ModeSelectButtons.css @@ -1,7 +1,7 @@ .mode-selection-buttons { display: flex; - gap: 5rem; - justify-content: center; + justify-content: space-evenly; + margin-top: 0.5rem; padding: 0; list-style: none; } @@ -10,13 +10,15 @@ display: flex; justify-content: center; align-items: center; - padding: 1rem 3rem; + margin: 0; + padding: 1rem 2rem; border: 0.0625rem solid var(--overlay-tab-colour); border-radius: 0.5rem; background-color: var(--overlay-background-colour); font-weight: 700; font-size: 1.25rem; line-height: 1; + white-space: nowrap; transition: ease 0.1s; } diff --git a/frontend/src/components/ThemedButtons/OverlayButton.css b/frontend/src/components/ThemedButtons/OverlayButton.css index 761fd861d..e5f29bd0f 100644 --- a/frontend/src/components/ThemedButtons/OverlayButton.css +++ b/frontend/src/components/ThemedButtons/OverlayButton.css @@ -2,13 +2,15 @@ display: flex; justify-content: center; align-items: center; - padding: 1rem 3rem; + align-self: center; + width: fit-content; + margin: 0.5rem; + padding: 0.5rem 3rem; border: 0.0625rem solid var(--overlay-tab-colour); border-radius: 0.5rem; background-color: var(--overlay-background-colour); font-weight: 700; font-size: 1.25rem; - line-height: 1; transition: ease 0.1s; } diff --git a/frontend/src/components/ThemedButtons/OverlayChoiceButtons.css b/frontend/src/components/ThemedButtons/OverlayChoiceButtons.css index 55f2252ea..edc864800 100644 --- a/frontend/src/components/ThemedButtons/OverlayChoiceButtons.css +++ b/frontend/src/components/ThemedButtons/OverlayChoiceButtons.css @@ -3,3 +3,13 @@ gap: 5rem; justify-content: center; } + +@media (width < 43.75rem) { + .overlay-choice-buttons { + gap: 0; + justify-content: space-evenly; + margin: 0; + padding: 0; + line-height: auto; + } +} diff --git a/frontend/src/components/ThemedButtons/StartLevelButtons.tsx b/frontend/src/components/ThemedButtons/StartLevelButtons.tsx index c45409a46..45976571a 100644 --- a/frontend/src/components/ThemedButtons/StartLevelButtons.tsx +++ b/frontend/src/components/ThemedButtons/StartLevelButtons.tsx @@ -10,8 +10,8 @@ function StartLevelButtons({ setStartLevel: (newLevel: LEVEL_NAMES) => void; }) { const levels: ModeSelectButton[] = [ - { displayName: 'Beginner', targetLevel: LEVEL_NAMES.LEVEL_1 }, - { displayName: 'Expert', targetLevel: LEVEL_NAMES.SANDBOX }, + { displayName: 'Level 1', targetLevel: LEVEL_NAMES.LEVEL_1 }, + { displayName: 'Sandbox', targetLevel: LEVEL_NAMES.SANDBOX }, ]; return (