* feat(ui): add THUMBS question type renderer - Add seven new renderer files under renderers/thumbs/: thumbs-correct.ts, take.svelte, edit.svelte, preview.svelte, thumbs-review-toggle.svelte, review.svelte, submission.svelte - Register THUMBS in renderer-contract.ts with edit/take/preview/submission/review - Export ThumbsEdit, ThumbsTake, ThumbsPreview, ThumbsSubmission, ThumbsReview from exercise-question/index.ts - Add THUMBS_FIXTURE, four thumbs label keys, and Thumbs story in storybook exercise-question files - Add THUMBS case to question-type-modes.svelte submission builder Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * feat(question-types): add THUMBS contracts, scoring, and seed Introduce the THUMBS question type (id 14) with boolean answer data, codec, auto-grading, Zod validation, DB seed/schema docs, and binary normalize support shared with TRUE_FALSE. Dashboard wiring is deferred. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * feat(dashboard): wire THUMBS question type into exercise UI Connect THUMBS option sync in edit/view modes, labels, picker icon, and locale strings so the existing contracts surface in the dashboard. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(ui): polish thumbs question UI and move premium upgrade icon Widen thumbs Yes/No toggles, inherit selected icon color via custom class, replace badge-based review correctness with solid fill states and bare Check/X icons, add a success pill in preview, and place the premium upgrade icon at the end of the question type select row. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(ui): align thumbs review colors with prototypes Tune correct/incorrect selected states so review buttons use dark emerald/red content colors on solid fills, matching the design comps. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(ui): address remaining PR review comments Use a shared timestamp local in thumbs edit fallback options. Move border-beam animation-delay after the animation shorthand. Alias --spacing to --ui-spacing for tw-animate compatibility. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): sync question_type sequence before seeding new types Avoid duplicate key errors when question_type_id_seq lags behind rows inserted via restore or explicit ids (e.g. THUMBS on restored databases). Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(exercise): resolve thumbs review label ICU crash Remove {value} ICU placeholder from thumbs.review.correct_answer_label since labels are resolved at map-build time without context. Append the dynamic yes/no label in thumbs-review-toggle instead. Also bind question points through the questionnaire store index to avoid the Svelte non-reactive property binding warning in edit-mode. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * docs(agent): add THUMBS guidance and sync question type docs Add shared agent question-type guide helpers in @cio/question-types and use them across the teacher prompt, add/update_questions tool schemas, legacy ai-assistant schemas, and MCP exercise tools. - Document when to use THUMBS (Yes/No sentiment) vs TRUE_FALSE (factual) - Fix stale MCP question type ids and add THUMBS=14 - Exclude disabled MATCHING/HOTSPOT from enabled type lists Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(thumbs): clarify auto-grading and add editable option labels THUMBS is auto-graded like TRUE_FALSE; the difference is thumbs UI with customizable Yes/No-style labels and the teacher selecting the correct option. Render take/preview/review from question.options labels and add label inputs in the thumbs edit renderer. Update agent and MCP docs to stop implying THUMBS is manual or non-gradable. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * chore(mcp): bump version to 0.0.10 Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(question-types): make THUMBS open-ended with no correct answer THUMBS is a sentiment/poll question type, not an auto-graded binary like TRUE_FALSE. Remove correct-answer UI, scoring, and validation; update agent and MCP docs accordingly. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(dashboard): stop treating THUMBS as auto-graded in editor validation Normalize thumbs questions on load/save, show an Open-ended badge instead of Auto, and only enforce points/correct-answer rules on auto-gradable types. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * revert(dashboard): remove unsolicited open-ended grading badge THUMBS uses the existing Manual badge like other non-auto-gradable types. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(exercise): show question title validation errors in editor Display mapped title errors beneath the question title input instead of only highlighting the question card border. Pass the exercise entity to mapZodErrorsToTranslations so messages resolve to validations.exercise.* keys, and use a translated snackbar key for save validation failures. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(mcp): build workspace deps before MCP publish bundle The MCP esbuild step imports @cio/question-types and @cio/utils dist outputs. CI failed on a clean checkout because mcp:build only built the MCP package itself. Build those dependencies first so 0.0.10 can publish. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
269 lines
8.5 KiB
CSS
269 lines
8.5 KiB
CSS
@import 'tailwindcss' prefix(ui);
|
|
@import 'tw-animate-css';
|
|
@import './custom/animation/marketing-layers.css';
|
|
|
|
@source './**/*.{html,js,ts,svelte}';
|
|
@source '../node_modules/layerchart/dist';
|
|
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
|
|
/**
|
|
* Shared z-index scale.
|
|
*
|
|
* Tailwind supports arbitrary integer z-index values out of the box (e.g. `z-137`), which is
|
|
* how one-off stacking numbers end up scattered across the app and collide as new layers get
|
|
* added. Prefer these named layers instead so intent is documented at the call site — see
|
|
* `packages/ui/README.md` for the full write-up and when to reach for each one.
|
|
*
|
|
* z-app-bar (100) Sticky/fixed app chrome: top bars, sticky in-page headers, the
|
|
* desktop sidebar rail, the side panel.
|
|
* z-app-bar-elevated (150) A sidebar (mobile sheet or desktop) that must render above other
|
|
* z-app-bar content while open, e.g. the course content sidebar.
|
|
* z-modal (200) Blocking dialogs/drawers.
|
|
* z-menu-elevated (250) Dropdown/select/popover content that must escape a z-app-bar,
|
|
* z-app-bar-elevated, or z-modal ancestor.
|
|
*
|
|
* Anything not listed (dropdowns, sheets, drawers) uses Tailwind's default `z-50`,
|
|
* which already sits above normal page content. Tooltips use `z-menu-elevated` so they
|
|
* remain visible over `z-app-bar` chrome such as the transcript side panel.
|
|
*/
|
|
@utility z-app-bar {
|
|
z-index: 100;
|
|
}
|
|
|
|
@utility z-app-bar-elevated {
|
|
z-index: 150;
|
|
}
|
|
|
|
@utility z-modal {
|
|
z-index: 200;
|
|
}
|
|
|
|
@utility z-menu-elevated {
|
|
z-index: 250;
|
|
}
|
|
|
|
/*
|
|
* `@utility` only emits a class when Tailwind's scanner sees that exact candidate
|
|
* somewhere under `packages/ui/src` — it does not ship every documented layer just because
|
|
* it's declared above. Safelist all four here so the compiled `@cio/ui` CSS always exposes
|
|
* the full scale, regardless of which layers happen to be referenced inside this package
|
|
* today; otherwise a consumer using e.g. `ui:z-menu-elevated` could silently get no rule.
|
|
*/
|
|
@source inline('ui:z-app-bar{,!}');
|
|
@source inline('ui:z-app-bar-elevated{,!}');
|
|
@source inline('ui:z-modal{,!}');
|
|
@source inline('ui:z-menu-elevated{,!}');
|
|
|
|
:root {
|
|
--spacing: var(--ui-spacing);
|
|
--background: oklch(1 0 0);
|
|
--foreground: oklch(0.145 0 0);
|
|
--card: oklch(1 0 0);
|
|
--card-foreground: oklch(0.145 0 0);
|
|
--popover: oklch(1 0 0);
|
|
--popover-foreground: oklch(0.145 0 0);
|
|
--primary: oklch(0.488 0.243 264.376);
|
|
--primary-foreground: oklch(0.97 0.014 254.604);
|
|
--secondary: oklch(0.967 0.001 286.375);
|
|
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
--muted: oklch(0.97 0 0);
|
|
--muted-foreground: oklch(0.556 0 0);
|
|
--accent: oklch(0.97 0 0);
|
|
--accent-foreground: oklch(0.205 0 0);
|
|
--destructive: oklch(0.577 0.245 27.325);
|
|
--border: oklch(0.922 0 0);
|
|
--input: oklch(0.922 0 0);
|
|
--ring: oklch(0.708 0 0);
|
|
--chart-1: oklch(0.809 0.105 251.813);
|
|
--chart-2: oklch(0.623 0.214 259.815);
|
|
--chart-3: oklch(0.546 0.245 262.881);
|
|
--chart-4: oklch(0.488 0.243 264.376);
|
|
--chart-5: oklch(0.424 0.199 265.638);
|
|
--radius: 0.625rem;
|
|
--sidebar: oklch(0.985 0 0);
|
|
--sidebar-foreground: oklch(0.145 0 0);
|
|
--sidebar-primary: oklch(0.546 0.245 262.881);
|
|
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
|
|
--sidebar-accent: oklch(0.97 0 0);
|
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
--sidebar-border: oklch(0.922 0 0);
|
|
--sidebar-ring: oklch(0.708 0 0);
|
|
}
|
|
|
|
.dark {
|
|
--background: oklch(0.145 0 0);
|
|
--foreground: oklch(0.985 0 0);
|
|
--card: oklch(0.205 0 0);
|
|
--card-foreground: oklch(0.985 0 0);
|
|
--popover: oklch(0.205 0 0);
|
|
--popover-foreground: oklch(0.985 0 0);
|
|
--primary: oklch(0.424 0.199 265.638);
|
|
--primary-foreground: oklch(0.97 0.014 254.604);
|
|
--secondary: oklch(0.274 0.006 286.033);
|
|
--secondary-foreground: oklch(0.985 0 0);
|
|
--muted: oklch(0.269 0 0);
|
|
--muted-foreground: oklch(0.708 0 0);
|
|
--accent: oklch(0.269 0 0);
|
|
--accent-foreground: oklch(0.985 0 0);
|
|
--destructive: oklch(0.704 0.191 22.216);
|
|
--border: oklch(1 0 0 / 10%);
|
|
--input: oklch(1 0 0 / 15%);
|
|
--ring: oklch(0.556 0 0);
|
|
--chart-1: oklch(0.809 0.105 251.813);
|
|
--chart-2: oklch(0.623 0.214 259.815);
|
|
--chart-3: oklch(0.546 0.245 262.881);
|
|
--chart-4: oklch(0.488 0.243 264.376);
|
|
--chart-5: oklch(0.424 0.199 265.638);
|
|
--sidebar: oklch(0.205 0 0);
|
|
--sidebar-foreground: oklch(0.985 0 0);
|
|
--sidebar-primary: oklch(0.623 0.214 259.815);
|
|
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
|
|
--sidebar-accent: oklch(0.269 0 0);
|
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
--sidebar-border: oklch(1 0 0 / 10%);
|
|
--sidebar-ring: oklch(0.556 0 0);
|
|
}
|
|
|
|
@theme inline {
|
|
/* --font-cio: 'Geist', Arial, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji, sans-serif; */
|
|
--font-cio: var(--font-sans);
|
|
--default-font-family: var(--font-cio);
|
|
--radius-sm: calc(var(--radius) - 4px);
|
|
--radius-md: calc(var(--radius) - 2px);
|
|
--radius-lg: var(--radius);
|
|
--radius-xl: calc(var(--radius) + 4px);
|
|
--color-background: var(--background);
|
|
--color-foreground: var(--foreground);
|
|
--color-card: var(--card);
|
|
--color-card-foreground: var(--card-foreground);
|
|
--color-popover: var(--popover);
|
|
--color-popover-foreground: var(--popover-foreground);
|
|
--color-primary: var(--primary);
|
|
--color-primary-foreground: var(--primary-foreground);
|
|
--color-secondary: var(--secondary);
|
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
--color-muted: var(--muted);
|
|
--color-muted-foreground: var(--muted-foreground);
|
|
--color-accent: var(--accent);
|
|
--color-accent-foreground: var(--accent-foreground);
|
|
--color-destructive: var(--destructive);
|
|
--color-border: var(--border);
|
|
--color-input: var(--input);
|
|
--color-ring: var(--ring);
|
|
--color-chart-1: var(--chart-1);
|
|
--color-chart-2: var(--chart-2);
|
|
--color-chart-3: var(--chart-3);
|
|
--color-chart-4: var(--chart-4);
|
|
--color-chart-5: var(--chart-5);
|
|
--color-sidebar: var(--sidebar);
|
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
--color-sidebar-primary: var(--sidebar-primary);
|
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
--color-sidebar-accent: var(--sidebar-accent);
|
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
--color-sidebar-border: var(--sidebar-border);
|
|
--color-sidebar-ring: var(--sidebar-ring);
|
|
|
|
/* Course-app style animations (marketing / landing) */
|
|
--animate-meteor: meteor 5s linear infinite;
|
|
--animate-shimmer-marketing: shimmer-marketing 8s infinite;
|
|
--animate-spin-around: spin-around calc(var(--speed) * 2) infinite linear;
|
|
--animate-magicslide: magicslide var(--speed) ease-in-out infinite alternate;
|
|
--animate-flip: flip 6s infinite steps(2, end);
|
|
--animate-kitrotate: kitrotate 3s linear infinite both;
|
|
--animate-shine: shine 2s linear infinite;
|
|
}
|
|
|
|
@layer base {
|
|
* {
|
|
@apply ui:border-border ui:outline-ring/50;
|
|
}
|
|
|
|
body {
|
|
@apply ui:bg-background ui:text-foreground ui:font-cio;
|
|
}
|
|
|
|
button,
|
|
input,
|
|
select,
|
|
optgroup,
|
|
textarea,
|
|
::file-selector-button {
|
|
@apply ui:font-cio;
|
|
}
|
|
|
|
.toaster {
|
|
--normal-bg: var(--color-popover);
|
|
--normal-text: var(--color-popover-foreground);
|
|
--normal-border: var(--color-border);
|
|
}
|
|
}
|
|
|
|
@property --border-angle {
|
|
inherits: false;
|
|
initial-value: 0deg;
|
|
syntax: '<angle>';
|
|
}
|
|
|
|
@keyframes border {
|
|
to {
|
|
--border-angle: 360deg;
|
|
}
|
|
}
|
|
|
|
@keyframes flip {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
@keyframes kitrotate {
|
|
to {
|
|
transform: rotate(90deg);
|
|
}
|
|
}
|
|
|
|
@keyframes shine {
|
|
from {
|
|
background-position: 0 0;
|
|
}
|
|
to {
|
|
background-position: -200% 0;
|
|
}
|
|
}
|
|
|
|
.animate-gradient-border {
|
|
border: 1px solid transparent;
|
|
background:
|
|
linear-gradient(45deg, white, white) padding-box,
|
|
conic-gradient(
|
|
from var(--border-angle),
|
|
rgb(148 163 184 / 0.3) 75%,
|
|
var(--primary, #1d4ee2) 82%,
|
|
var(--primary-foreground, white) 88%,
|
|
var(--primary, #1d4ee2) 92%,
|
|
rgb(148 163 184 / 0.3) 100%
|
|
)
|
|
border-box;
|
|
transition: --border-angle 3s ease-in-out;
|
|
}
|
|
|
|
.animate-gradient-border:hover {
|
|
animation: border 4s ease-in-out infinite;
|
|
}
|
|
|
|
.dark.animate-gradient-border {
|
|
background:
|
|
linear-gradient(45deg, rgb(0 0 0), rgb(0 0 0)) padding-box,
|
|
conic-gradient(
|
|
from var(--border-angle),
|
|
rgb(71 85 105 / 0.48) 75%,
|
|
var(--primary, #1d4ee2) 82%,
|
|
var(--primary-foreground, black) 88%,
|
|
var(--primary, #1d4ee2) 92%,
|
|
rgb(71 85 105 / 0.48) 100%
|
|
)
|
|
border-box;
|
|
}
|