From bcd42cf1575c4328eeedbb513ea23171204446f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20T=C3=BA=20Anh?= Date: Mon, 19 Oct 2026 21:39:31 +0700 Subject: [PATCH 1/3] CKEditor: compile the custom CSS instead of serving Tailwind source django-ckeditor-5 links CKEDITOR_5_CUSTOM_CSS as-is, but base/css/ckeditor5.css was Tailwind source that was never built: browsers dropped every @apply rule (100 of 108 rules were empty) and the theme('colors.*') values made the --ck-color-* variables invalid, so the editor and toolbar backgrounds were transparent and the text stayed black on the dark admin background. - the source moves to ./ckeditor5.css; scripts/tailwind.sh builds it into the same static path, so projects need no settings change - Tailwind 4: v3-only utilities converted (ring, shadow-sm, rounded, outline-none, *-opacity-*), scrollbar plugin classes dropped, theme() replaced by --theme() / var() - dark mode follows unfold's "dark" class like styles.css, instead of the OS setting (prefers-color-scheme) or only data-theme="dark", so the light admin theme stays light and daisyUI dark themes are covered - unfold's base/primary colors are referenced, not redefined - --ck-color-base-border set too (the toolbar panel kept CKEditor's light #ccced1 line in dark mode) - CI checks the built file is committed; a test fails if source syntax reaches the served file again Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 2 +- ckeditor5.css | 575 ++++++++++++++++++++++ scripts/tailwind.sh | 2 + styles.css | 3 + whiteneuron/base/tests_regressions.py | 14 + whiteneuron/static/base/css/ckeditor5.css | 552 +-------------------- 6 files changed, 597 insertions(+), 551 deletions(-) create mode 100644 ckeditor5.css diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c157831..0401d5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,5 +86,5 @@ jobs: - name: Committed files match the build run: | - git diff --exit-code -- daisy-themes.css whiteneuron/base/themes.py whiteneuron/static/base/css/styles.css \ + git diff --exit-code -- daisy-themes.css whiteneuron/base/themes.py whiteneuron/static/base/css/styles.css whiteneuron/static/base/css/ckeditor5.css \ || { echo "::error::Run 'bash scripts/tailwind.sh' and commit the regenerated files."; exit 1; } diff --git a/ckeditor5.css b/ckeditor5.css new file mode 100644 index 0000000..8a0f765 --- /dev/null +++ b/ckeditor5.css @@ -0,0 +1,575 @@ +/* CKEditor 5 styles for Unfold (settings.CKEDITOR_5_CUSTOM_CSS). + Source only: scripts/tailwind.sh builds it into whiteneuron/static/base/css/ckeditor5.css, + the file browsers get (they ignore @apply and --theme()). */ +@layer theme; +@import "tailwindcss/theme" layer(theme); + +/* same dark mode as styles.css: unfold's "dark" class, which daisyUI dark themes also set */ +@custom-variant dark (&:is(.dark, .dark *, [data-theme=dark], [data-theme=dark] *)); + +/* unfold defines these at runtime (UNFOLD["COLORS"], daisy-themes.css): reference, don't redefine */ +@theme inline reference { + --color-base-50: var(--color-base-50); + --color-base-100: var(--color-base-100); + --color-base-200: var(--color-base-200); + --color-base-300: var(--color-base-300); + --color-base-400: var(--color-base-400); + --color-base-500: var(--color-base-500); + --color-base-600: var(--color-base-600); + --color-base-700: var(--color-base-700); + --color-base-800: var(--color-base-800); + --color-base-900: var(--color-base-900); + --color-base-950: var(--color-base-950); + --color-primary-50: var(--color-primary-50); + --color-primary-100: var(--color-primary-100); + --color-primary-200: var(--color-primary-200); + --color-primary-300: var(--color-primary-300); + --color-primary-400: var(--color-primary-400); + --color-primary-500: var(--color-primary-500); + --color-primary-600: var(--color-primary-600); + --color-primary-700: var(--color-primary-700); + --color-primary-800: var(--color-primary-800); + --color-primary-900: var(--color-primary-900); + --color-primary-950: var(--color-primary-950); +} + +/* Container styling to match Unfold form fields */ +.ck-editor-container { + @apply relative w-full; +} + +/* Main editor wrapper */ +.ck.ck-editor { + @apply relative w-full bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xs; + @apply focus-within:border-primary-600 focus-within:ring-3 focus-within:ring-primary-500/40 dark:focus-within:border-primary-600 dark:focus-within:ring-primary-500/30; + @apply transition-all duration-300 ease-in-out; +} + +/* Toolbar styling */ +.ck.ck-toolbar { + @apply bg-base-100 dark:bg-base-900 border-b border-gray-200 dark:border-gray-700; + @apply px-2 py-1.5 rounded-t-lg; +} + +/* Toolbar items container */ +.ck.ck-toolbar__items { + @apply flex flex-wrap items-center gap-1; +} + +/* Toolbar separators */ +.ck.ck-toolbar__separator { + @apply w-px h-6 bg-gray-300 dark:bg-gray-600 mx-1; +} + +/* Buttons styling refined */ +.ck.ck-button { + @apply inline-flex items-center justify-center h-8 min-h-8 px-2.5 py-1.5; + @apply text-[13px] font-medium text-gray-700 dark:text-gray-300 tracking-tight; + @apply bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700/70; + @apply border border-transparent rounded-md; + @apply transition-colors duration-150 ease-in-out; + @apply cursor-pointer select-none; + @apply focus:outline-hidden focus:ring-2 focus:ring-primary-500/40 dark:focus:ring-primary-500/30; +} + +.ck.ck-button:hover { + @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100; +} + +.ck.ck-button.ck-on { + @apply bg-primary-100 dark:bg-primary-700/70 text-primary-700 dark:text-white; + @apply border-primary-300 dark:border-primary-600; +} + +.ck.ck-button.ck-disabled, +.ck.ck-button:disabled { + @apply opacity-50 cursor-not-allowed pointer-events-none; +} + +/* Button icons */ +.ck.ck-button .ck-icon { + @apply w-4 h-4 opacity-90 text-gray-600 dark:text-gray-300; +} + +.ck.ck-button:hover .ck-icon, +.ck.ck-button.ck-on .ck-icon { + @apply opacity-100 text-primary-600 dark:text-white; +} + +/* Dropdown buttons */ +.ck.ck-dropdown__button { + @apply inline-flex items-center gap-2; +} + +.ck.ck-dropdown__button .ck-dropdown__arrow { + @apply w-3 h-3 opacity-60; +} + +/* Split buttons styling */ +.ck.ck-splitbutton .ck-splitbutton__action { + @apply rounded-l-md border-r border-gray-300 dark:border-gray-600; +} + +.ck.ck-splitbutton .ck-splitbutton__arrow { + @apply rounded-r-md px-2; +} + +/* Dropdown panels */ +.ck.ck-dropdown__panel { + @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; + @apply rounded-lg shadow-lg mt-1 p-1 min-w-48; +} + +/* High contrast nested dropdown variants (language, heading, font size) */ +.ck.ck-code-block-dropdown .ck-dropdown__panel, +.ck.ck-font-size-dropdown .ck-dropdown__panel, +.ck.ck-heading-dropdown .ck-dropdown__panel { + @apply bg-white dark:bg-base-900 border border-gray-200 dark:border-gray-700 shadow-xl; +} + +/* Improve list item readability inside panels */ +.ck.ck-dropdown__panel .ck.ck-list .ck-list__item { @apply rounded-md; } +.ck.ck-dropdown__panel .ck.ck-list .ck-list__item + .ck-list__item { @apply mt-0.5; } +.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button { @apply w-full text-left px-3 py-2 text-[13px] font-medium; } +.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover { @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white; } +.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on { @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white; } + +/* Visible check holder */ +.ck.ck-dropdown__panel .ck-list-item-button__check-holder { @apply w-4 h-4 mr-2 flex items-center justify-center text-[11px] rounded-xs border border-gray-300 dark:border-gray-600 bg-white dark:bg-base-800; } + +/* Ensure visibility when panel is shown */ +.ck.ck-dropdown__panel.ck-dropdown__panel-visible { @apply bg-white dark:bg-base-900; } + +/* Scrollbars inside dropdown lists */ +.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar { @apply w-2; } +.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb { @apply bg-gray-300 dark:bg-gray-600 rounded-sm; } + +/* Zebra table rows inside content */ +.ck.ck-content table tbody tr:nth-child(odd) { @apply bg-gray-50 dark:bg-base-800; } +.ck.ck-content table tbody tr:nth-child(even) { @apply bg-white dark:bg-base-900; } +.ck.ck-content table th, .ck.ck-content table td { @apply border border-gray-200 dark:border-gray-700; } + +/* Text selection highlight */ +.ck.ck-content ::selection { background: var(--color-primary-500); color: white; } +.ck.ck-content ::-moz-selection { background: var(--color-primary-500); color: white; } + +/* Main editing area */ +.ck.ck-content { + @apply min-h-48 max-h-[32rem] p-5 bg-white dark:bg-base-800; + @apply text-gray-900 dark:text-gray-100 text-[15px] leading-relaxed; + @apply rounded-b-lg border-none outline-hidden; + @apply overflow-y-auto; + font-family: 'Inter', system-ui, -apple-system, sans-serif; +} + +.ck.ck-content:focus { + @apply outline-hidden ring-0; +} + +/* Content styling within editor */ +.ck.ck-content h1, +.ck.ck-content h2, +.ck.ck-content h3, +.ck.ck-content h4, +.ck.ck-content h5, +.ck.ck-content h6 { + @apply font-semibold text-gray-900 dark:text-gray-100 mb-3 mt-4; +} + +.ck.ck-content h1 { @apply text-2xl; } +.ck.ck-content h2 { @apply text-xl; } +.ck.ck-content h3 { @apply text-lg; } +.ck.ck-content h4 { @apply text-base font-bold; } +.ck.ck-content h5 { @apply text-sm font-bold; } +.ck.ck-content h6 { @apply text-xs font-bold uppercase tracking-wide; } + +.ck.ck-content p { + @apply mb-4 text-gray-700 dark:text-gray-300; +} + +.ck.ck-content blockquote { + /* Refined blockquote styling */ + @apply relative pl-5 py-3 my-5 rounded-md overflow-hidden italic; + @apply bg-gray-50 dark:bg-base-900 text-gray-700 dark:text-gray-300; + @apply border-l-4 border-primary-500; +} + +.ck.ck-content blockquote:before { + content: ""; + @apply absolute left-0 top-0 h-full w-1 bg-primary-500; +} + +.ck.ck-content ul, +.ck.ck-content ol { + @apply ml-6 mb-4; +} + +.ck.ck-content ul { + @apply list-disc; +} + +.ck.ck-content ol { + @apply list-decimal; +} + +.ck.ck-content li { + @apply mb-1 text-gray-700 dark:text-gray-300; +} + +.ck.ck-content a { + @apply text-primary-600 dark:text-primary-500 hover:text-primary-700 dark:hover:text-primary-400; + @apply underline transition-colors duration-200; +} + +.ck.ck-content code { + @apply bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200; + @apply px-2 py-1 rounded-sm font-mono text-sm; +} + +.ck.ck-content pre { + @apply bg-gray-900/90 dark:bg-gray-900 text-gray-100 dark:text-gray-100; + @apply border border-gray-800 dark:border-gray-700; + @apply p-4 rounded-md overflow-x-auto my-5 font-mono text-[13px] leading-snug; +} + +/* Table styling */ +.ck.ck-content table { + @apply w-full border-collapse my-4; +} + +.ck.ck-content table td, +.ck.ck-content table th { + @apply border border-gray-300 dark:border-gray-600 px-3 py-2; +} + +.ck.ck-content table th { + @apply bg-gray-100 dark:bg-gray-700 font-semibold text-gray-900 dark:text-gray-100; +} + +.ck.ck-content table td { + @apply bg-white dark:bg-base-800 text-gray-700 dark:text-gray-300; +} + +/* Image widget styling */ +.ck.ck-content figure.image { + @apply my-4 mx-auto max-w-full; +} + +.ck.ck-content figure.image img { + @apply max-w-full h-auto rounded-lg shadow-md; +} + +/* Widget styling */ +.ck.ck-widget { + @apply relative; +} + +.ck.ck-widget.ck-widget_selected { + @apply ring-2 ring-primary-400/50 rounded-sm; +} + +.ck.ck-widget__type-around__button { + @apply bg-primary-600 hover:bg-primary-700 text-white; + @apply w-6 h-6 rounded-full flex items-center justify-center; + @apply transition-colors duration-200; +} + +/* Resizer styling */ +.ck.ck-widget__resizer { + @apply border border-primary-400 bg-primary-50/20 dark:bg-primary-900/20; +} + +.ck.ck-widget__resizer__handle { + @apply bg-primary-600 w-3 h-3 border border-primary-700 rounded-sm; +} + +/* Placeholder styling */ +.ck.ck-content .ck-placeholder:before { + @apply text-gray-400 dark:text-gray-500 italic; +} + +/* Balloon panels */ +.ck.ck-balloon-panel { + @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; + @apply rounded-lg shadow-lg p-2; +} + +/* Link balloon styling */ +.ck.ck-link-form, +.ck.ck-link-actions { + @apply flex items-center gap-2 p-2; +} + +.ck.ck-link-form .ck-input { + @apply flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600; + @apply bg-white dark:bg-base-800 text-gray-900 dark:text-gray-100; + @apply rounded-md text-sm focus:ring-2 focus:ring-primary-300 focus:border-primary-600; +} + +/* Color picker styling */ +.ck.ck-color-selector { + @apply grid grid-cols-6 gap-1 p-2; +} + +.ck.ck-color-selector .ck-color-selector__option { + @apply w-7 h-7 rounded-md border border-gray-300 dark:border-gray-600 shadow-xs; + @apply hover:scale-110 transition-transform duration-150; +} +.ck.ck-color-selector .ck-color-selector__option.ck-on { + @apply ring-2 ring-primary-500 ring-offset-1 ring-offset-white dark:ring-offset-base-800 border-primary-500; +} + +/* Font size dropdown */ +.ck.ck-font-size-dropdown .ck-dropdown__panel { + @apply max-h-48 overflow-y-auto; +} + +.ck.ck-list .ck-list__item { + @apply px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700; + @apply text-gray-700 dark:text-gray-300 cursor-pointer; + @apply transition-colors duration-200; +} + +.ck.ck-list .ck-list__item.ck-on { + @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white font-medium rounded-sm; +} + +/* Code block dropdown */ +.ck.ck-code-block-dropdown .ck-dropdown__panel { + @apply min-w-56; +} + +/* Media embed styling */ +.ck.ck-content .ck-media__wrapper { + @apply my-4 rounded-lg overflow-hidden shadow-md; +} + +.ck.ck-content .ck-media__placeholder { + @apply bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400; + @apply p-8 text-center rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600; +} + +/* Todo list styling */ +.ck.ck-content .todo-list { + @apply list-none ml-0; +} + +.ck.ck-content .todo-list li { + @apply flex items-start gap-3 mb-2; +} + +.ck.ck-content .todo-list li .todo-list__label { + @apply flex items-center gap-2; +} + +.ck.ck-content .todo-list li .todo-list__label input { + @apply w-4 h-4 text-primary-600 border border-gray-300 dark:border-gray-600 rounded-sm; + @apply focus:ring-2 focus:ring-primary-300; +} + +/* Highlight styling */ +.ck.ck-content mark { + @apply bg-yellow-200 dark:bg-yellow-600 text-gray-900 dark:text-gray-100; + @apply px-1 rounded-sm; +} + +/* Source editing mode */ +.ck.ck-source-editing-area { + @apply w-full min-h-48 p-4 bg-gray-50 dark:bg-gray-900; + @apply text-gray-900 dark:text-gray-100 font-mono text-sm; + @apply border border-gray-300 dark:border-gray-600 rounded-lg; + @apply focus:ring-2 focus:ring-primary-300 focus:border-primary-600; + @apply resize-y; +} + +/* Word count styling */ +.word-count { + @apply mt-2 text-right; +} + +.ck.ck-word-count { + @apply text-xs text-gray-500 dark:text-gray-400 flex gap-4 justify-end; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .ck.ck-toolbar__items { + @apply gap-0.5; + } + + .ck.ck-button { + @apply h-7 min-h-7 px-2 text-xs; + } + + .ck.ck-content { + @apply min-h-32 p-3 text-sm; + } + + .ck.ck-dropdown__panel { + @apply min-w-full; + } +} + +/* Animation improvements */ +@media (prefers-reduced-motion: no-preference) { + .ck.ck-button { + @apply transition-all duration-200 ease-in-out; + } + + .ck.ck-dropdown__panel { + @apply transition-opacity duration-200 ease-in-out; + } + + .ck.ck-widget { + @apply transition-all duration-200 ease-in-out; + } +} + +/* Focus states for accessibility */ +.ck.ck-button:focus-visible { + @apply ring-2 ring-primary-400 ring-offset-1 ring-offset-white dark:ring-offset-base-800; +} + +.ck.ck-content:focus { + @apply ring-0 outline-hidden; +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .ck.ck-button { + @apply border border-gray-400 dark:border-gray-500; + } + + .ck.ck-content { + @apply border border-gray-400 dark:border-gray-500; + } +} + +/* Override default CKEditor colors for better integration */ +:root { + --ck-color-base-background: --theme(--color-white); + --ck-color-base-foreground: --theme(--color-gray-100); + --ck-color-focus-border: var(--color-primary-600); + --ck-color-text: --theme(--color-gray-900); + --ck-color-shadow-drop: rgba(0, 0, 0, 0.15); + --ck-color-shadow-inner: rgba(0, 0, 0, 0.1); + --ck-color-button-default-background: transparent; + --ck-color-button-default-hover-background: --theme(--color-gray-100); + --ck-color-button-on-background: var(--color-primary-100); + --ck-color-button-on-hover-background: var(--color-primary-200); + --ck-color-dropdown-panel-background: --theme(--color-white); + --ck-color-dropdown-panel-border: --theme(--color-gray-200); + --ck-color-dialog-background: --theme(--color-white); + --ck-color-dialog-form-header-border: --theme(--color-gray-200); + --ck-color-input-background: --theme(--color-white); + --ck-color-input-border: --theme(--color-gray-300); + --ck-color-input-error-border: --theme(--color-red-500); + --ck-color-list-background: --theme(--color-white); + --ck-color-list-button-hover-background: --theme(--color-gray-100); + --ck-color-list-button-on-background: var(--color-primary-100); + --ck-color-panel-background: --theme(--color-white); + --ck-color-panel-border: --theme(--color-gray-200); + --ck-color-toolbar-background: --theme(--color-gray-50); + --ck-color-toolbar-border: --theme(--color-gray-200); + --ck-color-base-border: --theme(--color-gray-200); + --ck-color-tooltip-background: --theme(--color-gray-800); + --ck-color-tooltip-text: --theme(--color-white); + --ck-color-engine-placeholder-text: --theme(--color-gray-400); + --ck-color-upload-bar-background: var(--color-primary-600); + --ck-color-link-default: var(--color-primary-600); + --ck-color-link-selected-background: var(--color-primary-100); +} + +.dark, +[data-theme="dark"] { + --ck-color-base-background: var(--color-base-800); + --ck-color-base-foreground: var(--color-base-700); + --ck-color-focus-border: var(--color-primary-600); + --ck-color-text: --theme(--color-gray-100); + --ck-color-shadow-drop: rgba(0, 0, 0, 0.3); + --ck-color-shadow-inner: rgba(0, 0, 0, 0.2); + --ck-color-button-default-background: transparent; + --ck-color-button-default-hover-background: --theme(--color-gray-700); + --ck-color-button-on-background: var(--color-primary-600); + --ck-color-button-on-hover-background: var(--color-primary-700); + --ck-color-dropdown-panel-background: var(--color-base-800); + --ck-color-dropdown-panel-border: --theme(--color-gray-600); + --ck-color-dialog-background: var(--color-base-800); + --ck-color-dialog-form-header-border: --theme(--color-gray-600); + --ck-color-input-background: var(--color-base-800); + --ck-color-input-border: --theme(--color-gray-600); + --ck-color-input-error-border: --theme(--color-red-500); + --ck-color-list-background: var(--color-base-800); + --ck-color-list-button-hover-background: --theme(--color-gray-700); + --ck-color-list-button-on-background: var(--color-primary-600); + --ck-color-panel-background: var(--color-base-800); + --ck-color-panel-border: --theme(--color-gray-600); + --ck-color-toolbar-background: var(--color-base-900); + --ck-color-toolbar-border: --theme(--color-gray-700); + --ck-color-base-border: --theme(--color-gray-700); + --ck-color-tooltip-background: --theme(--color-gray-700); + --ck-color-tooltip-text: --theme(--color-white); + --ck-color-engine-placeholder-text: --theme(--color-gray-500); + --ck-color-upload-bar-background: var(--color-primary-600); + --ck-color-link-default: var(--color-primary-500); + --ck-color-link-selected-background: var(--color-primary-800); +} + +/* Animation keyframes */ +@keyframes ck-fade-in { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Apply fade-in animation to dropdowns */ +.ck.ck-dropdown__panel { + animation: ck-fade-in 0.2s ease-out; +} + +/* Custom scrollbar for content area */ +.ck.ck-content::-webkit-scrollbar { + @apply w-2; +} + +.ck.ck-content::-webkit-scrollbar-track { + @apply bg-gray-100 dark:bg-gray-800 rounded-sm; +} + +.ck.ck-content::-webkit-scrollbar-thumb { + @apply bg-gray-400 dark:bg-gray-600 rounded-sm hover:bg-gray-500 dark:hover:bg-gray-500; +} + +/* Ensure proper spacing for nested editor containers */ +.ck-editor-container .ck-editor-container { + @apply mt-0; +} + +/* Integration with Django form styling */ +.form-row .ck-editor-container, +.field-box .ck-editor-container { + @apply w-full; +} + +/* Error state styling */ +.errors .ck.ck-editor { + @apply border-red-600 dark:border-red-500; +} + +.errors .ck.ck-editor:focus-within { + @apply ring-red-200 dark:ring-red-600/40 border-red-600 dark:border-red-500; +} + +/* Success state styling */ +.success .ck.ck-editor { + @apply border-green-600 dark:border-green-500; +} + +.success .ck.ck-editor:focus-within { + @apply ring-green-200 dark:ring-green-600/40 border-green-600 dark:border-green-500; +} diff --git a/scripts/tailwind.sh b/scripts/tailwind.sh index 9f98616..42aa5ff 100755 --- a/scripts/tailwind.sh +++ b/scripts/tailwind.sh @@ -10,3 +10,5 @@ ln -sfn "$MD2HTML" .tw-md2html trap 'rm -f .tw-md2html' EXIT npx @tailwindcss/cli -i styles.css -o whiteneuron/static/base/css/styles.css --minify +# CKEDITOR_5_CUSTOM_CSS: served as-is by django-ckeditor-5, so its @apply must be compiled here +npx @tailwindcss/cli -i ckeditor5.css -o whiteneuron/static/base/css/ckeditor5.css --minify diff --git a/styles.css b/styles.css index aa3f332..625b7cf 100755 --- a/styles.css +++ b/styles.css @@ -8,6 +8,9 @@ @source not "./*.md"; /* its own output: a removed utility would otherwise keep its theme variable alive, so rebuilds drift */ @source not "./whiteneuron/static/base/css/styles.css"; +/* CKEditor styles are a separate build (ckeditor5.css): their @apply classes are not page markup */ +@source not "./ckeditor5.css"; +@source not "./whiteneuron/static/base/css/ckeditor5.css"; /* All daisyUI themes + unfold color mapping (generated by scripts/gen-themes.mjs) */ @import "./daisy-themes.css"; diff --git a/whiteneuron/base/tests_regressions.py b/whiteneuron/base/tests_regressions.py index 96f0009..3ac8e12 100644 --- a/whiteneuron/base/tests_regressions.py +++ b/whiteneuron/base/tests_regressions.py @@ -712,3 +712,17 @@ def broken(**kwargs): with self.settings(MIDDLEWARE=middleware), self.assertLogs("whiteneuron.base.middleware", level="ERROR"): page = self.client.get(reverse("admin:login"), {"next": "/admin/"}) self.assertEqual(page.status_code, 200) + + +class CKEditorCustomCssTests(TestCase): + def test_served_css_is_compiled(self): + # django-ckeditor-5 links CKEDITOR_5_CUSTOM_CSS as-is: the file used to be Tailwind source, so + # browsers dropped every @apply rule and theme() left the editor transparent (unreadable in dark mode) + from django.conf import settings + from django.contrib.staticfiles import finders + + with open(finders.find(settings.CKEDITOR_5_CUSTOM_CSS), encoding="utf-8") as f: + css = f.read() + for source_only in ("@apply", "theme(", "@custom-variant"): + self.assertNotIn(source_only, css) + self.assertIn(".ck.ck-editor{", css) diff --git a/whiteneuron/static/base/css/ckeditor5.css b/whiteneuron/static/base/css/ckeditor5.css index af3e550..5063b78 100644 --- a/whiteneuron/static/base/css/ckeditor5.css +++ b/whiteneuron/static/base/css/ckeditor5.css @@ -1,550 +1,2 @@ -/* CKEditor5 Custom Styles for Unfold Theme with Tailwind CSS 3 & DaisyUI 4 */ - -/* Container styling to match Unfold form fields */ -.ck-editor-container { - @apply relative w-full; -} - -/* Main editor wrapper */ -.ck.ck-editor { - @apply relative w-full bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm; - @apply focus-within:border-primary-600 focus-within:ring focus-within:ring-primary-500/40 dark:focus-within:border-primary-600 dark:focus-within:ring-primary-500/30; - @apply transition-all duration-300 ease-in-out; -} - -/* Toolbar styling */ -.ck.ck-toolbar { - @apply bg-base-100 dark:bg-base-900 border-b border-gray-200 dark:border-gray-700; - @apply px-2 py-1.5 rounded-t-lg; -} - -/* Toolbar items container */ -.ck.ck-toolbar__items { - @apply flex flex-wrap items-center gap-1; -} - -/* Toolbar separators */ -.ck.ck-toolbar__separator { - @apply w-px h-6 bg-gray-300 dark:bg-gray-600 mx-1; -} - -/* Buttons styling refined */ -.ck.ck-button { - @apply inline-flex items-center justify-center h-8 min-h-8 px-2.5 py-1.5; - @apply text-[13px] font-medium text-gray-700 dark:text-gray-300 tracking-tight; - @apply bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700/70; - @apply border border-transparent rounded-md; - @apply transition-colors duration-150 ease-in-out; - @apply cursor-pointer select-none; - @apply focus:outline-none focus:ring-2 focus:ring-primary-500/40 dark:focus:ring-primary-500/30; -} - -.ck.ck-button:hover { - @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100; -} - -.ck.ck-button.ck-on { - @apply bg-primary-100 dark:bg-primary-700/70 text-primary-700 dark:text-white; - @apply border-primary-300 dark:border-primary-600; -} - -.ck.ck-button.ck-disabled, -.ck.ck-button:disabled { - @apply opacity-50 cursor-not-allowed pointer-events-none; -} - -/* Button icons */ -.ck.ck-button .ck-icon { - @apply w-4 h-4 opacity-90 text-gray-600 dark:text-gray-300; -} - -.ck.ck-button:hover .ck-icon, -.ck.ck-button.ck-on .ck-icon { - @apply opacity-100 text-primary-600 dark:text-white; -} - -/* Dropdown buttons */ -.ck.ck-dropdown__button { - @apply inline-flex items-center gap-2; -} - -.ck.ck-dropdown__button .ck-dropdown__arrow { - @apply w-3 h-3 opacity-60; -} - -/* Split buttons styling */ -.ck.ck-splitbutton .ck-splitbutton__action { - @apply rounded-l-md border-r border-gray-300 dark:border-gray-600; -} - -.ck.ck-splitbutton .ck-splitbutton__arrow { - @apply rounded-r-md px-2; -} - -/* Dropdown panels */ -.ck.ck-dropdown__panel { - @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; - @apply rounded-lg shadow-lg mt-1 p-1 min-w-48; -} - -/* High contrast nested dropdown variants (language, heading, font size) */ -.ck.ck-code-block-dropdown .ck-dropdown__panel, -.ck.ck-font-size-dropdown .ck-dropdown__panel, -.ck.ck-heading-dropdown .ck-dropdown__panel { - @apply bg-white dark:bg-base-900 border border-gray-200 dark:border-gray-700 shadow-xl; -} - -/* Improve list item readability inside panels */ -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item { @apply rounded-md; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item + .ck-list__item { @apply mt-0.5; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button { @apply w-full text-left px-3 py-2 text-[13px] font-medium; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover { @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on { @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white; } - -/* Visible check holder */ -.ck.ck-dropdown__panel .ck-list-item-button__check-holder { @apply w-4 h-4 mr-2 flex items-center justify-center text-[11px] rounded-sm border border-gray-300 dark:border-gray-600 bg-white dark:bg-base-800; } - -/* Ensure visibility when panel is shown */ -.ck.ck-dropdown__panel.ck-dropdown__panel-visible { @apply bg-white dark:bg-base-900; } - -/* Scrollbars inside dropdown lists */ -.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar { @apply w-2; } -.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb { @apply bg-gray-300 dark:bg-gray-600 rounded; } - -/* Zebra table rows inside content */ -.ck.ck-content table tbody tr:nth-child(odd) { @apply bg-gray-50 dark:bg-base-800; } -.ck.ck-content table tbody tr:nth-child(even) { @apply bg-white dark:bg-base-900; } -.ck.ck-content table th, .ck.ck-content table td { @apply border border-gray-200 dark:border-gray-700; } - -/* Text selection highlight */ -.ck.ck-content ::selection { background: theme('colors.primary.500'); color: white; } -.ck.ck-content ::-moz-selection { background: theme('colors.primary.500'); color: white; } - -/* Main editing area */ -.ck.ck-content { - @apply min-h-48 max-h-[32rem] p-5 bg-white dark:bg-base-800; - @apply text-gray-900 dark:text-gray-100 text-[15px] leading-relaxed; - @apply rounded-b-lg border-none outline-none; - @apply overflow-y-auto; - font-family: 'Inter', system-ui, -apple-system, sans-serif; -} - -.ck.ck-content:focus { - @apply outline-none ring-0; -} - -/* Content styling within editor */ -.ck.ck-content h1, -.ck.ck-content h2, -.ck.ck-content h3, -.ck.ck-content h4, -.ck.ck-content h5, -.ck.ck-content h6 { - @apply font-semibold text-gray-900 dark:text-gray-100 mb-3 mt-4; -} - -.ck.ck-content h1 { @apply text-2xl; } -.ck.ck-content h2 { @apply text-xl; } -.ck.ck-content h3 { @apply text-lg; } -.ck.ck-content h4 { @apply text-base font-bold; } -.ck.ck-content h5 { @apply text-sm font-bold; } -.ck.ck-content h6 { @apply text-xs font-bold uppercase tracking-wide; } - -.ck.ck-content p { - @apply mb-4 text-gray-700 dark:text-gray-300; -} - -.ck.ck-content blockquote { - /* Refined blockquote styling */ - @apply relative pl-5 py-3 my-5 rounded-md overflow-hidden italic; - @apply bg-gray-50 dark:bg-base-900 text-gray-700 dark:text-gray-300; - @apply border-l-4 border-primary-500; -} - -.ck.ck-content blockquote:before { - content: ""; - @apply absolute left-0 top-0 h-full w-1 bg-primary-500; -} - -.ck.ck-content ul, -.ck.ck-content ol { - @apply ml-6 mb-4; -} - -.ck.ck-content ul { - @apply list-disc; -} - -.ck.ck-content ol { - @apply list-decimal; -} - -.ck.ck-content li { - @apply mb-1 text-gray-700 dark:text-gray-300; -} - -.ck.ck-content a { - @apply text-primary-600 dark:text-primary-500 hover:text-primary-700 dark:hover:text-primary-400; - @apply underline transition-colors duration-200; -} - -.ck.ck-content code { - @apply bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200; - @apply px-2 py-1 rounded font-mono text-sm; -} - -.ck.ck-content pre { - @apply bg-gray-900/90 dark:bg-gray-900 text-gray-100 dark:text-gray-100; - @apply border border-gray-800 dark:border-gray-700; - @apply p-4 rounded-md overflow-x-auto my-5 font-mono text-[13px] leading-snug; -} - -/* Table styling */ -.ck.ck-content table { - @apply w-full border-collapse my-4; -} - -.ck.ck-content table td, -.ck.ck-content table th { - @apply border border-gray-300 dark:border-gray-600 px-3 py-2; -} - -.ck.ck-content table th { - @apply bg-gray-100 dark:bg-gray-700 font-semibold text-gray-900 dark:text-gray-100; -} - -.ck.ck-content table td { - @apply bg-white dark:bg-base-800 text-gray-700 dark:text-gray-300; -} - -/* Image widget styling */ -.ck.ck-content figure.image { - @apply my-4 mx-auto max-w-full; -} - -.ck.ck-content figure.image img { - @apply max-w-full h-auto rounded-lg shadow-md; -} - -/* Widget styling */ -.ck.ck-widget { - @apply relative; -} - -.ck.ck-widget.ck-widget_selected { - @apply ring-2 ring-primary-400 ring-opacity-50 rounded; -} - -.ck.ck-widget__type-around__button { - @apply bg-primary-600 hover:bg-primary-700 text-white; - @apply w-6 h-6 rounded-full flex items-center justify-center; - @apply transition-colors duration-200; -} - -/* Resizer styling */ -.ck.ck-widget__resizer { - @apply border border-primary-400 bg-primary-50 dark:bg-primary-900 bg-opacity-20; -} - -.ck.ck-widget__resizer__handle { - @apply bg-primary-600 w-3 h-3 border border-primary-700 rounded; -} - -/* Placeholder styling */ -.ck.ck-content .ck-placeholder:before { - @apply text-gray-400 dark:text-gray-500 italic; -} - -/* Balloon panels */ -.ck.ck-balloon-panel { - @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; - @apply rounded-lg shadow-lg p-2; -} - -/* Link balloon styling */ -.ck.ck-link-form, -.ck.ck-link-actions { - @apply flex items-center gap-2 p-2; -} - -.ck.ck-link-form .ck-input { - @apply flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600; - @apply bg-white dark:bg-base-800 text-gray-900 dark:text-gray-100; - @apply rounded-md text-sm focus:ring-2 focus:ring-primary-300 focus:border-primary-600; -} - -/* Color picker styling */ -.ck.ck-color-selector { - @apply grid grid-cols-6 gap-1 p-2; -} - -.ck.ck-color-selector .ck-color-selector__option { - @apply w-7 h-7 rounded-md border border-gray-300 dark:border-gray-600 shadow-sm; - @apply hover:scale-110 transition-transform duration-150; -} -.ck.ck-color-selector .ck-color-selector__option.ck-on { - @apply ring-2 ring-primary-500 ring-offset-1 ring-offset-white dark:ring-offset-base-800 border-primary-500; -} - -/* Font size dropdown */ -.ck.ck-font-size-dropdown .ck-dropdown__panel { - @apply max-h-48 overflow-y-auto; -} - -.ck.ck-list .ck-list__item { - @apply px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700; - @apply text-gray-700 dark:text-gray-300 cursor-pointer; - @apply transition-colors duration-200; -} - -.ck.ck-list .ck-list__item.ck-on { - @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white font-medium rounded; -} - -/* Code block dropdown */ -.ck.ck-code-block-dropdown .ck-dropdown__panel { - @apply min-w-56; -} - -/* Media embed styling */ -.ck.ck-content .ck-media__wrapper { - @apply my-4 rounded-lg overflow-hidden shadow-md; -} - -.ck.ck-content .ck-media__placeholder { - @apply bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400; - @apply p-8 text-center rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600; -} - -/* Todo list styling */ -.ck.ck-content .todo-list { - @apply list-none ml-0; -} - -.ck.ck-content .todo-list li { - @apply flex items-start gap-3 mb-2; -} - -.ck.ck-content .todo-list li .todo-list__label { - @apply flex items-center gap-2; -} - -.ck.ck-content .todo-list li .todo-list__label input { - @apply w-4 h-4 text-primary-600 border border-gray-300 dark:border-gray-600 rounded; - @apply focus:ring-2 focus:ring-primary-300; -} - -/* Highlight styling */ -.ck.ck-content mark { - @apply bg-yellow-200 dark:bg-yellow-600 text-gray-900 dark:text-gray-100; - @apply px-1 rounded; -} - -/* Source editing mode */ -.ck.ck-source-editing-area { - @apply w-full min-h-48 p-4 bg-gray-50 dark:bg-gray-900; - @apply text-gray-900 dark:text-gray-100 font-mono text-sm; - @apply border border-gray-300 dark:border-gray-600 rounded-lg; - @apply focus:ring-2 focus:ring-primary-300 focus:border-primary-600; - @apply resize-y; -} - -/* Word count styling */ -.word-count { - @apply mt-2 text-right; -} - -.ck.ck-word-count { - @apply text-xs text-gray-500 dark:text-gray-400 flex gap-4 justify-end; -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .ck.ck-toolbar__items { - @apply gap-0.5; - } - - .ck.ck-button { - @apply h-7 min-h-7 px-2 text-xs; - } - - .ck.ck-content { - @apply min-h-32 p-3 text-sm; - } - - .ck.ck-dropdown__panel { - @apply min-w-full; - } -} - -/* Animation improvements */ -@media (prefers-reduced-motion: no-preference) { - .ck.ck-button { - @apply transition-all duration-200 ease-in-out; - } - - .ck.ck-dropdown__panel { - @apply transition-opacity duration-200 ease-in-out; - } - - .ck.ck-widget { - @apply transition-all duration-200 ease-in-out; - } -} - -/* Dark mode specific adjustments */ -@media (prefers-color-scheme: dark) { - .ck.ck-editor { - @apply border-gray-600; - } - - .ck.ck-content { - @apply scrollbar-thin scrollbar-track-gray-800 scrollbar-thumb-gray-600; - } -} - -/* Focus states for accessibility */ -.ck.ck-button:focus-visible { - @apply ring-2 ring-primary-400 ring-offset-1 ring-offset-white dark:ring-offset-base-800; -} - -.ck.ck-content:focus { - @apply ring-0 outline-none; -} - -/* High contrast mode support */ -@media (prefers-contrast: high) { - .ck.ck-button { - @apply border border-gray-400 dark:border-gray-500; - } - - .ck.ck-content { - @apply border border-gray-400 dark:border-gray-500; - } -} - -/* Override default CKEditor colors for better integration */ -:root { - --ck-color-base-background: theme('colors.white'); - --ck-color-base-foreground: theme('colors.gray.100'); - --ck-color-focus-border: theme('colors.primary.600'); - --ck-color-text: theme('colors.gray.900'); - --ck-color-shadow-drop: rgba(0, 0, 0, 0.15); - --ck-color-shadow-inner: rgba(0, 0, 0, 0.1); - --ck-color-button-default-background: transparent; - --ck-color-button-default-hover-background: theme('colors.gray.100'); - --ck-color-button-on-background: theme('colors.primary.100'); - --ck-color-button-on-hover-background: theme('colors.primary.200'); - --ck-color-dropdown-panel-background: theme('colors.white'); - --ck-color-dropdown-panel-border: theme('colors.gray.200'); - --ck-color-dialog-background: theme('colors.white'); - --ck-color-dialog-form-header-border: theme('colors.gray.200'); - --ck-color-input-background: theme('colors.white'); - --ck-color-input-border: theme('colors.gray.300'); - --ck-color-input-error-border: theme('colors.red.500'); - --ck-color-list-background: theme('colors.white'); - --ck-color-list-button-hover-background: theme('colors.gray.100'); - --ck-color-list-button-on-background: theme('colors.primary.100'); - --ck-color-panel-background: theme('colors.white'); - --ck-color-panel-border: theme('colors.gray.200'); - --ck-color-toolbar-background: theme('colors.gray.50'); - --ck-color-toolbar-border: theme('colors.gray.200'); - --ck-color-tooltip-background: theme('colors.gray.800'); - --ck-color-tooltip-text: theme('colors.white'); - --ck-color-engine-placeholder-text: theme('colors.gray.400'); - --ck-color-upload-bar-background: theme('colors.primary.600'); - --ck-color-link-default: theme('colors.primary.600'); - --ck-color-link-selected-background: theme('colors.primary.100'); -} - -[data-theme="dark"] { - --ck-color-base-background: theme('colors.base.800'); - --ck-color-base-foreground: theme('colors.base.700'); - --ck-color-focus-border: theme('colors.primary.600'); - --ck-color-text: theme('colors.gray.100'); - --ck-color-shadow-drop: rgba(0, 0, 0, 0.3); - --ck-color-shadow-inner: rgba(0, 0, 0, 0.2); - --ck-color-button-default-background: transparent; - --ck-color-button-default-hover-background: theme('colors.gray.700'); - --ck-color-button-on-background: theme('colors.primary.600'); - --ck-color-button-on-hover-background: theme('colors.primary.700'); - --ck-color-dropdown-panel-background: theme('colors.base.800'); - --ck-color-dropdown-panel-border: theme('colors.gray.600'); - --ck-color-dialog-background: theme('colors.base.800'); - --ck-color-dialog-form-header-border: theme('colors.gray.600'); - --ck-color-input-background: theme('colors.base.800'); - --ck-color-input-border: theme('colors.gray.600'); - --ck-color-input-error-border: theme('colors.red.500'); - --ck-color-list-background: theme('colors.base.800'); - --ck-color-list-button-hover-background: theme('colors.gray.700'); - --ck-color-list-button-on-background: theme('colors.primary.600'); - --ck-color-panel-background: theme('colors.base.800'); - --ck-color-panel-border: theme('colors.gray.600'); - --ck-color-toolbar-background: theme('colors.base.900'); - --ck-color-toolbar-border: theme('colors.gray.700'); - --ck-color-tooltip-background: theme('colors.gray.700'); - --ck-color-tooltip-text: theme('colors.white'); - --ck-color-engine-placeholder-text: theme('colors.gray.500'); - --ck-color-upload-bar-background: theme('colors.primary.600'); - --ck-color-link-default: theme('colors.primary.500'); - --ck-color-link-selected-background: theme('colors.primary.800'); -} - -/* Animation keyframes */ -@keyframes ck-fade-in { - from { - opacity: 0; - transform: translateY(-4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Apply fade-in animation to dropdowns */ -.ck.ck-dropdown__panel { - animation: ck-fade-in 0.2s ease-out; -} - -/* Custom scrollbar for content area */ -.ck.ck-content::-webkit-scrollbar { - @apply w-2; -} - -.ck.ck-content::-webkit-scrollbar-track { - @apply bg-gray-100 dark:bg-gray-800 rounded; -} - -.ck.ck-content::-webkit-scrollbar-thumb { - @apply bg-gray-400 dark:bg-gray-600 rounded hover:bg-gray-500 dark:hover:bg-gray-500; -} - -/* Ensure proper spacing for nested editor containers */ -.ck-editor-container .ck-editor-container { - @apply mt-0; -} - -/* Integration with Django form styling */ -.form-row .ck-editor-container, -.field-box .ck-editor-container { - @apply w-full; -} - -/* Error state styling */ -.errors .ck.ck-editor { - @apply border-red-600 dark:border-red-500; -} - -.errors .ck.ck-editor:focus-within { - @apply ring-red-200 dark:ring-red-600/40 border-red-600 dark:border-red-500; -} - -/* Success state styling */ -.success .ck.ck-editor { - @apply border-green-600 dark:border-green-500; -} - -.success .ck.ck-editor:focus-within { - @apply ring-green-200 dark:ring-green-600/40 border-green-600 dark:border-green-500; -} +/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-leading:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-600:oklch(68.1% .162 75.834);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-snug:1.375;--leading-relaxed:1.625;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}}.ck-editor-container{width:100%;position:relative}.ck.ck-editor{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);width:100%;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:relative}.ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-800)}.ck.ck-editor:focus-within{border-color:var(--color-primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-editor:focus-within{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)40%,transparent)}}.ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus-within{border-color:var(--color-primary-600);--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus-within{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)30%,transparent)}}.ck.ck-editor{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out)}.ck.ck-toolbar{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-base-100)}.ck.ck-toolbar:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-900)}.ck.ck-toolbar{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1.5)}.ck.ck-toolbar__items{align-items:center;gap:calc(var(--spacing)*1);flex-wrap:wrap;display:flex}.ck.ck-toolbar__separator{margin-inline:calc(var(--spacing)*1);height:calc(var(--spacing)*6);background-color:var(--color-gray-300);width:1px}.ck.ck-toolbar__separator:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-600)}.ck.ck-button{height:calc(var(--spacing)*8);min-height:calc(var(--spacing)*8);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-700);justify-content:center;align-items:center;display:inline-flex}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-button{background-color:#0000}@media (hover:hover){.ck.ck-button:hover{background-color:var(--color-gray-100)}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{background-color:#364153b3}@supports (color:color-mix(in lab, red, red)){.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{background-color:color-mix(in oklab,var(--color-gray-700)70%,transparent)}}}.ck.ck-button{border-radius:var(--radius-md);border-style:var(--tw-border-style);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;--tw-ease:var(--ease-in-out);transition-duration:.15s;transition-timing-function:var(--ease-in-out);cursor:pointer;-webkit-user-select:none;user-select:none;border-width:1px;border-color:#0000}.ck.ck-button:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-button:focus{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)40%,transparent)}}.ck.ck-button:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.ck.ck-button:focus{outline-offset:2px;outline:2px solid #0000}}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus{--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)30%,transparent)}}.ck.ck-button:hover{background-color:var(--color-gray-100);color:var(--color-gray-900)}.ck.ck-button:hover:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700);color:var(--color-gray-100)}.ck.ck-button.ck-on{background-color:var(--color-primary-100);color:var(--color-primary-700)}.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-700)}@supports (color:color-mix(in lab, red, red)){.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-primary-700)70%,transparent)}}.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-white)}.ck.ck-button.ck-on{border-color:var(--color-primary-300)}.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-primary-600)}.ck.ck-button.ck-disabled,.ck.ck-button:disabled{pointer-events:none;cursor:not-allowed;opacity:.5}.ck.ck-button .ck-icon{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);color:var(--color-gray-600);opacity:.9}.ck.ck-button .ck-icon:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-button:hover .ck-icon,.ck.ck-button.ck-on .ck-icon{color:var(--color-primary-600);opacity:1}:is(.ck.ck-button:hover .ck-icon,.ck.ck-button.ck-on .ck-icon):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-white)}.ck.ck-dropdown__button{align-items:center;gap:calc(var(--spacing)*2);display:inline-flex}.ck.ck-dropdown__button .ck-dropdown__arrow{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3);opacity:.6}.ck.ck-splitbutton .ck-splitbutton__action{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md);border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-gray-300)}.ck.ck-splitbutton .ck-splitbutton__action:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-splitbutton .ck-splitbutton__arrow{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);padding-inline:calc(var(--spacing)*2)}.ck.ck-dropdown__panel{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white)}.ck.ck-dropdown__panel:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-800)}.ck.ck-dropdown__panel{margin-top:calc(var(--spacing)*1);min-width:calc(var(--spacing)*48);border-radius:var(--radius-lg);padding:calc(var(--spacing)*1);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-code-block-dropdown .ck-dropdown__panel,.ck.ck-font-size-dropdown .ck-dropdown__panel,.ck.ck-heading-dropdown .ck-dropdown__panel{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}:is(.ck.ck-code-block-dropdown .ck-dropdown__panel,.ck.ck-font-size-dropdown .ck-dropdown__panel,.ck.ck-heading-dropdown .ck-dropdown__panel):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-900)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item{border-radius:var(--radius-md)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item+.ck-list__item{margin-top:calc(var(--spacing)*.5)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button{width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);text-align:left;--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover{background-color:var(--color-gray-100);color:var(--color-gray-900)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700);color:var(--color-white)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on{background-color:var(--color-primary-100);color:var(--color-primary-700)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-700);color:var(--color-white)}.ck.ck-dropdown__panel .ck-list-item-button__check-holder{margin-right:calc(var(--spacing)*2);height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);border-radius:var(--radius-xs);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);justify-content:center;align-items:center;font-size:11px;display:flex}.ck.ck-dropdown__panel .ck-list-item-button__check-holder:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600);background-color:var(--color-base-800)}.ck.ck-dropdown__panel.ck-dropdown__panel-visible{background-color:var(--color-white)}.ck.ck-dropdown__panel.ck-dropdown__panel-visible:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-900)}.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar{width:calc(var(--spacing)*2)}.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb{border-radius:var(--radius-sm);background-color:var(--color-gray-300)}.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb:is(){background-color:var(--color-gray-600)}.ck.ck-content table tbody tr:nth-child(odd){background-color:var(--color-gray-50)}.ck.ck-content table tbody tr:nth-child(odd):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800)}.ck.ck-content table tbody tr:nth-child(2n){background-color:var(--color-white)}.ck.ck-content table tbody tr:nth-child(2n):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-900)}.ck.ck-content table th,.ck.ck-content table td{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}:is(.ck.ck-content table th,.ck.ck-content table td):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700)}.ck.ck-content ::selection{background:var(--color-primary-500);color:#fff}.ck.ck-content{max-height:32rem;min-height:calc(var(--spacing)*48);background-color:var(--color-white);padding:calc(var(--spacing)*5)}.ck.ck-content:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800)}.ck.ck-content{--tw-leading:var(--leading-relaxed);font-size:15px;line-height:var(--leading-relaxed);color:var(--color-gray-900)}.ck.ck-content:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.ck.ck-content{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg);--tw-border-style:none;--tw-outline-style:none;border-style:none;outline-style:none}@media (forced-colors:active){.ck.ck-content{outline-offset:2px;outline:2px solid #0000}}.ck.ck-content{font-family:Inter,system-ui,-apple-system,sans-serif;overflow-y:auto}@media (forced-colors:active){.ck.ck-content:focus{outline-offset:2px;outline:2px solid #0000}}.ck.ck-content h1,.ck.ck-content h2,.ck.ck-content h3,.ck.ck-content h4,.ck.ck-content h5,.ck.ck-content h6{margin-top:calc(var(--spacing)*4);margin-bottom:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}:is(.ck.ck-content h1,.ck.ck-content h2,.ck.ck-content h3,.ck.ck-content h4,.ck.ck-content h5,.ck.ck-content h6):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.ck.ck-content h1{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.ck.ck-content h2{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.ck.ck-content h3{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.ck.ck-content h4{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.ck.ck-content h5{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.ck.ck-content h6{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ck.ck-content p{margin-bottom:calc(var(--spacing)*4);color:var(--color-gray-700)}.ck.ck-content p:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-content blockquote{margin-block:calc(var(--spacing)*5);border-radius:var(--radius-md);padding-block:calc(var(--spacing)*3);padding-left:calc(var(--spacing)*5);background-color:var(--color-gray-50);color:var(--color-gray-700);font-style:italic;position:relative;overflow:hidden}.ck.ck-content blockquote:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-900);color:var(--color-gray-300)}.ck.ck-content blockquote{border-left-style:var(--tw-border-style);border-left-width:4px;border-color:var(--color-primary-500)}.ck.ck-content blockquote:before{content:"";top:calc(var(--spacing)*0);left:calc(var(--spacing)*0);height:100%;width:calc(var(--spacing)*1);background-color:var(--color-primary-500);position:absolute}.ck.ck-content ul,.ck.ck-content ol{margin-bottom:calc(var(--spacing)*4);margin-left:calc(var(--spacing)*6)}.ck.ck-content ul{list-style-type:disc}.ck.ck-content ol{list-style-type:decimal}.ck.ck-content li{margin-bottom:calc(var(--spacing)*1);color:var(--color-gray-700)}.ck.ck-content li:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-content a{color:var(--color-primary-600)}@media (hover:hover){.ck.ck-content a:hover{color:var(--color-primary-700)}}.ck.ck-content a:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-primary-500)}@media (hover:hover){.ck.ck-content a:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{color:var(--color-primary-400)}}.ck.ck-content a{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;text-decoration-line:underline;transition-duration:.2s}.ck.ck-content code{background-color:var(--color-gray-100);color:var(--color-gray-800)}.ck.ck-content code:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-800);color:var(--color-gray-200)}.ck.ck-content code{border-radius:var(--radius-sm);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.ck.ck-content pre{background-color:#101828e6}@supports (color:color-mix(in lab, red, red)){.ck.ck-content pre{background-color:color-mix(in oklab,var(--color-gray-900)90%,transparent)}}.ck.ck-content pre{color:var(--color-gray-100)}.ck.ck-content pre:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-900);color:var(--color-gray-100)}.ck.ck-content pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-800)}.ck.ck-content pre:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700)}.ck.ck-content pre{margin-block:calc(var(--spacing)*5);border-radius:var(--radius-md);padding:calc(var(--spacing)*4);font-family:var(--font-mono);--tw-leading:var(--leading-snug);font-size:13px;line-height:var(--leading-snug);overflow-x:auto}.ck.ck-content table{margin-block:calc(var(--spacing)*4);border-collapse:collapse;width:100%}.ck.ck-content table td,.ck.ck-content table th{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}:is(.ck.ck-content table td,.ck.ck-content table th):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-content table th{background-color:var(--color-gray-100);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.ck.ck-content table th:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700);color:var(--color-gray-100)}.ck.ck-content table td{background-color:var(--color-white);color:var(--color-gray-700)}.ck.ck-content table td:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800);color:var(--color-gray-300)}.ck.ck-content figure.image{margin-inline:auto;margin-block:calc(var(--spacing)*4);max-width:100%}.ck.ck-content figure.image img{border-radius:var(--radius-lg);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);max-width:100%;height:auto;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-widget{position:relative}.ck.ck-widget.ck-widget_selected{border-radius:var(--radius-sm);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-400)}@supports (color:color-mix(in lab, red, red)){.ck.ck-widget.ck-widget_selected{--tw-ring-color:color-mix(in oklab,var(--color-primary-400)50%,transparent)}}.ck.ck-widget__type-around__button{background-color:var(--color-primary-600);color:var(--color-white)}@media (hover:hover){.ck.ck-widget__type-around__button:hover{background-color:var(--color-primary-700)}}.ck.ck-widget__type-around__button{height:calc(var(--spacing)*6);width:calc(var(--spacing)*6);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;border-radius:3.40282e38px;justify-content:center;align-items:center;transition-duration:.2s;display:flex}.ck.ck-widget__resizer{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-primary-400);background-color:var(--color-primary-50)}@supports (color:color-mix(in lab, red, red)){.ck.ck-widget__resizer{background-color:color-mix(in oklab,var(--color-primary-50)20%,transparent)}}.ck.ck-widget__resizer:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-900)}@supports (color:color-mix(in lab, red, red)){.ck.ck-widget__resizer:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-primary-900)20%,transparent)}}.ck.ck-widget__resizer__handle{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3);border-radius:var(--radius-sm);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-primary-700);background-color:var(--color-primary-600)}.ck.ck-content .ck-placeholder:before{color:var(--color-gray-400);font-style:italic}.ck.ck-content .ck-placeholder:before:is(){color:var(--color-gray-500)}.ck.ck-balloon-panel{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white)}.ck.ck-balloon-panel:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-800)}.ck.ck-balloon-panel{border-radius:var(--radius-lg);padding:calc(var(--spacing)*2);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-link-form,.ck.ck-link-actions{align-items:center;gap:calc(var(--spacing)*2);padding:calc(var(--spacing)*2);display:flex}.ck.ck-link-form .ck-input{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);flex:1}.ck.ck-link-form .ck-input:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-link-form .ck-input{background-color:var(--color-white);color:var(--color-gray-900)}.ck.ck-link-form .ck-input:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800);color:var(--color-gray-100)}.ck.ck-link-form .ck-input{border-radius:var(--radius-md);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.ck.ck-link-form .ck-input:focus{border-color:var(--color-primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-300)}.ck.ck-color-selector{gap:calc(var(--spacing)*1);padding:calc(var(--spacing)*2);grid-template-columns:repeat(6,minmax(0,1fr));display:grid}.ck.ck-color-selector .ck-color-selector__option{height:calc(var(--spacing)*7);width:calc(var(--spacing)*7);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-color-selector .ck-color-selector__option:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-color-selector .ck-color-selector__option{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;transition-duration:.15s}@media (hover:hover){.ck.ck-color-selector .ck-color-selector__option:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.ck.ck-color-selector .ck-color-selector__option.ck-on{border-color:var(--color-primary-500);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-500);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--color-white)}.ck.ck-color-selector .ck-color-selector__option.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-offset-color:var(--color-base-800)}.ck.ck-font-size-dropdown .ck-dropdown__panel{max-height:calc(var(--spacing)*48);overflow-y:auto}.ck.ck-list .ck-list__item{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}@media (hover:hover){.ck.ck-list .ck-list__item:hover{background-color:var(--color-gray-100)}.ck.ck-list .ck-list__item:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-700)}}.ck.ck-list .ck-list__item{cursor:pointer;color:var(--color-gray-700)}.ck.ck-list .ck-list__item:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-list .ck-list__item{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}.ck.ck-list .ck-list__item.ck-on{border-radius:var(--radius-sm);background-color:var(--color-primary-100);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-primary-700)}.ck.ck-list .ck-list__item.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-700);color:var(--color-white)}.ck.ck-code-block-dropdown .ck-dropdown__panel{min-width:calc(var(--spacing)*56)}.ck.ck-content .ck-media__wrapper{margin-block:calc(var(--spacing)*4);border-radius:var(--radius-lg);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);overflow:hidden}.ck.ck-content .ck-media__placeholder{background-color:var(--color-gray-100);color:var(--color-gray-500)}.ck.ck-content .ck-media__placeholder:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-800);color:var(--color-gray-400)}.ck.ck-content .ck-media__placeholder{border-radius:var(--radius-lg);border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:2px;border-color:var(--color-gray-300);padding:calc(var(--spacing)*8);text-align:center}.ck.ck-content .ck-media__placeholder:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-content .todo-list{margin-left:calc(var(--spacing)*0);list-style-type:none}.ck.ck-content .todo-list li{margin-bottom:calc(var(--spacing)*2);align-items:flex-start;gap:calc(var(--spacing)*3);display:flex}.ck.ck-content .todo-list li .todo-list__label{align-items:center;gap:calc(var(--spacing)*2);display:flex}.ck.ck-content .todo-list li .todo-list__label input{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);border-radius:var(--radius-sm);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-primary-600)}.ck.ck-content .todo-list li .todo-list__label input:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-content .todo-list li .todo-list__label input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-300)}.ck.ck-content mark{background-color:var(--color-yellow-200);color:var(--color-gray-900)}.ck.ck-content mark:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-yellow-600);color:var(--color-gray-100)}.ck.ck-content mark{border-radius:var(--radius-sm);padding-inline:calc(var(--spacing)*1)}.ck.ck-source-editing-area{min-height:calc(var(--spacing)*48);background-color:var(--color-gray-50);width:100%;padding:calc(var(--spacing)*4)}.ck.ck-source-editing-area:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-900)}.ck.ck-source-editing-area{font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.ck.ck-source-editing-area:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.ck.ck-source-editing-area{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}.ck.ck-source-editing-area:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-source-editing-area:focus{border-color:var(--color-primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-300)}.ck.ck-source-editing-area{resize:vertical}.word-count{margin-top:calc(var(--spacing)*2);text-align:right}.ck.ck-word-count{justify-content:flex-end;gap:calc(var(--spacing)*4);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:flex}.ck.ck-word-count:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-400)}@media (max-width:768px){.ck.ck-toolbar__items{gap:calc(var(--spacing)*.5)}.ck.ck-button{height:calc(var(--spacing)*7);min-height:calc(var(--spacing)*7);padding-inline:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.ck.ck-content{min-height:calc(var(--spacing)*32);padding:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.ck.ck-dropdown__panel{min-width:100%}}@media (prefers-reduced-motion:no-preference){.ck.ck-button{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out)}.ck.ck-dropdown__panel{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out)}.ck.ck-widget{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out)}}.ck.ck-button:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-400);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--color-white)}.ck.ck-button:focus-visible:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-offset-color:var(--color-base-800)}.ck.ck-content:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.ck.ck-content:focus{outline-offset:2px;outline:2px solid #0000}}@media (prefers-contrast:high){.ck.ck-button{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-400)}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-500)}.ck.ck-content{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-400)}.ck.ck-content:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-500)}}:root{--ck-color-base-background:var(--color-white);--ck-color-base-foreground:var(--color-gray-100);--ck-color-focus-border:var(--color-primary-600);--ck-color-text:var(--color-gray-900);--ck-color-shadow-drop:#00000026;--ck-color-shadow-inner:#0000001a;--ck-color-button-default-background:transparent;--ck-color-button-default-hover-background:var(--color-gray-100);--ck-color-button-on-background:var(--color-primary-100);--ck-color-button-on-hover-background:var(--color-primary-200);--ck-color-dropdown-panel-background:var(--color-white);--ck-color-dropdown-panel-border:var(--color-gray-200);--ck-color-dialog-background:var(--color-white);--ck-color-dialog-form-header-border:var(--color-gray-200);--ck-color-input-background:var(--color-white);--ck-color-input-border:var(--color-gray-300);--ck-color-input-error-border:var(--color-red-500);--ck-color-list-background:var(--color-white);--ck-color-list-button-hover-background:var(--color-gray-100);--ck-color-list-button-on-background:var(--color-primary-100);--ck-color-panel-background:var(--color-white);--ck-color-panel-border:var(--color-gray-200);--ck-color-toolbar-background:var(--color-gray-50);--ck-color-toolbar-border:var(--color-gray-200);--ck-color-base-border:var(--color-gray-200);--ck-color-tooltip-background:var(--color-gray-800);--ck-color-tooltip-text:var(--color-white);--ck-color-engine-placeholder-text:var(--color-gray-400);--ck-color-upload-bar-background:var(--color-primary-600);--ck-color-link-default:var(--color-primary-600);--ck-color-link-selected-background:var(--color-primary-100)}.dark,[data-theme=dark]{--ck-color-base-background:var(--color-base-800);--ck-color-base-foreground:var(--color-base-700);--ck-color-focus-border:var(--color-primary-600);--ck-color-text:var(--color-gray-100);--ck-color-shadow-drop:#0000004d;--ck-color-shadow-inner:#0003;--ck-color-button-default-background:transparent;--ck-color-button-default-hover-background:var(--color-gray-700);--ck-color-button-on-background:var(--color-primary-600);--ck-color-button-on-hover-background:var(--color-primary-700);--ck-color-dropdown-panel-background:var(--color-base-800);--ck-color-dropdown-panel-border:var(--color-gray-600);--ck-color-dialog-background:var(--color-base-800);--ck-color-dialog-form-header-border:var(--color-gray-600);--ck-color-input-background:var(--color-base-800);--ck-color-input-border:var(--color-gray-600);--ck-color-input-error-border:var(--color-red-500);--ck-color-list-background:var(--color-base-800);--ck-color-list-button-hover-background:var(--color-gray-700);--ck-color-list-button-on-background:var(--color-primary-600);--ck-color-panel-background:var(--color-base-800);--ck-color-panel-border:var(--color-gray-600);--ck-color-toolbar-background:var(--color-base-900);--ck-color-toolbar-border:var(--color-gray-700);--ck-color-base-border:var(--color-gray-700);--ck-color-tooltip-background:var(--color-gray-700);--ck-color-tooltip-text:var(--color-white);--ck-color-engine-placeholder-text:var(--color-gray-500);--ck-color-upload-bar-background:var(--color-primary-600);--ck-color-link-default:var(--color-primary-500);--ck-color-link-selected-background:var(--color-primary-800)}@keyframes ck-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.ck.ck-dropdown__panel{animation:.2s ease-out ck-fade-in}.ck.ck-content::-webkit-scrollbar{width:calc(var(--spacing)*2)}.ck.ck-content::-webkit-scrollbar-track{border-radius:var(--radius-sm);background-color:var(--color-gray-100)}.ck.ck-content::-webkit-scrollbar-track:is(){background-color:var(--color-gray-800)}.ck.ck-content::-webkit-scrollbar-thumb{border-radius:var(--radius-sm);background-color:var(--color-gray-400)}@media (hover:hover){.ck.ck-content::-webkit-scrollbar-thumb:hover{background-color:var(--color-gray-500)}}.ck.ck-content::-webkit-scrollbar-thumb:is(){background-color:var(--color-gray-600)}@media (hover:hover){.ck.ck-content::-webkit-scrollbar-thumb:is():hover{background-color:var(--color-gray-500)}}.ck-editor-container .ck-editor-container{margin-top:calc(var(--spacing)*0)}.form-row .ck-editor-container,.field-box .ck-editor-container{width:100%}.errors .ck.ck-editor{border-color:var(--color-red-600)}.errors .ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-red-500)}.errors .ck.ck-editor:focus-within{border-color:var(--color-red-600);--tw-ring-color:var(--color-red-200)}.errors .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-red-500);--tw-ring-color:#e4001466}@supports (color:color-mix(in lab, red, red)){.errors .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-color:color-mix(in oklab,var(--color-red-600)40%,transparent)}}.success .ck.ck-editor{border-color:var(--color-green-600)}.success .ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-green-500)}.success .ck.ck-editor:focus-within{border-color:var(--color-green-600);--tw-ring-color:var(--color-green-200)}.success .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-green-500);--tw-ring-color:#00a54466}@supports (color:color-mix(in lab, red, red)){.success .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-color:color-mix(in oklab,var(--color-green-600)40%,transparent)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file From 5543eb7bdf8d779bbc696e7f91b0f48982bbf39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20T=C3=BA=20Anh?= Date: Mon, 19 Oct 2026 22:09:32 +0700 Subject: [PATCH 2/3] CKEditor: theme with --ck-* variables only, no Tailwind build Review on #27: compiling the old @apply rules switched on ~100 rules that had never rendered against CKEditor 47 and broke the colour selector, highlight colours, the insert-table grid, image side/align styles, code blocks, nested todos and table backgrounds; pseudo-element dark: rules compiled to an empty :is(). The bug only needed valid variables, so base/css/ckeditor5.css is now plain CSS that overrides CKEditor's --ck-* variables (its documented theming method) and leaves every feature on CKEditor's own styles: - light: borders, text, focus, links and "on" buttons from unfold's base/primary/font colors, each with CKEditor's default as fallback - dark (unfold's "dark" class): base variables plus every light default that is not derived from them: split-button hover, input/button disabled states, placeholders, blur selection, widget borders, links, shadows, captions, and --ck-content-font-color (#000, the black text) - one rule for code blocks, whose #353535 text has no variable The Tailwind source, build step, styles.css exclusions and CI gate from the previous commit are removed. The test uses SimpleTestCase and finds the packaged file by path, not via settings. docs/CKEDITOR5_CUSTOMIZATION.md is rewritten: it described an @apply build and a JS file that don't exist. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 2 +- ckeditor5.css | 575 ---------------------- docs/CKEDITOR5_CUSTOMIZATION.md | 270 ++-------- scripts/tailwind.sh | 2 - styles.css | 3 - whiteneuron/base/tests_regressions.py | 13 +- whiteneuron/static/base/css/ckeditor5.css | 82 ++- 7 files changed, 137 insertions(+), 810 deletions(-) delete mode 100644 ckeditor5.css diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0401d5d..c157831 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,5 +86,5 @@ jobs: - name: Committed files match the build run: | - git diff --exit-code -- daisy-themes.css whiteneuron/base/themes.py whiteneuron/static/base/css/styles.css whiteneuron/static/base/css/ckeditor5.css \ + git diff --exit-code -- daisy-themes.css whiteneuron/base/themes.py whiteneuron/static/base/css/styles.css \ || { echo "::error::Run 'bash scripts/tailwind.sh' and commit the regenerated files."; exit 1; } diff --git a/ckeditor5.css b/ckeditor5.css deleted file mode 100644 index 8a0f765..0000000 --- a/ckeditor5.css +++ /dev/null @@ -1,575 +0,0 @@ -/* CKEditor 5 styles for Unfold (settings.CKEDITOR_5_CUSTOM_CSS). - Source only: scripts/tailwind.sh builds it into whiteneuron/static/base/css/ckeditor5.css, - the file browsers get (they ignore @apply and --theme()). */ -@layer theme; -@import "tailwindcss/theme" layer(theme); - -/* same dark mode as styles.css: unfold's "dark" class, which daisyUI dark themes also set */ -@custom-variant dark (&:is(.dark, .dark *, [data-theme=dark], [data-theme=dark] *)); - -/* unfold defines these at runtime (UNFOLD["COLORS"], daisy-themes.css): reference, don't redefine */ -@theme inline reference { - --color-base-50: var(--color-base-50); - --color-base-100: var(--color-base-100); - --color-base-200: var(--color-base-200); - --color-base-300: var(--color-base-300); - --color-base-400: var(--color-base-400); - --color-base-500: var(--color-base-500); - --color-base-600: var(--color-base-600); - --color-base-700: var(--color-base-700); - --color-base-800: var(--color-base-800); - --color-base-900: var(--color-base-900); - --color-base-950: var(--color-base-950); - --color-primary-50: var(--color-primary-50); - --color-primary-100: var(--color-primary-100); - --color-primary-200: var(--color-primary-200); - --color-primary-300: var(--color-primary-300); - --color-primary-400: var(--color-primary-400); - --color-primary-500: var(--color-primary-500); - --color-primary-600: var(--color-primary-600); - --color-primary-700: var(--color-primary-700); - --color-primary-800: var(--color-primary-800); - --color-primary-900: var(--color-primary-900); - --color-primary-950: var(--color-primary-950); -} - -/* Container styling to match Unfold form fields */ -.ck-editor-container { - @apply relative w-full; -} - -/* Main editor wrapper */ -.ck.ck-editor { - @apply relative w-full bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xs; - @apply focus-within:border-primary-600 focus-within:ring-3 focus-within:ring-primary-500/40 dark:focus-within:border-primary-600 dark:focus-within:ring-primary-500/30; - @apply transition-all duration-300 ease-in-out; -} - -/* Toolbar styling */ -.ck.ck-toolbar { - @apply bg-base-100 dark:bg-base-900 border-b border-gray-200 dark:border-gray-700; - @apply px-2 py-1.5 rounded-t-lg; -} - -/* Toolbar items container */ -.ck.ck-toolbar__items { - @apply flex flex-wrap items-center gap-1; -} - -/* Toolbar separators */ -.ck.ck-toolbar__separator { - @apply w-px h-6 bg-gray-300 dark:bg-gray-600 mx-1; -} - -/* Buttons styling refined */ -.ck.ck-button { - @apply inline-flex items-center justify-center h-8 min-h-8 px-2.5 py-1.5; - @apply text-[13px] font-medium text-gray-700 dark:text-gray-300 tracking-tight; - @apply bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700/70; - @apply border border-transparent rounded-md; - @apply transition-colors duration-150 ease-in-out; - @apply cursor-pointer select-none; - @apply focus:outline-hidden focus:ring-2 focus:ring-primary-500/40 dark:focus:ring-primary-500/30; -} - -.ck.ck-button:hover { - @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100; -} - -.ck.ck-button.ck-on { - @apply bg-primary-100 dark:bg-primary-700/70 text-primary-700 dark:text-white; - @apply border-primary-300 dark:border-primary-600; -} - -.ck.ck-button.ck-disabled, -.ck.ck-button:disabled { - @apply opacity-50 cursor-not-allowed pointer-events-none; -} - -/* Button icons */ -.ck.ck-button .ck-icon { - @apply w-4 h-4 opacity-90 text-gray-600 dark:text-gray-300; -} - -.ck.ck-button:hover .ck-icon, -.ck.ck-button.ck-on .ck-icon { - @apply opacity-100 text-primary-600 dark:text-white; -} - -/* Dropdown buttons */ -.ck.ck-dropdown__button { - @apply inline-flex items-center gap-2; -} - -.ck.ck-dropdown__button .ck-dropdown__arrow { - @apply w-3 h-3 opacity-60; -} - -/* Split buttons styling */ -.ck.ck-splitbutton .ck-splitbutton__action { - @apply rounded-l-md border-r border-gray-300 dark:border-gray-600; -} - -.ck.ck-splitbutton .ck-splitbutton__arrow { - @apply rounded-r-md px-2; -} - -/* Dropdown panels */ -.ck.ck-dropdown__panel { - @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; - @apply rounded-lg shadow-lg mt-1 p-1 min-w-48; -} - -/* High contrast nested dropdown variants (language, heading, font size) */ -.ck.ck-code-block-dropdown .ck-dropdown__panel, -.ck.ck-font-size-dropdown .ck-dropdown__panel, -.ck.ck-heading-dropdown .ck-dropdown__panel { - @apply bg-white dark:bg-base-900 border border-gray-200 dark:border-gray-700 shadow-xl; -} - -/* Improve list item readability inside panels */ -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item { @apply rounded-md; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item + .ck-list__item { @apply mt-0.5; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button { @apply w-full text-left px-3 py-2 text-[13px] font-medium; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover { @apply bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white; } -.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on { @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white; } - -/* Visible check holder */ -.ck.ck-dropdown__panel .ck-list-item-button__check-holder { @apply w-4 h-4 mr-2 flex items-center justify-center text-[11px] rounded-xs border border-gray-300 dark:border-gray-600 bg-white dark:bg-base-800; } - -/* Ensure visibility when panel is shown */ -.ck.ck-dropdown__panel.ck-dropdown__panel-visible { @apply bg-white dark:bg-base-900; } - -/* Scrollbars inside dropdown lists */ -.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar { @apply w-2; } -.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb { @apply bg-gray-300 dark:bg-gray-600 rounded-sm; } - -/* Zebra table rows inside content */ -.ck.ck-content table tbody tr:nth-child(odd) { @apply bg-gray-50 dark:bg-base-800; } -.ck.ck-content table tbody tr:nth-child(even) { @apply bg-white dark:bg-base-900; } -.ck.ck-content table th, .ck.ck-content table td { @apply border border-gray-200 dark:border-gray-700; } - -/* Text selection highlight */ -.ck.ck-content ::selection { background: var(--color-primary-500); color: white; } -.ck.ck-content ::-moz-selection { background: var(--color-primary-500); color: white; } - -/* Main editing area */ -.ck.ck-content { - @apply min-h-48 max-h-[32rem] p-5 bg-white dark:bg-base-800; - @apply text-gray-900 dark:text-gray-100 text-[15px] leading-relaxed; - @apply rounded-b-lg border-none outline-hidden; - @apply overflow-y-auto; - font-family: 'Inter', system-ui, -apple-system, sans-serif; -} - -.ck.ck-content:focus { - @apply outline-hidden ring-0; -} - -/* Content styling within editor */ -.ck.ck-content h1, -.ck.ck-content h2, -.ck.ck-content h3, -.ck.ck-content h4, -.ck.ck-content h5, -.ck.ck-content h6 { - @apply font-semibold text-gray-900 dark:text-gray-100 mb-3 mt-4; -} - -.ck.ck-content h1 { @apply text-2xl; } -.ck.ck-content h2 { @apply text-xl; } -.ck.ck-content h3 { @apply text-lg; } -.ck.ck-content h4 { @apply text-base font-bold; } -.ck.ck-content h5 { @apply text-sm font-bold; } -.ck.ck-content h6 { @apply text-xs font-bold uppercase tracking-wide; } - -.ck.ck-content p { - @apply mb-4 text-gray-700 dark:text-gray-300; -} - -.ck.ck-content blockquote { - /* Refined blockquote styling */ - @apply relative pl-5 py-3 my-5 rounded-md overflow-hidden italic; - @apply bg-gray-50 dark:bg-base-900 text-gray-700 dark:text-gray-300; - @apply border-l-4 border-primary-500; -} - -.ck.ck-content blockquote:before { - content: ""; - @apply absolute left-0 top-0 h-full w-1 bg-primary-500; -} - -.ck.ck-content ul, -.ck.ck-content ol { - @apply ml-6 mb-4; -} - -.ck.ck-content ul { - @apply list-disc; -} - -.ck.ck-content ol { - @apply list-decimal; -} - -.ck.ck-content li { - @apply mb-1 text-gray-700 dark:text-gray-300; -} - -.ck.ck-content a { - @apply text-primary-600 dark:text-primary-500 hover:text-primary-700 dark:hover:text-primary-400; - @apply underline transition-colors duration-200; -} - -.ck.ck-content code { - @apply bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200; - @apply px-2 py-1 rounded-sm font-mono text-sm; -} - -.ck.ck-content pre { - @apply bg-gray-900/90 dark:bg-gray-900 text-gray-100 dark:text-gray-100; - @apply border border-gray-800 dark:border-gray-700; - @apply p-4 rounded-md overflow-x-auto my-5 font-mono text-[13px] leading-snug; -} - -/* Table styling */ -.ck.ck-content table { - @apply w-full border-collapse my-4; -} - -.ck.ck-content table td, -.ck.ck-content table th { - @apply border border-gray-300 dark:border-gray-600 px-3 py-2; -} - -.ck.ck-content table th { - @apply bg-gray-100 dark:bg-gray-700 font-semibold text-gray-900 dark:text-gray-100; -} - -.ck.ck-content table td { - @apply bg-white dark:bg-base-800 text-gray-700 dark:text-gray-300; -} - -/* Image widget styling */ -.ck.ck-content figure.image { - @apply my-4 mx-auto max-w-full; -} - -.ck.ck-content figure.image img { - @apply max-w-full h-auto rounded-lg shadow-md; -} - -/* Widget styling */ -.ck.ck-widget { - @apply relative; -} - -.ck.ck-widget.ck-widget_selected { - @apply ring-2 ring-primary-400/50 rounded-sm; -} - -.ck.ck-widget__type-around__button { - @apply bg-primary-600 hover:bg-primary-700 text-white; - @apply w-6 h-6 rounded-full flex items-center justify-center; - @apply transition-colors duration-200; -} - -/* Resizer styling */ -.ck.ck-widget__resizer { - @apply border border-primary-400 bg-primary-50/20 dark:bg-primary-900/20; -} - -.ck.ck-widget__resizer__handle { - @apply bg-primary-600 w-3 h-3 border border-primary-700 rounded-sm; -} - -/* Placeholder styling */ -.ck.ck-content .ck-placeholder:before { - @apply text-gray-400 dark:text-gray-500 italic; -} - -/* Balloon panels */ -.ck.ck-balloon-panel { - @apply bg-white dark:bg-base-800 border border-gray-200 dark:border-gray-700; - @apply rounded-lg shadow-lg p-2; -} - -/* Link balloon styling */ -.ck.ck-link-form, -.ck.ck-link-actions { - @apply flex items-center gap-2 p-2; -} - -.ck.ck-link-form .ck-input { - @apply flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600; - @apply bg-white dark:bg-base-800 text-gray-900 dark:text-gray-100; - @apply rounded-md text-sm focus:ring-2 focus:ring-primary-300 focus:border-primary-600; -} - -/* Color picker styling */ -.ck.ck-color-selector { - @apply grid grid-cols-6 gap-1 p-2; -} - -.ck.ck-color-selector .ck-color-selector__option { - @apply w-7 h-7 rounded-md border border-gray-300 dark:border-gray-600 shadow-xs; - @apply hover:scale-110 transition-transform duration-150; -} -.ck.ck-color-selector .ck-color-selector__option.ck-on { - @apply ring-2 ring-primary-500 ring-offset-1 ring-offset-white dark:ring-offset-base-800 border-primary-500; -} - -/* Font size dropdown */ -.ck.ck-font-size-dropdown .ck-dropdown__panel { - @apply max-h-48 overflow-y-auto; -} - -.ck.ck-list .ck-list__item { - @apply px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700; - @apply text-gray-700 dark:text-gray-300 cursor-pointer; - @apply transition-colors duration-200; -} - -.ck.ck-list .ck-list__item.ck-on { - @apply bg-primary-100 dark:bg-primary-700 text-primary-700 dark:text-white font-medium rounded-sm; -} - -/* Code block dropdown */ -.ck.ck-code-block-dropdown .ck-dropdown__panel { - @apply min-w-56; -} - -/* Media embed styling */ -.ck.ck-content .ck-media__wrapper { - @apply my-4 rounded-lg overflow-hidden shadow-md; -} - -.ck.ck-content .ck-media__placeholder { - @apply bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400; - @apply p-8 text-center rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600; -} - -/* Todo list styling */ -.ck.ck-content .todo-list { - @apply list-none ml-0; -} - -.ck.ck-content .todo-list li { - @apply flex items-start gap-3 mb-2; -} - -.ck.ck-content .todo-list li .todo-list__label { - @apply flex items-center gap-2; -} - -.ck.ck-content .todo-list li .todo-list__label input { - @apply w-4 h-4 text-primary-600 border border-gray-300 dark:border-gray-600 rounded-sm; - @apply focus:ring-2 focus:ring-primary-300; -} - -/* Highlight styling */ -.ck.ck-content mark { - @apply bg-yellow-200 dark:bg-yellow-600 text-gray-900 dark:text-gray-100; - @apply px-1 rounded-sm; -} - -/* Source editing mode */ -.ck.ck-source-editing-area { - @apply w-full min-h-48 p-4 bg-gray-50 dark:bg-gray-900; - @apply text-gray-900 dark:text-gray-100 font-mono text-sm; - @apply border border-gray-300 dark:border-gray-600 rounded-lg; - @apply focus:ring-2 focus:ring-primary-300 focus:border-primary-600; - @apply resize-y; -} - -/* Word count styling */ -.word-count { - @apply mt-2 text-right; -} - -.ck.ck-word-count { - @apply text-xs text-gray-500 dark:text-gray-400 flex gap-4 justify-end; -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .ck.ck-toolbar__items { - @apply gap-0.5; - } - - .ck.ck-button { - @apply h-7 min-h-7 px-2 text-xs; - } - - .ck.ck-content { - @apply min-h-32 p-3 text-sm; - } - - .ck.ck-dropdown__panel { - @apply min-w-full; - } -} - -/* Animation improvements */ -@media (prefers-reduced-motion: no-preference) { - .ck.ck-button { - @apply transition-all duration-200 ease-in-out; - } - - .ck.ck-dropdown__panel { - @apply transition-opacity duration-200 ease-in-out; - } - - .ck.ck-widget { - @apply transition-all duration-200 ease-in-out; - } -} - -/* Focus states for accessibility */ -.ck.ck-button:focus-visible { - @apply ring-2 ring-primary-400 ring-offset-1 ring-offset-white dark:ring-offset-base-800; -} - -.ck.ck-content:focus { - @apply ring-0 outline-hidden; -} - -/* High contrast mode support */ -@media (prefers-contrast: high) { - .ck.ck-button { - @apply border border-gray-400 dark:border-gray-500; - } - - .ck.ck-content { - @apply border border-gray-400 dark:border-gray-500; - } -} - -/* Override default CKEditor colors for better integration */ -:root { - --ck-color-base-background: --theme(--color-white); - --ck-color-base-foreground: --theme(--color-gray-100); - --ck-color-focus-border: var(--color-primary-600); - --ck-color-text: --theme(--color-gray-900); - --ck-color-shadow-drop: rgba(0, 0, 0, 0.15); - --ck-color-shadow-inner: rgba(0, 0, 0, 0.1); - --ck-color-button-default-background: transparent; - --ck-color-button-default-hover-background: --theme(--color-gray-100); - --ck-color-button-on-background: var(--color-primary-100); - --ck-color-button-on-hover-background: var(--color-primary-200); - --ck-color-dropdown-panel-background: --theme(--color-white); - --ck-color-dropdown-panel-border: --theme(--color-gray-200); - --ck-color-dialog-background: --theme(--color-white); - --ck-color-dialog-form-header-border: --theme(--color-gray-200); - --ck-color-input-background: --theme(--color-white); - --ck-color-input-border: --theme(--color-gray-300); - --ck-color-input-error-border: --theme(--color-red-500); - --ck-color-list-background: --theme(--color-white); - --ck-color-list-button-hover-background: --theme(--color-gray-100); - --ck-color-list-button-on-background: var(--color-primary-100); - --ck-color-panel-background: --theme(--color-white); - --ck-color-panel-border: --theme(--color-gray-200); - --ck-color-toolbar-background: --theme(--color-gray-50); - --ck-color-toolbar-border: --theme(--color-gray-200); - --ck-color-base-border: --theme(--color-gray-200); - --ck-color-tooltip-background: --theme(--color-gray-800); - --ck-color-tooltip-text: --theme(--color-white); - --ck-color-engine-placeholder-text: --theme(--color-gray-400); - --ck-color-upload-bar-background: var(--color-primary-600); - --ck-color-link-default: var(--color-primary-600); - --ck-color-link-selected-background: var(--color-primary-100); -} - -.dark, -[data-theme="dark"] { - --ck-color-base-background: var(--color-base-800); - --ck-color-base-foreground: var(--color-base-700); - --ck-color-focus-border: var(--color-primary-600); - --ck-color-text: --theme(--color-gray-100); - --ck-color-shadow-drop: rgba(0, 0, 0, 0.3); - --ck-color-shadow-inner: rgba(0, 0, 0, 0.2); - --ck-color-button-default-background: transparent; - --ck-color-button-default-hover-background: --theme(--color-gray-700); - --ck-color-button-on-background: var(--color-primary-600); - --ck-color-button-on-hover-background: var(--color-primary-700); - --ck-color-dropdown-panel-background: var(--color-base-800); - --ck-color-dropdown-panel-border: --theme(--color-gray-600); - --ck-color-dialog-background: var(--color-base-800); - --ck-color-dialog-form-header-border: --theme(--color-gray-600); - --ck-color-input-background: var(--color-base-800); - --ck-color-input-border: --theme(--color-gray-600); - --ck-color-input-error-border: --theme(--color-red-500); - --ck-color-list-background: var(--color-base-800); - --ck-color-list-button-hover-background: --theme(--color-gray-700); - --ck-color-list-button-on-background: var(--color-primary-600); - --ck-color-panel-background: var(--color-base-800); - --ck-color-panel-border: --theme(--color-gray-600); - --ck-color-toolbar-background: var(--color-base-900); - --ck-color-toolbar-border: --theme(--color-gray-700); - --ck-color-base-border: --theme(--color-gray-700); - --ck-color-tooltip-background: --theme(--color-gray-700); - --ck-color-tooltip-text: --theme(--color-white); - --ck-color-engine-placeholder-text: --theme(--color-gray-500); - --ck-color-upload-bar-background: var(--color-primary-600); - --ck-color-link-default: var(--color-primary-500); - --ck-color-link-selected-background: var(--color-primary-800); -} - -/* Animation keyframes */ -@keyframes ck-fade-in { - from { - opacity: 0; - transform: translateY(-4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Apply fade-in animation to dropdowns */ -.ck.ck-dropdown__panel { - animation: ck-fade-in 0.2s ease-out; -} - -/* Custom scrollbar for content area */ -.ck.ck-content::-webkit-scrollbar { - @apply w-2; -} - -.ck.ck-content::-webkit-scrollbar-track { - @apply bg-gray-100 dark:bg-gray-800 rounded-sm; -} - -.ck.ck-content::-webkit-scrollbar-thumb { - @apply bg-gray-400 dark:bg-gray-600 rounded-sm hover:bg-gray-500 dark:hover:bg-gray-500; -} - -/* Ensure proper spacing for nested editor containers */ -.ck-editor-container .ck-editor-container { - @apply mt-0; -} - -/* Integration with Django form styling */ -.form-row .ck-editor-container, -.field-box .ck-editor-container { - @apply w-full; -} - -/* Error state styling */ -.errors .ck.ck-editor { - @apply border-red-600 dark:border-red-500; -} - -.errors .ck.ck-editor:focus-within { - @apply ring-red-200 dark:ring-red-600/40 border-red-600 dark:border-red-500; -} - -/* Success state styling */ -.success .ck.ck-editor { - @apply border-green-600 dark:border-green-500; -} - -.success .ck.ck-editor:focus-within { - @apply ring-green-200 dark:ring-green-600/40 border-green-600 dark:border-green-500; -} diff --git a/docs/CKEDITOR5_CUSTOMIZATION.md b/docs/CKEDITOR5_CUSTOMIZATION.md index c3613e3..b4f5370 100644 --- a/docs/CKEDITOR5_CUSTOMIZATION.md +++ b/docs/CKEDITOR5_CUSTOMIZATION.md @@ -1,247 +1,77 @@ -# CKEditor5 Unfold Theme Integration +# CKEditor 5 in the Unfold admin -This implementation provides a comprehensive customization of CKEditor5 to seamlessly integrate with the Unfold admin theme, utilizing Tailwind CSS 3 and DaisyUI 4 with the `ui-` prefix. +django-whiteneuron ships [django-ckeditor-5](https://github.com/hvlads/django-ckeditor-5) configured +for the admin, plus a small stylesheet that themes the editor with Unfold's colors in light and dark +mode. -## Features +## Enabling the editor -### 🎨 **Visual Design** -- **Modern UI**: Clean, minimalist design matching Unfold's aesthetic -- **Dark Mode Support**: Automatic theme detection and switching -- **Responsive Design**: Mobile-first approach with adaptive toolbars -- **DaisyUI Integration**: Uses `ui-` prefixed components for consistency +The default `TextField` widget of `whiteneuron.base.modeladmin.ModelAdmin` is `wysiwyg`. To use +CKEditor in a model admin: -### 🛠 **Enhanced Functionality** -- **Auto-save**: Automatic content preservation with localStorage -- **Accessibility**: WCAG 2.1 compliant with proper ARIA labels -- **Word Count**: Real-time statistics display -- **Responsive Toolbar**: Simplified mobile interface -- **Theme Synchronization**: Automatic dark/light mode switching - -### 🎯 **User Experience** -- **Smooth Animations**: Subtle transitions and hover effects -- **Focus Management**: Enhanced keyboard navigation -- **Error States**: Visual feedback for form validation -- **Loading States**: Progressive enhancement indicators - -## File Structure - -``` -whiteneuron/static/base/ -├── css/ -│ └── ckeditor5.css # Main styling with Tailwind classes -└── js/ - └── ckeditor5-unfold.js # Enhanced functionality and configuration +```python +class ArticleAdmin(ModelAdmin): + text_field_widget = "ckeditor" # options: 'wysiwyg', 'ckeditor', 'mdeditor', '' ``` -## Implementation Details - -### CSS Architecture (`ckeditor5.css`) - -The CSS file uses Tailwind's `@apply` directive to leverage the existing design system: - -- **Container Styling**: Matches Django form field appearance -- **Toolbar Design**: Uses DaisyUI button components with `ui-` prefix -- **Content Area**: Styled with typography utilities -- **Dark Mode**: Automatic color scheme adaptation -- **Responsive Breakpoints**: Mobile-optimized layouts +Or use `django_ckeditor_5.fields.CKEditor5Field` / `django_ckeditor_5.widgets.CKEditor5Widget` directly. -### JavaScript Enhancements (`ckeditor5-unfold.js`) +The package settings (`whiteneuron/base/settings.py`) define `CKEDITOR_5_CONFIGS` (toolbars), +`CKEDITOR_5_FILE_STORAGE`, `CKEDITOR_5_FILE_UPLOAD_PERMISSION` and: -The JavaScript file provides advanced functionality: - -- **Theme Management**: Detects and responds to theme changes -- **Configuration**: Comprehensive CKEditor setup optimized for content editing -- **Auto-save**: Background content preservation -- **Accessibility**: Enhanced keyboard and screen reader support -- **Mobile Optimization**: Responsive toolbar configuration - -## Configuration Options - -### Default Toolbar Configuration - -```javascript -toolbar: { - items: [ - 'heading', '|', - 'bold', 'italic', 'link', '|', - 'bulletedList', 'numberedList', 'blockQuote', '|', - 'uploadImage', 'insertTable', '|', - 'code', 'codeBlock', '|', - 'subscript', 'superscript', 'highlight', '|', - 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', '|', - 'mediaEmbed', 'removeFormat', '|', - 'sourceEditing' - ] -} +```python +CKEDITOR_5_CUSTOM_CSS = "base/css/ckeditor5.css" ``` -### Color Palette Integration - -The color picker integrates with your theme's primary colors: - -```javascript -fontColor: { - colors: [ - // Standard colors - { color: 'hsl(0, 0%, 0%)', label: 'Black' }, - // ... other colors - // Theme integration - { color: 'rgb(var(--color-sky-500))', label: 'Primary' }, - { color: 'rgb(var(--color-sky-600))', label: 'Primary Dark' }, - { color: 'rgb(var(--color-sky-400))', label: 'Primary Light' } - ] -} -``` +django-ckeditor-5 adds that file to the widget's media after CKEditor's own CSS. -## Usage +## The theme stylesheet -### Basic Implementation +`whiteneuron/static/base/css/ckeditor5.css` is **plain CSS with no build step**. Browsers get the +file exactly as committed, so it must not contain Tailwind syntax (`@apply`, `theme()`, +`@custom-variant`); a regression test checks this. -```html - - - +It only overrides CKEditor's `--ck-*` CSS variables, which is +[CKEditor's documented theming method](https://ckeditor.com/docs/ckeditor5/latest/framework/deep-dive/ui/theme-customization.html). +Layout, sizes and feature styles (colour pickers, highlights, image styles, tables, code blocks, +todo lists) stay CKEditor's own. - -
- -
-``` - -### Django Template Integration +- **Colors** come from Unfold: `--color-base-*`, `--color-primary-*` and `--color-font-*` + (`UNFOLD["COLORS"]`, remapped per theme by `daisy-themes.css`). Every `var()` has CKEditor's + default as its fallback. +- **Dark mode** follows Unfold's `dark` class on ``, which daisyUI dark themes also set. + CKEditor's light defaults that are not derived from its base variables (button and input states, + placeholders, selection, content text, captions) are overridden one by one. +- **Code blocks** are the one exception: CKEditor gives `pre` a fixed `#353535` text color with no + variable, so a single dark-mode rule recolors it. -```html -{% load static %} +## Customizing in a project -
- -
- {{ form.content }} -
-
- - - - -``` - -### Custom Configuration - -```javascript -// Initialize with custom configuration -window.initUnfoldCKEditor(document.querySelector('#my-editor'), { - toolbar: ['bold', 'italic', 'link'], - placeholder: 'Custom placeholder text...', - // Other custom options -}); -``` - -## Customization - -### Adding New Toolbar Items - -```javascript -// Extend the default configuration -window.UnfoldCKEditorConfig.toolbar.items.push('specialCharacters'); -``` - -### Custom Color Themes - -```javascript -// Add custom colors to the palette -window.UnfoldCKEditorConfig.fontColor.colors.push({ - color: '#your-color', - label: 'Custom Color' -}); -``` - -### Styling Overrides +Point `CKEDITOR_5_CUSTOM_CSS` at your own static file and override variables there. Start from the +packaged file, or keep it and load yours after it: ```css -/* Add custom styles in your CSS file */ -.ck.ck-editor.my-custom-editor { - @apply border-2 border-blue-500; +:root { + --ck-color-focus-border: #7c3aed; + --ck-content-font-family: "Inter", system-ui, sans-serif; } -.ck.ck-editor.my-custom-editor .ck-toolbar { - @apply bg-blue-50 dark:bg-blue-900; +.dark { + --ck-color-base-background: #0b1020; } ``` -## Browser Support - -- **Modern Browsers**: Chrome 63+, Firefox 60+, Safari 12+, Edge 79+ -- **Mobile Support**: iOS Safari 12+, Chrome Mobile 63+ -- **Accessibility**: WCAG 2.1 AA compliant -- **RTL Support**: Full right-to-left text direction support - -## Performance Considerations - -- **Lazy Loading**: CKEditor initializes only when needed -- **Asset Optimization**: Minified CSS and JavaScript -- **Memory Management**: Proper cleanup on navigation -- **Auto-save Throttling**: Configurable save intervals - -## Accessibility Features - -- **Keyboard Navigation**: Full keyboard support for all features -- **Screen Reader Support**: Comprehensive ARIA labels and descriptions -- **High Contrast Mode**: Enhanced visibility in high contrast environments -- **Focus Management**: Proper focus indicators and tab order - -## Error Handling - -The implementation includes robust error handling: - -- **Initialization Errors**: Graceful fallback to plain textarea -- **Network Issues**: Local storage backup for content -- **Browser Compatibility**: Feature detection and polyfills - -## Development Notes - -### CSS Processing - -The CSS file uses Tailwind's `@apply` directive and requires processing through your build pipeline: - -```bash -# Process through Tailwind -npx tailwindcss -i ./static/base/css/ckeditor5.css -o ./dist/ckeditor5.css -``` - -### JavaScript Dependencies - -The JavaScript file depends on: -- CKEditor5 Classic Editor build -- Modern browser APIs (MutationObserver, localStorage) -- CSS custom properties support +CKEditor declares its variables on `:root` and derives most of them from a few base ones +(`--ck-color-base-background`, `-foreground`, `-border`, `-text`). Override them on `` (`:root`, +`.dark`) so the derived variables follow. The full list is in CKEditor's CSS +(`django_ckeditor_5/static/django_ckeditor_5/dist/styles.css`). -### Testing Considerations - -Test the implementation across: -- Different screen sizes (mobile, tablet, desktop) -- Theme switching (light/dark modes) -- Form validation states -- Content restoration from auto-save +If you need Tailwind utilities, compile your stylesheet in your own build and point +`CKEDITOR_5_CUSTOM_CSS` at the output, never at the source. ## Troubleshooting -### Common Issues - -1. **Styles not applying**: Ensure Tailwind CSS is processing the `@apply` directives -2. **Theme not switching**: Check that `data-theme` attribute changes are detected -3. **Auto-save not working**: Verify localStorage availability and permissions -4. **Mobile toolbar issues**: Check responsive breakpoints and toolbar configuration - -### Debug Mode - -Enable debug logging by setting: - -```javascript -window.CKEditorDebug = true; -``` - -This will log initialization steps and theme changes to the console. - -## License - -This implementation follows the same license as your Django project and includes integration code compatible with CKEditor5's GPL license. \ No newline at end of file +- **Transparent editor or black text in dark mode**: the stylesheet has invalid values, usually + Tailwind source served without a build. Check the file in the browser's network tab. +- **Theme does not switch**: the variables follow the `dark` class on ``. Check that Unfold's + theme switch sets it. diff --git a/scripts/tailwind.sh b/scripts/tailwind.sh index 42aa5ff..9f98616 100755 --- a/scripts/tailwind.sh +++ b/scripts/tailwind.sh @@ -10,5 +10,3 @@ ln -sfn "$MD2HTML" .tw-md2html trap 'rm -f .tw-md2html' EXIT npx @tailwindcss/cli -i styles.css -o whiteneuron/static/base/css/styles.css --minify -# CKEDITOR_5_CUSTOM_CSS: served as-is by django-ckeditor-5, so its @apply must be compiled here -npx @tailwindcss/cli -i ckeditor5.css -o whiteneuron/static/base/css/ckeditor5.css --minify diff --git a/styles.css b/styles.css index 625b7cf..aa3f332 100755 --- a/styles.css +++ b/styles.css @@ -8,9 +8,6 @@ @source not "./*.md"; /* its own output: a removed utility would otherwise keep its theme variable alive, so rebuilds drift */ @source not "./whiteneuron/static/base/css/styles.css"; -/* CKEditor styles are a separate build (ckeditor5.css): their @apply classes are not page markup */ -@source not "./ckeditor5.css"; -@source not "./whiteneuron/static/base/css/ckeditor5.css"; /* All daisyUI themes + unfold color mapping (generated by scripts/gen-themes.mjs) */ @import "./daisy-themes.css"; diff --git a/whiteneuron/base/tests_regressions.py b/whiteneuron/base/tests_regressions.py index 3ac8e12..c0ed42a 100644 --- a/whiteneuron/base/tests_regressions.py +++ b/whiteneuron/base/tests_regressions.py @@ -4,7 +4,7 @@ from django.contrib.admin.sites import AdminSite from django.contrib.auth import get_user_model -from django.test import RequestFactory, TestCase, override_settings +from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings from django.urls import reverse from django.utils import translation @@ -714,15 +714,14 @@ def broken(**kwargs): self.assertEqual(page.status_code, 200) -class CKEditorCustomCssTests(TestCase): - def test_served_css_is_compiled(self): +class CKEditorCustomCssTests(SimpleTestCase): + def test_packaged_css_is_plain_css(self): # django-ckeditor-5 links CKEDITOR_5_CUSTOM_CSS as-is: the file used to be Tailwind source, so # browsers dropped every @apply rule and theme() left the editor transparent (unreadable in dark mode) - from django.conf import settings from django.contrib.staticfiles import finders - with open(finders.find(settings.CKEDITOR_5_CUSTOM_CSS), encoding="utf-8") as f: + with open(finders.find("base/css/ckeditor5.css"), encoding="utf-8") as f: css = f.read() - for source_only in ("@apply", "theme(", "@custom-variant"): + for source_only in ("@apply", "theme(", "@custom-variant", "@tailwind"): self.assertNotIn(source_only, css) - self.assertIn(".ck.ck-editor{", css) + self.assertIn("--ck-color-base-background", css) diff --git a/whiteneuron/static/base/css/ckeditor5.css b/whiteneuron/static/base/css/ckeditor5.css index 5063b78..5bbe6f2 100644 --- a/whiteneuron/static/base/css/ckeditor5.css +++ b/whiteneuron/static/base/css/ckeditor5.css @@ -1,2 +1,80 @@ -/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-leading:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-600:oklch(68.1% .162 75.834);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-snug:1.375;--leading-relaxed:1.625;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}}.ck-editor-container{width:100%;position:relative}.ck.ck-editor{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);width:100%;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:relative}.ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-800)}.ck.ck-editor:focus-within{border-color:var(--color-primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-editor:focus-within{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)40%,transparent)}}.ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus-within{border-color:var(--color-primary-600);--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus-within{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)30%,transparent)}}.ck.ck-editor{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;--tw-ease:var(--ease-in-out);transition-duration:.3s;transition-timing-function:var(--ease-in-out)}.ck.ck-toolbar{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-base-100)}.ck.ck-toolbar:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-900)}.ck.ck-toolbar{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1.5)}.ck.ck-toolbar__items{align-items:center;gap:calc(var(--spacing)*1);flex-wrap:wrap;display:flex}.ck.ck-toolbar__separator{margin-inline:calc(var(--spacing)*1);height:calc(var(--spacing)*6);background-color:var(--color-gray-300);width:1px}.ck.ck-toolbar__separator:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-600)}.ck.ck-button{height:calc(var(--spacing)*8);min-height:calc(var(--spacing)*8);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-700);justify-content:center;align-items:center;display:inline-flex}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-button{background-color:#0000}@media (hover:hover){.ck.ck-button:hover{background-color:var(--color-gray-100)}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{background-color:#364153b3}@supports (color:color-mix(in lab, red, red)){.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{background-color:color-mix(in oklab,var(--color-gray-700)70%,transparent)}}}.ck.ck-button{border-radius:var(--radius-md);border-style:var(--tw-border-style);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;--tw-ease:var(--ease-in-out);transition-duration:.15s;transition-timing-function:var(--ease-in-out);cursor:pointer;-webkit-user-select:none;user-select:none;border-width:1px;border-color:#0000}.ck.ck-button:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-button:focus{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)40%,transparent)}}.ck.ck-button:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.ck.ck-button:focus{outline-offset:2px;outline:2px solid #0000}}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus{--tw-ring-color:var(--color-primary-500)}@supports (color:color-mix(in lab, red, red)){.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):focus{--tw-ring-color:color-mix(in oklab,var(--color-primary-500)30%,transparent)}}.ck.ck-button:hover{background-color:var(--color-gray-100);color:var(--color-gray-900)}.ck.ck-button:hover:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700);color:var(--color-gray-100)}.ck.ck-button.ck-on{background-color:var(--color-primary-100);color:var(--color-primary-700)}.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-700)}@supports (color:color-mix(in lab, red, red)){.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-primary-700)70%,transparent)}}.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-white)}.ck.ck-button.ck-on{border-color:var(--color-primary-300)}.ck.ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-primary-600)}.ck.ck-button.ck-disabled,.ck.ck-button:disabled{pointer-events:none;cursor:not-allowed;opacity:.5}.ck.ck-button .ck-icon{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);color:var(--color-gray-600);opacity:.9}.ck.ck-button .ck-icon:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-button:hover .ck-icon,.ck.ck-button.ck-on .ck-icon{color:var(--color-primary-600);opacity:1}:is(.ck.ck-button:hover .ck-icon,.ck.ck-button.ck-on .ck-icon):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-white)}.ck.ck-dropdown__button{align-items:center;gap:calc(var(--spacing)*2);display:inline-flex}.ck.ck-dropdown__button .ck-dropdown__arrow{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3);opacity:.6}.ck.ck-splitbutton .ck-splitbutton__action{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md);border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-gray-300)}.ck.ck-splitbutton .ck-splitbutton__action:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-splitbutton .ck-splitbutton__arrow{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);padding-inline:calc(var(--spacing)*2)}.ck.ck-dropdown__panel{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white)}.ck.ck-dropdown__panel:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-800)}.ck.ck-dropdown__panel{margin-top:calc(var(--spacing)*1);min-width:calc(var(--spacing)*48);border-radius:var(--radius-lg);padding:calc(var(--spacing)*1);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-code-block-dropdown .ck-dropdown__panel,.ck.ck-font-size-dropdown .ck-dropdown__panel,.ck.ck-heading-dropdown .ck-dropdown__panel{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}:is(.ck.ck-code-block-dropdown .ck-dropdown__panel,.ck.ck-font-size-dropdown .ck-dropdown__panel,.ck.ck-heading-dropdown .ck-dropdown__panel):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-900)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item{border-radius:var(--radius-md)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item+.ck-list__item{margin-top:calc(var(--spacing)*.5)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button{width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);text-align:left;--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover{background-color:var(--color-gray-100);color:var(--color-gray-900)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button:hover:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700);color:var(--color-white)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on{background-color:var(--color-primary-100);color:var(--color-primary-700)}.ck.ck-dropdown__panel .ck.ck-list .ck-list__item .ck-button.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-700);color:var(--color-white)}.ck.ck-dropdown__panel .ck-list-item-button__check-holder{margin-right:calc(var(--spacing)*2);height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);border-radius:var(--radius-xs);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);justify-content:center;align-items:center;font-size:11px;display:flex}.ck.ck-dropdown__panel .ck-list-item-button__check-holder:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600);background-color:var(--color-base-800)}.ck.ck-dropdown__panel.ck-dropdown__panel-visible{background-color:var(--color-white)}.ck.ck-dropdown__panel.ck-dropdown__panel-visible:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-900)}.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar{width:calc(var(--spacing)*2)}.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb{border-radius:var(--radius-sm);background-color:var(--color-gray-300)}.ck.ck-dropdown__panel .ck.ck-list::-webkit-scrollbar-thumb:is(){background-color:var(--color-gray-600)}.ck.ck-content table tbody tr:nth-child(odd){background-color:var(--color-gray-50)}.ck.ck-content table tbody tr:nth-child(odd):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800)}.ck.ck-content table tbody tr:nth-child(2n){background-color:var(--color-white)}.ck.ck-content table tbody tr:nth-child(2n):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-900)}.ck.ck-content table th,.ck.ck-content table td{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}:is(.ck.ck-content table th,.ck.ck-content table td):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700)}.ck.ck-content ::selection{background:var(--color-primary-500);color:#fff}.ck.ck-content{max-height:32rem;min-height:calc(var(--spacing)*48);background-color:var(--color-white);padding:calc(var(--spacing)*5)}.ck.ck-content:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800)}.ck.ck-content{--tw-leading:var(--leading-relaxed);font-size:15px;line-height:var(--leading-relaxed);color:var(--color-gray-900)}.ck.ck-content:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.ck.ck-content{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg);--tw-border-style:none;--tw-outline-style:none;border-style:none;outline-style:none}@media (forced-colors:active){.ck.ck-content{outline-offset:2px;outline:2px solid #0000}}.ck.ck-content{font-family:Inter,system-ui,-apple-system,sans-serif;overflow-y:auto}@media (forced-colors:active){.ck.ck-content:focus{outline-offset:2px;outline:2px solid #0000}}.ck.ck-content h1,.ck.ck-content h2,.ck.ck-content h3,.ck.ck-content h4,.ck.ck-content h5,.ck.ck-content h6{margin-top:calc(var(--spacing)*4);margin-bottom:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}:is(.ck.ck-content h1,.ck.ck-content h2,.ck.ck-content h3,.ck.ck-content h4,.ck.ck-content h5,.ck.ck-content h6):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.ck.ck-content h1{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.ck.ck-content h2{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.ck.ck-content h3{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.ck.ck-content h4{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.ck.ck-content h5{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.ck.ck-content h6{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ck.ck-content p{margin-bottom:calc(var(--spacing)*4);color:var(--color-gray-700)}.ck.ck-content p:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-content blockquote{margin-block:calc(var(--spacing)*5);border-radius:var(--radius-md);padding-block:calc(var(--spacing)*3);padding-left:calc(var(--spacing)*5);background-color:var(--color-gray-50);color:var(--color-gray-700);font-style:italic;position:relative;overflow:hidden}.ck.ck-content blockquote:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-900);color:var(--color-gray-300)}.ck.ck-content blockquote{border-left-style:var(--tw-border-style);border-left-width:4px;border-color:var(--color-primary-500)}.ck.ck-content blockquote:before{content:"";top:calc(var(--spacing)*0);left:calc(var(--spacing)*0);height:100%;width:calc(var(--spacing)*1);background-color:var(--color-primary-500);position:absolute}.ck.ck-content ul,.ck.ck-content ol{margin-bottom:calc(var(--spacing)*4);margin-left:calc(var(--spacing)*6)}.ck.ck-content ul{list-style-type:disc}.ck.ck-content ol{list-style-type:decimal}.ck.ck-content li{margin-bottom:calc(var(--spacing)*1);color:var(--color-gray-700)}.ck.ck-content li:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-content a{color:var(--color-primary-600)}@media (hover:hover){.ck.ck-content a:hover{color:var(--color-primary-700)}}.ck.ck-content a:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-primary-500)}@media (hover:hover){.ck.ck-content a:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{color:var(--color-primary-400)}}.ck.ck-content a{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;text-decoration-line:underline;transition-duration:.2s}.ck.ck-content code{background-color:var(--color-gray-100);color:var(--color-gray-800)}.ck.ck-content code:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-800);color:var(--color-gray-200)}.ck.ck-content code{border-radius:var(--radius-sm);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.ck.ck-content pre{background-color:#101828e6}@supports (color:color-mix(in lab, red, red)){.ck.ck-content pre{background-color:color-mix(in oklab,var(--color-gray-900)90%,transparent)}}.ck.ck-content pre{color:var(--color-gray-100)}.ck.ck-content pre:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-900);color:var(--color-gray-100)}.ck.ck-content pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-800)}.ck.ck-content pre:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700)}.ck.ck-content pre{margin-block:calc(var(--spacing)*5);border-radius:var(--radius-md);padding:calc(var(--spacing)*4);font-family:var(--font-mono);--tw-leading:var(--leading-snug);font-size:13px;line-height:var(--leading-snug);overflow-x:auto}.ck.ck-content table{margin-block:calc(var(--spacing)*4);border-collapse:collapse;width:100%}.ck.ck-content table td,.ck.ck-content table th{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}:is(.ck.ck-content table td,.ck.ck-content table th):is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-content table th{background-color:var(--color-gray-100);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.ck.ck-content table th:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-700);color:var(--color-gray-100)}.ck.ck-content table td{background-color:var(--color-white);color:var(--color-gray-700)}.ck.ck-content table td:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800);color:var(--color-gray-300)}.ck.ck-content figure.image{margin-inline:auto;margin-block:calc(var(--spacing)*4);max-width:100%}.ck.ck-content figure.image img{border-radius:var(--radius-lg);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);max-width:100%;height:auto;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-widget{position:relative}.ck.ck-widget.ck-widget_selected{border-radius:var(--radius-sm);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-400)}@supports (color:color-mix(in lab, red, red)){.ck.ck-widget.ck-widget_selected{--tw-ring-color:color-mix(in oklab,var(--color-primary-400)50%,transparent)}}.ck.ck-widget__type-around__button{background-color:var(--color-primary-600);color:var(--color-white)}@media (hover:hover){.ck.ck-widget__type-around__button:hover{background-color:var(--color-primary-700)}}.ck.ck-widget__type-around__button{height:calc(var(--spacing)*6);width:calc(var(--spacing)*6);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;border-radius:3.40282e38px;justify-content:center;align-items:center;transition-duration:.2s;display:flex}.ck.ck-widget__resizer{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-primary-400);background-color:var(--color-primary-50)}@supports (color:color-mix(in lab, red, red)){.ck.ck-widget__resizer{background-color:color-mix(in oklab,var(--color-primary-50)20%,transparent)}}.ck.ck-widget__resizer:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-900)}@supports (color:color-mix(in lab, red, red)){.ck.ck-widget__resizer:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:color-mix(in oklab,var(--color-primary-900)20%,transparent)}}.ck.ck-widget__resizer__handle{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3);border-radius:var(--radius-sm);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-primary-700);background-color:var(--color-primary-600)}.ck.ck-content .ck-placeholder:before{color:var(--color-gray-400);font-style:italic}.ck.ck-content .ck-placeholder:before:is(){color:var(--color-gray-500)}.ck.ck-balloon-panel{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white)}.ck.ck-balloon-panel:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-700);background-color:var(--color-base-800)}.ck.ck-balloon-panel{border-radius:var(--radius-lg);padding:calc(var(--spacing)*2);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-link-form,.ck.ck-link-actions{align-items:center;gap:calc(var(--spacing)*2);padding:calc(var(--spacing)*2);display:flex}.ck.ck-link-form .ck-input{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);flex:1}.ck.ck-link-form .ck-input:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-link-form .ck-input{background-color:var(--color-white);color:var(--color-gray-900)}.ck.ck-link-form .ck-input:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-base-800);color:var(--color-gray-100)}.ck.ck-link-form .ck-input{border-radius:var(--radius-md);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.ck.ck-link-form .ck-input:focus{border-color:var(--color-primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-300)}.ck.ck-color-selector{gap:calc(var(--spacing)*1);padding:calc(var(--spacing)*2);grid-template-columns:repeat(6,minmax(0,1fr));display:grid}.ck.ck-color-selector .ck-color-selector__option{height:calc(var(--spacing)*7);width:calc(var(--spacing)*7);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ck.ck-color-selector .ck-color-selector__option:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-color-selector .ck-color-selector__option{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.15s;transition-duration:.15s}@media (hover:hover){.ck.ck-color-selector .ck-color-selector__option:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.ck.ck-color-selector .ck-color-selector__option.ck-on{border-color:var(--color-primary-500);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-500);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--color-white)}.ck.ck-color-selector .ck-color-selector__option.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-offset-color:var(--color-base-800)}.ck.ck-font-size-dropdown .ck-dropdown__panel{max-height:calc(var(--spacing)*48);overflow-y:auto}.ck.ck-list .ck-list__item{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}@media (hover:hover){.ck.ck-list .ck-list__item:hover{background-color:var(--color-gray-100)}.ck.ck-list .ck-list__item:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *):hover{background-color:var(--color-gray-700)}}.ck.ck-list .ck-list__item{cursor:pointer;color:var(--color-gray-700)}.ck.ck-list .ck-list__item:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-300)}.ck.ck-list .ck-list__item{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s}.ck.ck-list .ck-list__item.ck-on{border-radius:var(--radius-sm);background-color:var(--color-primary-100);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-primary-700)}.ck.ck-list .ck-list__item.ck-on:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-primary-700);color:var(--color-white)}.ck.ck-code-block-dropdown .ck-dropdown__panel{min-width:calc(var(--spacing)*56)}.ck.ck-content .ck-media__wrapper{margin-block:calc(var(--spacing)*4);border-radius:var(--radius-lg);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);overflow:hidden}.ck.ck-content .ck-media__placeholder{background-color:var(--color-gray-100);color:var(--color-gray-500)}.ck.ck-content .ck-media__placeholder:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-800);color:var(--color-gray-400)}.ck.ck-content .ck-media__placeholder{border-radius:var(--radius-lg);border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:2px;border-color:var(--color-gray-300);padding:calc(var(--spacing)*8);text-align:center}.ck.ck-content .ck-media__placeholder:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-content .todo-list{margin-left:calc(var(--spacing)*0);list-style-type:none}.ck.ck-content .todo-list li{margin-bottom:calc(var(--spacing)*2);align-items:flex-start;gap:calc(var(--spacing)*3);display:flex}.ck.ck-content .todo-list li .todo-list__label{align-items:center;gap:calc(var(--spacing)*2);display:flex}.ck.ck-content .todo-list li .todo-list__label input{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);border-radius:var(--radius-sm);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-primary-600)}.ck.ck-content .todo-list li .todo-list__label input:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-content .todo-list li .todo-list__label input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-300)}.ck.ck-content mark{background-color:var(--color-yellow-200);color:var(--color-gray-900)}.ck.ck-content mark:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-yellow-600);color:var(--color-gray-100)}.ck.ck-content mark{border-radius:var(--radius-sm);padding-inline:calc(var(--spacing)*1)}.ck.ck-source-editing-area{min-height:calc(var(--spacing)*48);background-color:var(--color-gray-50);width:100%;padding:calc(var(--spacing)*4)}.ck.ck-source-editing-area:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){background-color:var(--color-gray-900)}.ck.ck-source-editing-area{font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.ck.ck-source-editing-area:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-100)}.ck.ck-source-editing-area{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}.ck.ck-source-editing-area:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-600)}.ck.ck-source-editing-area:focus{border-color:var(--color-primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-300)}.ck.ck-source-editing-area{resize:vertical}.word-count{margin-top:calc(var(--spacing)*2);text-align:right}.ck.ck-word-count{justify-content:flex-end;gap:calc(var(--spacing)*4);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:flex}.ck.ck-word-count:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){color:var(--color-gray-400)}@media (max-width:768px){.ck.ck-toolbar__items{gap:calc(var(--spacing)*.5)}.ck.ck-button{height:calc(var(--spacing)*7);min-height:calc(var(--spacing)*7);padding-inline:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.ck.ck-content{min-height:calc(var(--spacing)*32);padding:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.ck.ck-dropdown__panel{min-width:100%}}@media (prefers-reduced-motion:no-preference){.ck.ck-button{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out)}.ck.ck-dropdown__panel{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out)}.ck.ck-widget{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out)}}.ck.ck-button:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-primary-400);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--color-white)}.ck.ck-button:focus-visible:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-offset-color:var(--color-base-800)}.ck.ck-content:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.ck.ck-content:focus{outline-offset:2px;outline:2px solid #0000}}@media (prefers-contrast:high){.ck.ck-button{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-400)}.ck.ck-button:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-500)}.ck.ck-content{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-400)}.ck.ck-content:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-gray-500)}}:root{--ck-color-base-background:var(--color-white);--ck-color-base-foreground:var(--color-gray-100);--ck-color-focus-border:var(--color-primary-600);--ck-color-text:var(--color-gray-900);--ck-color-shadow-drop:#00000026;--ck-color-shadow-inner:#0000001a;--ck-color-button-default-background:transparent;--ck-color-button-default-hover-background:var(--color-gray-100);--ck-color-button-on-background:var(--color-primary-100);--ck-color-button-on-hover-background:var(--color-primary-200);--ck-color-dropdown-panel-background:var(--color-white);--ck-color-dropdown-panel-border:var(--color-gray-200);--ck-color-dialog-background:var(--color-white);--ck-color-dialog-form-header-border:var(--color-gray-200);--ck-color-input-background:var(--color-white);--ck-color-input-border:var(--color-gray-300);--ck-color-input-error-border:var(--color-red-500);--ck-color-list-background:var(--color-white);--ck-color-list-button-hover-background:var(--color-gray-100);--ck-color-list-button-on-background:var(--color-primary-100);--ck-color-panel-background:var(--color-white);--ck-color-panel-border:var(--color-gray-200);--ck-color-toolbar-background:var(--color-gray-50);--ck-color-toolbar-border:var(--color-gray-200);--ck-color-base-border:var(--color-gray-200);--ck-color-tooltip-background:var(--color-gray-800);--ck-color-tooltip-text:var(--color-white);--ck-color-engine-placeholder-text:var(--color-gray-400);--ck-color-upload-bar-background:var(--color-primary-600);--ck-color-link-default:var(--color-primary-600);--ck-color-link-selected-background:var(--color-primary-100)}.dark,[data-theme=dark]{--ck-color-base-background:var(--color-base-800);--ck-color-base-foreground:var(--color-base-700);--ck-color-focus-border:var(--color-primary-600);--ck-color-text:var(--color-gray-100);--ck-color-shadow-drop:#0000004d;--ck-color-shadow-inner:#0003;--ck-color-button-default-background:transparent;--ck-color-button-default-hover-background:var(--color-gray-700);--ck-color-button-on-background:var(--color-primary-600);--ck-color-button-on-hover-background:var(--color-primary-700);--ck-color-dropdown-panel-background:var(--color-base-800);--ck-color-dropdown-panel-border:var(--color-gray-600);--ck-color-dialog-background:var(--color-base-800);--ck-color-dialog-form-header-border:var(--color-gray-600);--ck-color-input-background:var(--color-base-800);--ck-color-input-border:var(--color-gray-600);--ck-color-input-error-border:var(--color-red-500);--ck-color-list-background:var(--color-base-800);--ck-color-list-button-hover-background:var(--color-gray-700);--ck-color-list-button-on-background:var(--color-primary-600);--ck-color-panel-background:var(--color-base-800);--ck-color-panel-border:var(--color-gray-600);--ck-color-toolbar-background:var(--color-base-900);--ck-color-toolbar-border:var(--color-gray-700);--ck-color-base-border:var(--color-gray-700);--ck-color-tooltip-background:var(--color-gray-700);--ck-color-tooltip-text:var(--color-white);--ck-color-engine-placeholder-text:var(--color-gray-500);--ck-color-upload-bar-background:var(--color-primary-600);--ck-color-link-default:var(--color-primary-500);--ck-color-link-selected-background:var(--color-primary-800)}@keyframes ck-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.ck.ck-dropdown__panel{animation:.2s ease-out ck-fade-in}.ck.ck-content::-webkit-scrollbar{width:calc(var(--spacing)*2)}.ck.ck-content::-webkit-scrollbar-track{border-radius:var(--radius-sm);background-color:var(--color-gray-100)}.ck.ck-content::-webkit-scrollbar-track:is(){background-color:var(--color-gray-800)}.ck.ck-content::-webkit-scrollbar-thumb{border-radius:var(--radius-sm);background-color:var(--color-gray-400)}@media (hover:hover){.ck.ck-content::-webkit-scrollbar-thumb:hover{background-color:var(--color-gray-500)}}.ck.ck-content::-webkit-scrollbar-thumb:is(){background-color:var(--color-gray-600)}@media (hover:hover){.ck.ck-content::-webkit-scrollbar-thumb:is():hover{background-color:var(--color-gray-500)}}.ck-editor-container .ck-editor-container{margin-top:calc(var(--spacing)*0)}.form-row .ck-editor-container,.field-box .ck-editor-container{width:100%}.errors .ck.ck-editor{border-color:var(--color-red-600)}.errors .ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-red-500)}.errors .ck.ck-editor:focus-within{border-color:var(--color-red-600);--tw-ring-color:var(--color-red-200)}.errors .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-red-500);--tw-ring-color:#e4001466}@supports (color:color-mix(in lab, red, red)){.errors .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-color:color-mix(in oklab,var(--color-red-600)40%,transparent)}}.success .ck.ck-editor{border-color:var(--color-green-600)}.success .ck.ck-editor:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-green-500)}.success .ck.ck-editor:focus-within{border-color:var(--color-green-600);--tw-ring-color:var(--color-green-200)}.success .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){border-color:var(--color-green-500);--tw-ring-color:#00a54466}@supports (color:color-mix(in lab, red, red)){.success .ck.ck-editor:focus-within:is(.dark,.dark *,[data-theme=dark],[data-theme=dark] *){--tw-ring-color:color-mix(in oklab,var(--color-green-600)40%,transparent)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1} \ No newline at end of file +/* + * CKEditor 5 theme for Unfold (settings.CKEDITOR_5_CUSTOM_CSS). + * + * Plain CSS, no build step: django-ckeditor-5 links this file as-is after CKEditor's own CSS. + * It only overrides CKEditor's --ck-* variables (its documented theming method), so the editor's + * layout and features keep CKEditor's own styles. Colors come from unfold (UNFOLD["COLORS"], + * daisy-themes.css); each var() falls back to CKEditor's default. + * + * CKEditor declares its variables on :root and derives most of them from a few base ones + * (--ck-color-base-*), so overriding those on recolors the whole UI. The others are + * fixed light values and are overridden one by one for dark mode. + */ + +:root { + --ck-color-base-border: var(--color-base-200, #ccced1); + --ck-color-base-text: var(--color-font-important-light, #333); + --ck-color-focus-border: var(--color-primary-600, hsl(218, 81.8%, 56.9%)); + --ck-color-link-default: var(--color-primary-600, #0000f0); + + --ck-color-button-on-color: var(--color-primary-600, #2977ff); + --ck-color-button-on-background: var(--color-primary-50, #f0f7ff); + --ck-color-button-on-hover-background: var(--color-primary-100, #dbecff); + --ck-color-button-on-active-background: var(--color-primary-100, #dbecff); +} + +/* unfold's dark mode: the "dark" class on , also set for daisyUI dark themes */ +.dark, +[data-theme="dark"] { + color-scheme: dark; + + --ck-color-base-background: var(--color-base-900, #111827); + --ck-color-base-foreground: var(--color-base-800, #1f2937); + --ck-color-base-border: var(--color-base-700, #374151); + --ck-color-base-text: var(--color-font-important-dark, #f9fafb); + --ck-color-focus-border: var(--color-primary-500, #0ea5e9); + --ck-color-focus-outer-shadow: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 35%, transparent); + --ck-color-shadow-drop: rgba(0, 0, 0, 0.4); + --ck-color-shadow-drop-active: rgba(0, 0, 0, 0.5); + --ck-color-shadow-inner: rgba(0, 0, 0, 0.3); + + /* buttons */ + --ck-color-button-default-hover-background: var(--color-base-800, #1f2937); + --ck-color-button-default-active-background: var(--color-base-700, #374151); + --ck-color-button-on-color: var(--color-primary-400, #38bdf8); + --ck-color-button-on-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 20%, transparent); + --ck-color-button-on-hover-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 30%, transparent); + --ck-color-button-on-active-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 35%, transparent); + --ck-color-button-on-disabled-background: var(--color-base-800, #1f2937); + --ck-color-button-action-text: #fff; + --ck-color-split-button-hover-background: var(--color-base-700, #374151); + --ck-color-split-button-hover-border: var(--color-base-600, #4b5563); + + /* inputs and placeholders */ + --ck-color-input-disabled-background: var(--color-base-800, #1f2937); + --ck-color-input-disabled-text: var(--color-font-subtle-dark, #9ca3af); + --ck-color-engine-placeholder-text: var(--color-font-subtle-dark, #9ca3af); + --ck-color-media-embed-placeholder-url-text: var(--color-font-subtle-dark, #9ca3af); + + /* selection, widgets, links */ + --ck-color-editable-blur-selection: var(--color-base-700, #374151); + --ck-color-widget-blurred-border: var(--color-base-600, #4b5563); + --ck-color-link-default: var(--color-primary-400, #38bdf8); + --ck-color-link-selected-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 20%, transparent); + --ck-color-code-block-label-background: var(--color-base-600, #4b5563); + + /* editing area: CKEditor's content text is #000 by default */ + --ck-content-font-color: var(--color-font-important-dark, #f9fafb); + --ck-content-color-image-caption-background: var(--color-base-800, #1f2937); + --ck-content-color-image-caption-text: var(--color-font-default-dark, #d1d5db); + --ck-content-color-table-caption-background: var(--color-base-800, #1f2937); + --ck-content-color-table-caption-text: var(--color-font-default-dark, #d1d5db); + --ck-content-color-mention-text: #f472b6; +} + +/* the one fixed content color without a variable: code blocks are #353535 text */ +.dark .ck-content pre, +[data-theme="dark"] .ck-content pre { + color: var(--color-font-default-dark, #d1d5db); + border-color: var(--color-base-700, #374151); +} From 3fd225510e583a29c497edebaf5621519df19a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20T=C3=BA=20Anh?= Date: Wed, 21 Oct 2026 20:33:36 +0700 Subject: [PATCH 3/3] CKEditor: cover the remaining dark-mode colors and restore the Inter font - content font back to Inter (the rewrite dropped the old font-family rule), written out so Tailwind does not emit its default font theme variable into styles.css - dark variables scoped to html.dark (Unfold's dark class) instead of any .dark or [data-theme="dark"] element; color-scheme set on the editor only, not the page - dark mode: keyboard shortcuts dialog, caption highlight flash, style panel and table border helpers; rules for todo checkbox borders and find & replace matches - light mode: focus glow follows the primary color - test reads the packaged file by path, rejects more Tailwind directives, and fails when CKEditor has a fixed light color that is neither overridden nor reviewed - docs: how to extend the packaged theme with @import (django-ckeditor-5 loads one file), and the fallbacks described correctly Co-Authored-By: Claude Opus 5.5 --- docs/CKEDITOR5_CUSTOMIZATION.md | 32 ++++++---- whiteneuron/base/tests_regressions.py | 71 ++++++++++++++++++++--- whiteneuron/static/base/css/ckeditor5.css | 64 +++++++++++++++----- 3 files changed, 136 insertions(+), 31 deletions(-) diff --git a/docs/CKEDITOR5_CUSTOMIZATION.md b/docs/CKEDITOR5_CUSTOMIZATION.md index b4f5370..0346ff8 100644 --- a/docs/CKEDITOR5_CUSTOMIZATION.md +++ b/docs/CKEDITOR5_CUSTOMIZATION.md @@ -37,33 +37,45 @@ Layout, sizes and feature styles (colour pickers, highlights, image styles, tabl todo lists) stay CKEditor's own. - **Colors** come from Unfold: `--color-base-*`, `--color-primary-*` and `--color-font-*` - (`UNFOLD["COLORS"]`, remapped per theme by `daisy-themes.css`). Every `var()` has CKEditor's - default as its fallback. + (`UNFOLD["COLORS"]`, remapped per theme by `daisy-themes.css`). The `var()` fallbacks only apply + without them: CKEditor's defaults in light mode, Tailwind grays in dark mode. +- **Content font** is Inter, Unfold's font. - **Dark mode** follows Unfold's `dark` class on ``, which daisyUI dark themes also set. CKEditor's light defaults that are not derived from its base variables (button and input states, - placeholders, selection, content text, captions) are overridden one by one. -- **Code blocks** are the one exception: CKEditor gives `pre` a fixed `#353535` text color with no - variable, so a single dark-mode rule recolors it. + placeholders, selection, content text, captions, panels) are overridden one by one. A regression + test lists the ones left as they are (accents, translucent overlays, badges) and fails when a + django-ckeditor-5 upgrade adds a new one. +- **Fixed colors without a variable** get a dark-mode rule each: code block text (`#353535`), todo + list checkbox borders (`#333`) and the text of find & replace matches. ## Customizing in a project -Point `CKEDITOR_5_CUSTOM_CSS` at your own static file and override variables there. Start from the -packaged file, or keep it and load yours after it: +django-ckeditor-5 loads a single `CKEDITOR_5_CUSTOM_CSS` file. To keep the packaged theme and adjust +it, point the setting at your own static file and import the packaged one first: + +```python +CKEDITOR_5_CUSTOM_CSS = "myproject/css/ckeditor5.css" +``` ```css +/* static/myproject/css/ckeditor5.css */ +@import url("../../base/css/ckeditor5.css"); + :root { --ck-color-focus-border: #7c3aed; - --ck-content-font-family: "Inter", system-ui, sans-serif; } -.dark { +html.dark { --ck-color-base-background: #0b1020; } ``` +Your rules come after the import and use the same selectors, so they win. Django's manifest +storages rewrite the relative `@import` to the hashed file name. + CKEditor declares its variables on `:root` and derives most of them from a few base ones (`--ck-color-base-background`, `-foreground`, `-border`, `-text`). Override them on `` (`:root`, -`.dark`) so the derived variables follow. The full list is in CKEditor's CSS +`html.dark`) so the derived variables follow. The full list is in CKEditor's CSS (`django_ckeditor_5/static/django_ckeditor_5/dist/styles.css`). If you need Tailwind utilities, compile your stylesheet in your own build and point diff --git a/whiteneuron/base/tests_regressions.py b/whiteneuron/base/tests_regressions.py index c0ed42a..08e6235 100644 --- a/whiteneuron/base/tests_regressions.py +++ b/whiteneuron/base/tests_regressions.py @@ -1,5 +1,7 @@ """Regression tests for the hardening / performance changes.""" import asyncio +import re +from pathlib import Path from unittest import mock from django.contrib.admin.sites import AdminSite @@ -715,13 +717,68 @@ def broken(**kwargs): class CKEditorCustomCssTests(SimpleTestCase): + # CKEditor's fixed light colors that stay readable in dark mode: accents, translucent overlays, + # self-contained badges and tooltips; highlights get dark text from the browser (MarkText) + READABLE_IN_DARK = { + "--ck-color-base-action", "--ck-color-base-active", "--ck-color-base-active-focus", + "--ck-color-base-error", "--ck-color-base-focus", "--ck-color-button-action-active-background", + "--ck-color-button-action-disabled-background", "--ck-color-button-action-hover-background", + "--ck-color-button-cancel", "--ck-color-button-save", "--ck-color-color-grid-check-icon", + "--ck-color-image-upload-icon", "--ck-color-image-upload-icon-background", "--ck-color-light-red", + "--ck-color-switch-button-off-background", "--ck-color-switch-button-off-hover-background", + "--ck-color-switch-button-on-hover-background", "--ck-color-upload-bar-background", + "--ck-color-upload-placeholder-loader", "--ck-color-widget-hover-border", + "--ck-html-embed-source-disabled-color", "--ck-show-blocks-border-color", + # translucent + "--ck-color-focus-disabled-shadow", "--ck-color-focus-error-shadow", "--ck-color-link-fake-selection", + "--ck-color-switch-button-inner-shadow", "--ck-color-table-focused-cell-background", + "--ck-content-color-mention-background", "--ck-dialog-overlay-background-color", + "--ck-table-selected-cell-background", + # badges and tooltips with their own background + "--ck-color-resizer-tooltip-background", "--ck-color-resizer-tooltip-text", + "--ck-evaluation-badge-background", "--ck-evaluation-badge-text-color", + "--ck-powered-by-background", "--ck-powered-by-text-color", + # highlights: find results get dark text from a rule, markers from the browser + "--ck-color-highlight-background", "--ck-content-highlight-marker-blue", + "--ck-content-highlight-marker-green", "--ck-content-highlight-marker-pink", + "--ck-content-highlight-marker-yellow", "--ck-content-highlight-pen-green", + "--ck-content-highlight-pen-red", + } + + @staticmethod + def _read(package, path): + return (Path(package.__file__).parent / path).read_text(encoding="utf-8") + + def setUp(self): + import whiteneuron + + # the packaged file itself: staticfiles finders may return a project's file of the same name + self.css = self._read(whiteneuron, "static/base/css/ckeditor5.css") + def test_packaged_css_is_plain_css(self): # django-ckeditor-5 links CKEDITOR_5_CUSTOM_CSS as-is: the file used to be Tailwind source, so # browsers dropped every @apply rule and theme() left the editor transparent (unreadable in dark mode) - from django.contrib.staticfiles import finders - - with open(finders.find("base/css/ckeditor5.css"), encoding="utf-8") as f: - css = f.read() - for source_only in ("@apply", "theme(", "@custom-variant", "@tailwind"): - self.assertNotIn(source_only, css) - self.assertIn("--ck-color-base-background", css) + source_only = ( + "@apply", "theme(", "@custom-variant", "@tailwind", "@theme", "@utility", "@variant", + "@source", "@plugin", "@reference", "--alpha(", "--spacing(", + ) + for token in source_only: + self.assertNotIn(token, self.css) + self.assertIn("--ck-color-base-background", self.css) + + def test_dark_mode_covers_ckeditor_light_colors(self): + # CKEditor's fixed light colors (not derived from --ck-color-base-*) must be overridden for dark + # mode or reviewed above, so a django-ckeditor-5 upgrade that adds one fails here + import django_ckeditor_5 + + ckeditor_css = self._read(django_ckeditor_5, "static/django_ckeditor_5/dist/styles.css") + fixed = { + name + for block in re.findall(r":root\{([^}]*)\}", ckeditor_css) + for name, value in re.findall(r"(--ck-[\w-]+):([^;]+)", block) + if re.match(r"\s*(#[0-9a-f]{3,8}\b|rgba?\(|hsla?\()", value, re.I) and "var(" not in value + } + self.assertGreater(len(fixed), 50) # the parse still finds CKEditor's variables + dark = re.search(r"html\.dark \{([^}]*)\}", self.css).group(1) + overridden = set(re.findall(r"(--ck-[\w-]+):", dark)) + self.assertEqual(sorted(fixed - overridden - self.READABLE_IN_DARK), []) diff --git a/whiteneuron/static/base/css/ckeditor5.css b/whiteneuron/static/base/css/ckeditor5.css index 5bbe6f2..1fd0433 100644 --- a/whiteneuron/static/base/css/ckeditor5.css +++ b/whiteneuron/static/base/css/ckeditor5.css @@ -4,17 +4,24 @@ * Plain CSS, no build step: django-ckeditor-5 links this file as-is after CKEditor's own CSS. * It only overrides CKEditor's --ck-* variables (its documented theming method), so the editor's * layout and features keep CKEditor's own styles. Colors come from unfold (UNFOLD["COLORS"], - * daisy-themes.css); each var() falls back to CKEditor's default. + * daisy-themes.css); the var() fallbacks only apply without them: CKEditor's defaults in light + * mode, Tailwind grays in dark mode. * * CKEditor declares its variables on :root and derives most of them from a few base ones * (--ck-color-base-*), so overriding those on recolors the whole UI. The others are - * fixed light values and are overridden one by one for dark mode. + * fixed light values and are overridden one by one for dark mode; tests_regressions.py fails + * when a CKEditor upgrade adds one that is neither overridden nor reviewed. */ :root { + /* unfold's font, written out: Tailwind scans this file, and referencing its font theme variable + makes styles.css emit Tailwind's default font, overriding unfold's */ + --ck-content-font-family: "Inter", system-ui, -apple-system, sans-serif; + --ck-color-base-border: var(--color-base-200, #ccced1); --ck-color-base-text: var(--color-font-important-light, #333); --ck-color-focus-border: var(--color-primary-600, hsl(218, 81.8%, 56.9%)); + --ck-color-focus-outer-shadow: var(--color-primary-200, #cae1fc); --ck-color-link-default: var(--color-primary-600, #0000f0); --ck-color-button-on-color: var(--color-primary-600, #2977ff); @@ -24,16 +31,15 @@ } /* unfold's dark mode: the "dark" class on , also set for daisyUI dark themes */ -.dark, -[data-theme="dark"] { - color-scheme: dark; +html.dark { + --wn-ck-primary: var(--color-primary-500, #0ea5e9); --ck-color-base-background: var(--color-base-900, #111827); --ck-color-base-foreground: var(--color-base-800, #1f2937); --ck-color-base-border: var(--color-base-700, #374151); --ck-color-base-text: var(--color-font-important-dark, #f9fafb); - --ck-color-focus-border: var(--color-primary-500, #0ea5e9); - --ck-color-focus-outer-shadow: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 35%, transparent); + --ck-color-focus-border: var(--wn-ck-primary); + --ck-color-focus-outer-shadow: color-mix(in srgb, var(--wn-ck-primary) 35%, transparent); --ck-color-shadow-drop: rgba(0, 0, 0, 0.4); --ck-color-shadow-drop-active: rgba(0, 0, 0, 0.5); --ck-color-shadow-inner: rgba(0, 0, 0, 0.3); @@ -42,9 +48,9 @@ --ck-color-button-default-hover-background: var(--color-base-800, #1f2937); --ck-color-button-default-active-background: var(--color-base-700, #374151); --ck-color-button-on-color: var(--color-primary-400, #38bdf8); - --ck-color-button-on-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 20%, transparent); - --ck-color-button-on-hover-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 30%, transparent); - --ck-color-button-on-active-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 35%, transparent); + --ck-color-button-on-background: color-mix(in srgb, var(--wn-ck-primary) 20%, transparent); + --ck-color-button-on-hover-background: color-mix(in srgb, var(--wn-ck-primary) 30%, transparent); + --ck-color-button-on-active-background: color-mix(in srgb, var(--wn-ck-primary) 35%, transparent); --ck-color-button-on-disabled-background: var(--color-base-800, #1f2937); --ck-color-button-action-text: #fff; --ck-color-split-button-hover-background: var(--color-base-700, #374151); @@ -60,8 +66,18 @@ --ck-color-editable-blur-selection: var(--color-base-700, #374151); --ck-color-widget-blurred-border: var(--color-base-600, #4b5563); --ck-color-link-default: var(--color-primary-400, #38bdf8); - --ck-color-link-selected-background: color-mix(in srgb, var(--color-primary-500, #0ea5e9) 20%, transparent); + --ck-color-link-selected-background: color-mix(in srgb, var(--wn-ck-primary) 20%, transparent); --ck-color-code-block-label-background: var(--color-base-600, #4b5563); + --ck-color-image-caption-highlighted-background: var(--color-base-600, #4b5563); + --ck-color-table-caption-highlighted-background: var(--color-base-600, #4b5563); + + /* panels: keyboard shortcuts dialog (Alt+0), style grid */ + --ck-accessibility-help-dialog-border-color: var(--color-base-700, #374151); + --ck-accessibility-help-dialog-code-background-color: var(--color-base-700, #374151); + --ck-accessibility-help-dialog-kbd-shadow-color: var(--color-base-950, #030712); + --ck-style-panel-button-label-background: var(--color-base-800, #1f2937); + --ck-style-panel-button-hover-label-background: var(--color-base-700, #374151); + --ck-style-panel-button-hover-border-color: var(--color-base-600, #4b5563); /* editing area: CKEditor's content text is #000 by default */ --ck-content-font-color: var(--color-font-important-dark, #f9fafb); @@ -70,11 +86,31 @@ --ck-content-color-table-caption-background: var(--color-base-800, #1f2937); --ck-content-color-table-caption-text: var(--color-font-default-dark, #d1d5db); --ck-content-color-mention-text: #f472b6; + --ck-table-content-default-border-color: var(--color-base-600, #4b5563); + --ck-table-layout-default-border-color: var(--color-base-600, #4b5563); + --ck-table-border-none-helper-line-color: var(--color-base-600, #4b5563); +} + +/* native scrollbars and controls inside the editor; the page's color-scheme is unfold's */ +html.dark .ck { + color-scheme: dark; } -/* the one fixed content color without a variable: code blocks are #353535 text */ -.dark .ck-content pre, -[data-theme="dark"] .ck-content pre { +/* fixed content colors without a variable */ + +/* code blocks: #353535 text */ +html.dark .ck-content pre { color: var(--color-font-default-dark, #d1d5db); border-color: var(--color-base-700, #374151); } + +/* todo list checkboxes: #333 border (checked ones keep CKEditor's green) */ +html.dark .ck-content .todo-list .todo-list__label > input::before, +html.dark .ck-content .todo-list .todo-list__label > span[contenteditable="false"] > input::before { + border-color: var(--color-base-400, #9ca3af); +} + +/* find & replace matches: --ck-color-text (light in dark mode) on yellow / #ff9633 */ +html.dark .ck-find-result { + color: var(--color-base-900, #111827); +}