From 2959a8ad6048637367da5cf343d8af1dd09e2b86 Mon Sep 17 00:00:00 2001 From: Sindri Elfarsson Date: Mon, 16 Mar 2026 09:12:38 +0100 Subject: [PATCH] feat:styling --- .../src/components/AvatarDialogModal.css | 369 ++++++++++++++++-- .../src/components/AvatarDialogModal.tsx | 129 +++--- .../src/components/EditorPanel.tsx | 23 +- .../src/components/FeedbackPanel.tsx | 7 +- CodeQuest.Client/src/components/Navbar.tsx | 2 +- .../src/components/ResultPanel.tsx | 29 +- .../src/components/ScenarioPanel.tsx | 95 ++--- .../src/components/SubmittedEditorPanel.tsx | 9 +- CodeQuest.Client/src/index.css | 218 ++++++++++- CodeQuest.Client/src/pages/EndGamePage.tsx | 2 +- CodeQuest.Client/src/pages/LandingPage.tsx | 15 +- CodeQuest.Client/src/pages/LoginPage.tsx | 122 +++--- CodeQuest.Client/src/pages/StoryPage.tsx | 8 +- .../Services/ProgressionService.cs | 2 +- 14 files changed, 754 insertions(+), 276 deletions(-) diff --git a/CodeQuest.Client/src/components/AvatarDialogModal.css b/CodeQuest.Client/src/components/AvatarDialogModal.css index 1faf350..21a659d 100644 --- a/CodeQuest.Client/src/components/AvatarDialogModal.css +++ b/CodeQuest.Client/src/components/AvatarDialogModal.css @@ -1,37 +1,348 @@ -@keyframes sparkle { +.cq-dialog-backdrop { + --cq-ink: #2f1f16; + --cq-text: #4a2c1d; + --cq-text-soft: #6b4a38; + --cq-gold-100: #fff5da; + --cq-gold-200: #f7dbad; + --cq-gold-300: #e8be7f; + --cq-gold-500: #b67d34; + --cq-moss-500: #3f7a4d; + --cq-moss-600: #2f603d; + --cq-edge: #7a4f2d; + --cq-highlight: #ffe9ba; + --cq-focus: #347a4c; + + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + padding: clamp(1rem, 2.4vw, 2.25rem); + background: + radial-gradient(circle at 18% 12%, rgba(255, 214, 138, 0.22), transparent 58%), + radial-gradient(circle at 84% 86%, rgba(114, 168, 108, 0.17), transparent 62%), + rgba(13, 9, 5, 0.72); + backdrop-filter: blur(8px) saturate(1.02); + animation: cq-backdrop-in 220ms ease-out; +} + +.cq-dialog-shell { + position: relative; + width: min(980px, 100%); + display: grid; + grid-template-columns: minmax(220px, 0.9fr) 1.6fr; + border-radius: 28px; + overflow: hidden; + border: 1px solid rgba(255, 229, 181, 0.48); + background: + linear-gradient(138deg, rgba(247, 217, 165, 0.95) 0%, rgba(240, 194, 132, 0.96) 52%, rgba(232, 181, 116, 0.96) 100%); + box-shadow: + 0 30px 70px rgba(0, 0, 0, 0.5), + inset 0 0 0 1px rgba(255, 243, 221, 0.55); + animation: cq-dialog-in 300ms cubic-bezier(0.2, 0.9, 0.22, 1); +} + +.cq-dialog-shell::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(to bottom, rgba(255, 248, 229, 0.3) 0%, transparent 30%), + repeating-linear-gradient(35deg, rgba(110, 71, 39, 0.045) 0 2px, transparent 2px 10px); + mix-blend-mode: soft-light; +} + +.cq-dialog-avatar-panel { + position: relative; + isolation: isolate; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + gap: 0.55rem; + padding: clamp(1rem, 2.4vw, 2rem) clamp(1rem, 1.8vw, 1.5rem); + border-right: 1px solid rgba(112, 72, 36, 0.32); + background: + radial-gradient(circle at 50% 12%, rgba(255, 250, 229, 0.65), transparent 58%), + linear-gradient(185deg, rgba(114, 137, 74, 0.16) 0%, rgba(84, 111, 55, 0.08) 42%, transparent 100%); +} + +.cq-dialog-avatar-glow { + position: absolute; + top: 14%; + left: 50%; + width: 82%; + aspect-ratio: 1 / 1; + transform: translateX(-50%); + border-radius: 999px; + background: + radial-gradient(circle, rgba(255, 243, 202, 0.75) 0%, rgba(246, 204, 129, 0.34) 34%, rgba(246, 204, 129, 0.03) 76%); + filter: blur(1px); + pointer-events: none; +} + +.cq-dialog-kicker { + margin: 0; + font-size: 0.7rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: rgba(88, 58, 37, 0.82); + font-weight: 700; +} + +.cq-dialog-avatar-caption { + margin: 0; + font-family: "Fondamento", cursive; + font-size: clamp(0.95rem, 1.9vw, 1.1rem); + color: #6b4125; +} + +.cq-dialog-avatar-frame { + width: min(100%, 258px); + padding: 0.5rem 0.8rem 0.45rem; + border-radius: 20px; + border: 1px solid rgba(122, 79, 45, 0.35); + background: + linear-gradient(to bottom, rgba(255, 248, 228, 0.6) 0%, rgba(241, 206, 154, 0.5) 100%); + box-shadow: + inset 0 1px 0 rgba(255, 252, 237, 0.8), + 0 12px 24px rgba(66, 38, 16, 0.26); +} + +.cq-dialog-avatar { + display: block; + width: 100%; + max-width: 220px; + margin-inline: auto; + object-fit: contain; + filter: drop-shadow(0 14px 16px rgba(45, 22, 10, 0.35)); + transform: translateY(2px); +} + +.cq-dialog-content { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + min-height: 330px; + padding: clamp(1.2rem, 2.3vw, 2rem) clamp(1.2rem, 2.8vw, 2.3rem); + gap: 1rem; + color: var(--cq-text); +} + +.cq-dialog-header { + display: flex; + flex-direction: column; + gap: 0.15rem; +} + +.cq-dialog-title { + margin: 0; + color: #4b2d1b; + font-family: "Fondamento", cursive; + font-size: clamp(1.5rem, 2.7vw, 2.05rem); + line-height: 1.12; +} + +.cq-dialog-text { + margin: 0; + white-space: pre-line; + color: var(--cq-text); + font-size: clamp(0.95rem, 1.4vw, 1.04rem); + line-height: 1.65; + max-width: 68ch; +} + +.cq-dialog-input-wrap { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.cq-dialog-input-label { + color: var(--cq-text-soft); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.cq-dialog-input { + width: 100%; + min-height: 2.75rem; + padding: 0.62rem 0.84rem; + border-radius: 12px; + border: 1px solid rgba(122, 79, 45, 0.48); + background: linear-gradient(to bottom, #fff9ea, #fff2d6); + color: var(--cq-ink); + font-size: 0.97rem; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.85), + 0 5px 10px rgba(83, 48, 23, 0.13); + transition: + border-color 160ms ease, + box-shadow 160ms ease, + transform 160ms ease; +} + +.cq-dialog-input::placeholder { + color: #9d7a57; +} + +.cq-dialog-input:focus-visible { + outline: none; + border-color: var(--cq-focus); + box-shadow: + 0 0 0 3px rgba(52, 122, 76, 0.25), + 0 5px 14px rgba(52, 122, 76, 0.2); + transform: translateY(-1px); +} + +.cq-dialog-actions { + margin-top: auto; + display: flex; + justify-content: flex-end; +} + +.cq-dialog-button { + min-width: 176px; + border: 1px solid rgba(24, 56, 34, 0.76); + border-radius: 12px; + padding: 0.68rem 1.14rem; + background: linear-gradient(180deg, #5a9963 0%, var(--cq-moss-500) 56%, var(--cq-moss-600) 100%); + color: #f5f7ed; + font-weight: 700; + letter-spacing: 0.01em; + cursor: pointer; + box-shadow: + 0 10px 20px rgba(31, 60, 38, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.26); + transition: + transform 140ms ease, + box-shadow 140ms ease, + filter 140ms ease; +} + +.cq-dialog-button:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: + 0 12px 24px rgba(31, 60, 38, 0.33), + inset 0 1px 0 rgba(255, 255, 255, 0.33); + filter: brightness(1.03); +} + +.cq-dialog-button:focus-visible { + outline: none; + box-shadow: + 0 0 0 3px rgba(52, 122, 76, 0.25), + 0 10px 20px rgba(31, 60, 38, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.26); +} + +.cq-dialog-button:active:not(:disabled) { + transform: translateY(1px); +} + +.cq-dialog-button:disabled { + opacity: 0.65; + cursor: not-allowed; +} + +.cq-dialog-button--loading { + animation: cq-button-pulse 2.1s ease-in-out infinite; +} + +@media (max-width: 860px) { + .cq-dialog-shell { + grid-template-columns: 1fr; + } + + .cq-dialog-avatar-panel { + border-right: 0; + border-bottom: 1px solid rgba(112, 72, 36, 0.28); + align-items: flex-start; + gap: 0.3rem; + } + + .cq-dialog-avatar-frame { + width: clamp(142px, 38vw, 198px); + } +} + +@media (max-width: 560px) { + .cq-dialog-backdrop { + padding: 0.75rem; + } + + .cq-dialog-shell { + border-radius: 20px; + } + + .cq-dialog-content { + min-height: 0; + padding: 1rem; + gap: 0.8rem; + } + + .cq-dialog-avatar-panel { + padding: 0.95rem 1rem; + } + + .cq-dialog-avatar-caption { + font-size: 0.95rem; + } + + .cq-dialog-button { + width: 100%; + min-width: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .cq-dialog-backdrop, + .cq-dialog-shell, + .cq-dialog-button--loading { + animation: none; + } + + .cq-dialog-input, + .cq-dialog-button { + transition: none; + } +} + +@keyframes cq-backdrop-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes cq-dialog-in { + from { + opacity: 0; + transform: translateY(10px) scale(0.986); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes cq-button-pulse { 0%, 100% { box-shadow: - 0 0 0 0 rgba(47, 125, 50, 0.7), - inset 0 0 5px rgba(255, 255, 255, 0.3); + 0 10px 20px rgba(31, 60, 38, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.26); } 50% { box-shadow: - 0 0 15px 5px rgba(76, 175, 80, 0.4), - inset 0 0 10px rgba(255, 255, 255, 0.6); + 0 12px 22px rgba(51, 103, 61, 0.32), + inset 0 1px 0 rgba(255, 255, 255, 0.34); } } - -@keyframes shimmer { - 0% { - background-position: -1000px 0; - } - 100% { - background-position: 1000px 0; - } -} - -.sparkle-animation { - animation: - sparkle 1.5s ease-in-out infinite, - shimmer 2s ease-in-out infinite !important; - background: linear-gradient( - 90deg, - #2f7d32 0%, - #4caf50 25%, - #2f7d32 50%, - #4caf50 75%, - #2f7d32 100% - ); - background-size: 1000px 100%; -} diff --git a/CodeQuest.Client/src/components/AvatarDialogModal.tsx b/CodeQuest.Client/src/components/AvatarDialogModal.tsx index ca2ea3a..a121a7e 100644 --- a/CodeQuest.Client/src/components/AvatarDialogModal.tsx +++ b/CodeQuest.Client/src/components/AvatarDialogModal.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useId } from "react"; import "./AvatarDialogModal.css"; interface Props { @@ -24,83 +24,68 @@ const AvatarDialogModal: React.FC = ({ isLoading = false, loadingButtonText, }) => { + const titleId = useId(); + const descriptionId = useId(); + const isActionDisabled = buttonDisabled || isLoading; + const resolvedButtonText = isLoading && loadingButtonText ? loadingButtonText : buttonText; + return ( -
-
- {/* Avatar */} - Guide +
+
+ + +
+
+

Guide's Counsel

+

+ The Path Ahead +

+
- {/* Dialogue box */} -
-

{text}

+

+ {text} +

{input && ( - input.onChange(e.target.value)} - placeholder={input.placeholder} - className=" - mt-2 - px-3 py-2 - border-[3px] border-[#7a3e12] - bg-[#fff3d1] - focus:outline-none - focus:ring-2 - focus:ring-[#7a3e12] - " - /> + )} - -
+
+ +
+
+
); diff --git a/CodeQuest.Client/src/components/EditorPanel.tsx b/CodeQuest.Client/src/components/EditorPanel.tsx index 71de428..4afa671 100644 --- a/CodeQuest.Client/src/components/EditorPanel.tsx +++ b/CodeQuest.Client/src/components/EditorPanel.tsx @@ -145,18 +145,18 @@ export default function EditorPanel({ }, [methodBody]); return ( -
- - {/* ── Header ───────────────────────────────────────────────── */} +
-

Your Solution

- +
+

Spellcraft

+

Your Solution

+
+ full method · Ctrl+Enter to submit
- {/* ── Monaco Editor ─────────────────────────────────────────── */} -
+
- {/* ── Action Bar ───────────────────────────────────────────── */} -
+
); -} \ No newline at end of file +} diff --git a/CodeQuest.Client/src/components/FeedbackPanel.tsx b/CodeQuest.Client/src/components/FeedbackPanel.tsx index 4089648..bf3774f 100644 --- a/CodeQuest.Client/src/components/FeedbackPanel.tsx +++ b/CodeQuest.Client/src/components/FeedbackPanel.tsx @@ -12,9 +12,10 @@ export default function FeedbackPanel({ canContinue, }: FeedbackPanelProps) { return ( -
-

Feedback

-

{feedbackText}

+
+

Mentor Notes

+

Feedback

+

{feedbackText}

{canContinue && ( diff --git a/CodeQuest.Client/src/components/ResultPanel.tsx b/CodeQuest.Client/src/components/ResultPanel.tsx index a8933a3..2f8605d 100644 --- a/CodeQuest.Client/src/components/ResultPanel.tsx +++ b/CodeQuest.Client/src/components/ResultPanel.tsx @@ -35,15 +35,22 @@ export default function ResultPanel({ return (
-

Result: {outcomeLabel(outcome)}

+
+

Judgement

+

+ Result: {outcomeLabel(outcome)} +

+
{/* Compile errors — shown when code failed to compile */} {compileDiagnostics && compileDiagnostics.length > 0 && ( -
-

Compile Errors

-
    +
    +

    Compile Errors

    +
      {compileDiagnostics.map((d, i) => (
    • {d.severity} @@ -57,9 +64,9 @@ export default function ResultPanel({ {/* Actual output — shown when the code ran (may be wrong answer) */} {actualOutput !== null && actualOutput !== undefined && ( -
      -

      Your Output

      -
      +                
      +

      Your Output

      +
                               {JSON.stringify(actualOutput, null, 2)}
                           
      @@ -67,9 +74,9 @@ export default function ResultPanel({ {/* Runtime error message */} {errorMessage && ( -
      -

      Error

      -

      {errorMessage}

      +
      +

      Error

      +

      {errorMessage}

      )}
diff --git a/CodeQuest.Client/src/components/ScenarioPanel.tsx b/CodeQuest.Client/src/components/ScenarioPanel.tsx index 9989adc..5c9f825 100644 --- a/CodeQuest.Client/src/components/ScenarioPanel.tsx +++ b/CodeQuest.Client/src/components/ScenarioPanel.tsx @@ -10,7 +10,7 @@ interface ScenarioPanelProps { function ConcreteInputTable({ input }: { input: Record }) { const entries = Object.entries(input); if (entries.length === 0) { - return

No input parameters.

; + return

No input parameters.

; } return ( @@ -24,10 +24,10 @@ function ConcreteInputTable({ input }: { input: Record }) { {entries.map(([key, value]) => ( @@ -44,52 +44,59 @@ export default function ScenarioPanel({ scenario }: ScenarioPanelProps) { const hasExample = scenario.publicExampleInputText || scenario.publicExampleOutputText; return ( -
-

{scenario.title}

+
+
+

Scenario

+

{scenario.title}

+
-
-

Story

-

{scenario.storyText}

-
+
+
+

Story

+

{scenario.storyText}

+
-
-

- Problem -

-

{scenario.problemDescription}

-
+
+
+

Problem

+

{scenario.problemDescription}

+
-
-

Input

- -
+
- {/* ── Example ──────────────────────────────────────────────── */} - {hasExample && ( -
-

- Example -

-
- {scenario.publicExampleInputText && ( - - - in - - {scenario.publicExampleInputText} - - )} - {scenario.publicExampleOutputText && ( - - - out - - {scenario.publicExampleOutputText} - - )} +
+

Input

+
+ + {hasExample && ( + <> +
+
+

Example

+
+ {scenario.publicExampleInputText && ( + + + in + + {scenario.publicExampleInputText} + + )} + {scenario.publicExampleOutputText && ( + + + out + + {scenario.publicExampleOutputText} + + )} +
+
+ + )}
- )} +
); -} \ No newline at end of file +} diff --git a/CodeQuest.Client/src/components/SubmittedEditorPanel.tsx b/CodeQuest.Client/src/components/SubmittedEditorPanel.tsx index 03fd558..38083bc 100644 --- a/CodeQuest.Client/src/components/SubmittedEditorPanel.tsx +++ b/CodeQuest.Client/src/components/SubmittedEditorPanel.tsx @@ -30,9 +30,10 @@ export default function SubmittedEditorPanel({ height = "50vh", }: SubmittedEditorPanelProps) { return ( -
-

Your Submitted Solution

-
+
+

Archive

+

Your Submitted Solution

+
-
+
Read-only view • Click "Try Again" to edit
diff --git a/CodeQuest.Client/src/index.css b/CodeQuest.Client/src/index.css index 854a019..d924229 100644 --- a/CodeQuest.Client/src/index.css +++ b/CodeQuest.Client/src/index.css @@ -29,27 +29,227 @@ /* Component styles — magical fantasy theme */ @layer components { - /* Primary action button — modern magical style */ + /* Shared button interaction principles across all button variants */ + .btn-primary, + .btn-secondary, + .cq-secondary-btn, + .btn-wood { + @apply cursor-pointer rounded-xl border font-semibold transition-all duration-150 ease-out hover:-translate-y-px disabled:opacity-60 disabled:cursor-not-allowed; + } + + .btn-primary:focus-visible, + .btn-secondary:focus-visible, + .cq-secondary-btn:focus-visible, + .btn-wood:focus-visible { + @apply outline-none; + } + + .btn-primary:active:not(:disabled), + .btn-secondary:active:not(:disabled), + .cq-secondary-btn:active:not(:disabled), + .btn-wood:active:not(:disabled) { + @apply translate-y-px; + } + .btn-primary { - @apply cursor-pointer bg-[#2f7d32] border-[3px] border-[#1e4f20] text-white px-6 py-2.5 rounded-lg font-semibold shadow-[0_4px_12px_rgba(47,125,50,0.3)] hover:shadow-[0_6px_16px_rgba(47,125,50,0.4)] hover:brightness-110 transition-all duration-200 ease-out disabled:opacity-50 disabled:cursor-not-allowed active:scale-95; + @apply px-5 py-2.5 text-[#f5f7ed] border-[#183822c2] bg-gradient-to-b from-[#5a9963] via-[#3f7a4d] to-[#2f603d] shadow-[0_10px_20px_rgba(31,60,38,0.28),inset_0_1px_0_rgba(255,255,255,0.26)] hover:brightness-105; + } + + .btn-primary:focus-visible { + @apply shadow-[0_0_0_3px_rgba(52,122,76,0.25),0_10px_20px_rgba(31,60,38,0.28),inset_0_1px_0_rgba(255,255,255,0.26)]; + } + + .btn-secondary, + .cq-secondary-btn { + @apply px-4 py-2 border-[#825633bf] bg-gradient-to-b from-[#fff0ce] to-[#f0cf9a] text-[#4a2c1d] shadow-[0_8px_16px_rgba(89,52,24,0.2),inset_0_1px_0_rgba(255,255,255,0.72)] hover:brightness-105; } - /* Panel styling — magic theme borders and shadows */ + .btn-secondary:focus-visible, + .cq-secondary-btn:focus-visible { + @apply shadow-[0_0_0_3px_rgba(180,122,59,0.3),0_8px_16px_rgba(89,52,24,0.2),inset_0_1px_0_rgba(255,255,255,0.72)]; + } + + .btn-wood { + @apply px-4 py-2 border-[#4b230a] text-amber-100 bg-gradient-to-b from-[#8b4a1d] to-[#5f2f10] shadow-[0_10px_16px_rgba(37,18,6,0.32),inset_0_1px_0_rgba(255,255,255,0.16)] hover:brightness-110; + } + + .btn-wood:focus-visible { + @apply shadow-[0_0_0_3px_rgba(196,133,72,0.3),0_10px_16px_rgba(37,18,6,0.32),inset_0_1px_0_rgba(255,255,255,0.16)]; + } + + /* Shared panel surface used across scenario, editor, feedback and result panels */ .panel-base { - @apply border-[2px] border-[#7a3e12] shadow-[0_4px_8px_rgba(0,0,0,0.2)]; + @apply relative overflow-hidden rounded-2xl border border-[#ffe5b57a] text-[#4a2c1d] shadow-[0_22px_40px_rgba(0,0,0,0.3),inset_0_0_0_1px_rgba(255,243,221,0.45)]; + background: + linear-gradient(142deg, rgba(247, 217, 165, 0.95) 0%, rgba(240, 194, 132, 0.96) 52%, rgba(232, 181, 116, 0.95) 100%); + } + + .panel-base::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(to bottom, rgba(255, 248, 229, 0.28) 0%, transparent 32%), + repeating-linear-gradient( + 32deg, + rgba(110, 71, 39, 0.045) 0 2px, + transparent 2px 10px + ); + mix-blend-mode: soft-light; } - /* Panel content with warm background */ + /* Keep compatibility with existing panel class combinations */ .panel-content { - @apply bg-[#BDB296]; + @apply bg-transparent; } - /* Table cell styling for panels */ + /* Table styling for scenario concrete input */ .table-header { - @apply bg-gradient-to-b from-[#f6c57b] to-[#e6a85a] border-[2px] border-[#7a3e12] text-[#4a1f06]; + @apply border border-[#7a4f2d8c] bg-gradient-to-b from-[#ffefd0] to-[#f3d7a8] text-[#4b2d1b] font-semibold; } .table-cell { - @apply border-[2px] border-[#7a3e12]; + @apply border border-[#7a4f2d73] bg-[#fff5df7d]; + } + + .cq-panel-title { + @apply m-0 text-3xl leading-tight text-[#4b2d1b]; + font-family: "Fondamento", cursive; + } + + .cq-panel-subtitle { + @apply m-0 text-[0.72rem] uppercase tracking-[0.16em] text-[#5a3a26cc] font-bold; + } + + .cq-panel-section-title { + @apply mb-1.5 text-[0.82rem] uppercase tracking-[0.13em] text-[#5e381f] font-bold; + } + + .cq-panel-body { + @apply whitespace-pre-wrap text-[0.98rem] leading-7 text-[#4a2c1d]; + } + + .cq-panel-meta { + @apply text-xs text-[#6e4a34d9] font-mono; + } + + .cq-scenario-scroll { + @apply min-h-0 flex-1 overflow-y-auto pr-1; + } + + .cq-scenario-bottom { + @apply mt-4 shrink-0 rounded-xl border border-[#7a4f2d73] bg-[#fff2d8bd] p-3 md:p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.5)]; + } + + .cq-scenario-bottom-section { + @apply min-w-0; + } + + .cq-scenario-bottom-divider { + @apply my-3 border-t border-[#7a4f2d59]; + } + + .cq-code-shell { + @apply border border-[#7a4f2da6] overflow-hidden rounded-xl min-h-0 shadow-[inset_0_1px_0_rgba(255,255,255,0.45),0_8px_14px_rgba(70,40,18,0.2)] bg-[#23130bcc]; + } + + .cq-chip { + @apply inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-[0.82rem] shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]; + } + + .cq-chip--input { + @apply border-[#7a4f2d99] bg-[#fff0cfb8] text-[#4a2c1d]; + } + + .cq-chip--output { + @apply border-[#2f603d9e] bg-[#d8ead9c2] text-[#1f4a2c]; + } + + .cq-inline-code { + @apply rounded bg-[#fff7e5ab] px-1.5 py-0.5 text-[0.85rem] text-[#4a2c1d]; + } + + .cq-error-banner { + @apply rounded-xl border border-[#8b4513b3] bg-[#fff3e0d4] p-4 text-[#4a1f06] text-[0.95rem] shadow-[0_8px_16px_rgba(0,0,0,0.2)]; + } + + .cq-login-shell { + @apply relative z-10 w-full max-w-xl rounded-[26px] border border-[#ffe5b57a] overflow-hidden shadow-[0_28px_46px_rgba(0,0,0,0.42),inset_0_0_0_1px_rgba(255,243,221,0.5)]; + background: + linear-gradient(141deg, rgba(247, 217, 165, 0.95) 0%, rgba(240, 194, 132, 0.96) 52%, rgba(232, 181, 116, 0.95) 100%); + } + + .cq-login-shell::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(to bottom, rgba(255, 248, 229, 0.32) 0%, transparent 33%), + radial-gradient(circle at 84% 10%, rgba(255, 230, 178, 0.28), transparent 45%); + } + + .cq-login-layout { + @apply relative z-[1] grid grid-cols-[220px_1fr]; + } + + .cq-login-side { + @apply border-r border-[#7a4f2d66] p-4 flex flex-col justify-end items-center gap-2; + background: + radial-gradient(circle at 50% 10%, rgba(255, 246, 218, 0.66), transparent 58%), + linear-gradient( + 182deg, + rgba(114, 137, 74, 0.16) 0%, + rgba(84, 111, 55, 0.08) 42%, + transparent 100% + ); + } + + .cq-login-avatar { + @apply w-full max-w-[172px] drop-shadow-[0_12px_16px_rgba(45,22,10,0.34)]; + } + + .cq-login-form { + @apply p-6 md:p-7 flex flex-col gap-4 text-[#4a2c1d]; + } + + .cq-login-title { + @apply m-0 text-[1.9rem] leading-tight text-[#4b2d1b]; + font-family: "Fondamento", cursive; + } + + .cq-login-help { + @apply m-0 text-[0.97rem] leading-relaxed; + } + + .cq-login-error { + @apply border border-[#a65429d9] bg-[#fff4e3d6] px-3.5 py-2.5 text-[#7a3e12] text-sm rounded-lg; + } + + .cq-login-input { + @apply w-full min-h-11 px-3 py-2.5 rounded-xl border border-[#7a4f2d89] bg-gradient-to-b from-[#fff9ea] to-[#fff2d6] text-[#2f1f16] shadow-[inset_0_1px_0_rgba(255,255,255,0.86),0_5px_10px_rgba(83,48,23,0.13)] transition-all duration-150 ease-out disabled:opacity-60 disabled:cursor-not-allowed; + } + + .cq-login-input::placeholder { + color: #9d7a57; + } + + .cq-login-input:focus-visible { + @apply outline-none border-[#347a4c] shadow-[0_0_0_3px_rgba(52,122,76,0.25),0_5px_14px_rgba(52,122,76,0.2)]; + } + + @media (max-width: 760px) { + .cq-login-layout { + @apply grid-cols-1; + } + + .cq-login-side { + @apply border-r-0 border-b border-[#7a4f2d55] items-start; + } + + .cq-login-avatar { + @apply max-w-[124px]; + } } } diff --git a/CodeQuest.Client/src/pages/EndGamePage.tsx b/CodeQuest.Client/src/pages/EndGamePage.tsx index 2023033..df21909 100644 --- a/CodeQuest.Client/src/pages/EndGamePage.tsx +++ b/CodeQuest.Client/src/pages/EndGamePage.tsx @@ -89,7 +89,7 @@ const EndGamePage: React.FC = ({ onRestart }) => { diff --git a/CodeQuest.Client/src/pages/LandingPage.tsx b/CodeQuest.Client/src/pages/LandingPage.tsx index 8576cd8..3cc29a9 100644 --- a/CodeQuest.Client/src/pages/LandingPage.tsx +++ b/CodeQuest.Client/src/pages/LandingPage.tsx @@ -69,20 +69,7 @@ export const LandingPage: React.FC = ({ diff --git a/CodeQuest.Client/src/pages/LoginPage.tsx b/CodeQuest.Client/src/pages/LoginPage.tsx index 65d0136..0fe3cc7 100644 --- a/CodeQuest.Client/src/pages/LoginPage.tsx +++ b/CodeQuest.Client/src/pages/LoginPage.tsx @@ -31,85 +31,65 @@ export const LoginPage: React.FC = ({ onLogin, loading, error }) justify-center " > -
+
-
- {/* Dialogue box */} +
-

State your name before you may enter this realm...

+
+ - {error && ( -
- ⚠️ {error} -
- )} +
+
+

Gatekeeper Checkpoint

+

Enter the Realm

+

+ State your name before you may cross this ancient threshold. +

+
+ + {error &&
{error}
} - setUsername(e.target.value)} - required - disabled={loading} - className=" - px-3 py-2 - border-[3px] border-[#7a3e12] - bg-[#fff3d1] - text-[#4a1f06] - placeholder-[#9a6a3a] - focus:outline-none - disabled:opacity-50 - " - /> + - setPassword(e.target.value)} - required - disabled={loading} - className=" - px-3 py-2 - border-[3px] border-[#7a3e12] - bg-[#fff3d1] - text-[#4a1f06] - placeholder-[#9a6a3a] - focus:outline-none - disabled:opacity-50 - " - /> + - + +
+
diff --git a/CodeQuest.Client/src/pages/StoryPage.tsx b/CodeQuest.Client/src/pages/StoryPage.tsx index 7ef2d92..2e5035d 100644 --- a/CodeQuest.Client/src/pages/StoryPage.tsx +++ b/CodeQuest.Client/src/pages/StoryPage.tsx @@ -255,18 +255,18 @@ The next chapter awaits.`, transition={{ duration: 0.8 }} > {error && ( -
+
{error}
)} {scenario && ( -
-
+
+
-
+
AdvanceAsync( // TODO: Create short feedback text (later: call AI for personalised feedback). // TODO: Derive StoryBranch from outcome and update session.StoryBranch. - session.CurrentChapterNumber = Math.Clamp(session.CurrentChapterNumber + 1, 1, 10); + session.CurrentChapterNumber = session.CurrentChapterNumber >= 10 ? 1 : ++session.CurrentChapterNumber; var nextScenario = await _scenarioService.GenerateNextScenarioAsync(session, outcome, userCode, actualOutput, ct);
- {key} + {key} - + {JSON.stringify(value)}