diff --git a/static/blog/css/article.css b/static/blog/css/article.css index d4c17af..4bd321f 100644 --- a/static/blog/css/article.css +++ b/static/blog/css/article.css @@ -1,90 +1,462 @@ -/* Стили для содержимого статьи */ -.article-content { - font-size: 1rem; - line-height: 1.6; +/* + * article.css + * Styles for CKEditor 5 rendered content inside .content-card + * and for the article detail page layout. + * + * Scope: all rules are under .content-card .card-content + * so they never leak into the rest of the site. + */ + +/* ===== ARTICLE PAGE LAYOUT ===== */ + +.article-meta { + display: flex; + align-items: center; + gap: 1.5rem; + flex-wrap: wrap; + font-size: 0.9rem; + color: var(--text-light); + margin-bottom: 2rem; + padding-bottom: 1.5rem; + border-bottom: 1px solid var(--border-light); +} + +.article-meta span { + display: flex; + align-items: center; + gap: 0.4rem; +} + +.article-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 1.5rem 0; +} + +.article-tag { + display: inline-block; + padding: 0.25rem 0.75rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-light); + border-radius: var(--radius-sm); + font-size: 0.8rem; + color: var(--text-secondary); + text-decoration: none; + transition: var(--transition); +} + +.article-tag:hover { + background: var(--primary); + color: white; + border-color: var(--primary); +} + +/* ===== CKEDITOR 5 CONTENT ===== */ + +.content-card .card-content { + font-size: 1.05rem; + line-height: 1.8; + color: var(--text-primary); + word-break: break-word; +} + +/* --- Headings --- */ + +.content-card .card-content h2, +.content-card .card-content h3, +.content-card .card-content h4, +.content-card .card-content h5, +.content-card .card-content h6 { + font-weight: 700; + line-height: 1.3; + margin-top: 2rem; + margin-bottom: 0.75rem; color: var(--text-primary); } -.article-content h1, -.article-content h2, -.article-content h3, -.article-content h4, -.article-content h5, -.article-content h6 { - margin-top: 1.5em; - margin-bottom: 0.75em; - font-weight: 600; +.content-card .card-content h2 { + font-size: 1.75rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--border-light); } -.article-content h1 { font-size: 2rem; } -.article-content h2 { font-size: 1.75rem; } -.article-content h3 { font-size: 1.5rem; } - -.article-content p { - margin-bottom: 1rem; -} - -.article-content ul, -.article-content ol { - margin-bottom: 1rem; - padding-left: 2rem; -} - -.article-content li { - margin-bottom: 0.25rem; -} - -.article-content blockquote { +.content-card .card-content h3 { + font-size: 1.375rem; + padding-left: 0.75rem; border-left: 4px solid var(--primary); - padding: 0.5rem 1rem; - background: var(--bg-secondary); - margin: 1rem 0; - font-style: italic; } -.article-content table { +.content-card .card-content h4 { + font-size: 1.125rem; + color: var(--text-secondary); +} + +/* --- Paragraphs --- */ + +.content-card .card-content p { + margin-bottom: 1.25rem; +} + +.content-card .card-content p:last-child { + margin-bottom: 0; +} + +/* --- Links --- */ + +.content-card .card-content a { + color: var(--primary); + text-decoration: underline; + text-decoration-color: rgba(255, 107, 0, 0.4); + text-underline-offset: 3px; + transition: var(--transition); +} + +.content-card .card-content a:hover { + color: var(--primary-dark); + text-decoration-color: var(--primary-dark); +} + +/* --- Lists --- */ + +.content-card .card-content ul, +.content-card .card-content ol { + margin: 0 0 1.25rem 0; + padding-left: 1.75rem; +} + +.content-card .card-content ul { + list-style: none; + padding-left: 0; +} + +.content-card .card-content ul li { + position: relative; + padding-left: 1.5rem; + margin-bottom: 0.5rem; +} + +.content-card .card-content ul li::before { + content: '▸'; + position: absolute; + left: 0; + color: var(--primary); + font-weight: bold; +} + +.content-card .card-content ol li { + margin-bottom: 0.5rem; +} + +.content-card .card-content ul ul, +.content-card .card-content ol ol, +.content-card .card-content ul ol, +.content-card .card-content ol ul { + margin-top: 0.5rem; + margin-bottom: 0; +} + +/* --- Blockquote --- */ + +.content-card .card-content blockquote { + margin: 1.5rem 0; + padding: 1.25rem 1.5rem; + border-left: 4px solid var(--primary); + background: var(--bg-secondary); + border-radius: 0 var(--radius-md) var(--radius-md) 0; + color: var(--text-secondary); + font-style: italic; + font-size: 1.05rem; +} + +.content-card .card-content blockquote p:last-child { + margin-bottom: 0; +} + +/* --- Inline code (not inside pre) --- */ +/* + * High specificity is fine here because this selector explicitly + * excludes pre > code, so it never touches hljs blocks. + */ + +.content-card .card-content :not(pre) > code { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 0.875em; + background: var(--bg-tertiary); + color: var(--primary-dark); + padding: 0.15em 0.4em; + border-radius: 4px; + border: 1px solid var(--border-light); + word-break: break-all; +} + +/* --- Code blocks (CKEditor generates
) --- */
+/*
+ * ALL pre and pre code rules use :where() — zero specificity.
+ * This guarantees hljs always wins on colors, background, and padding.
+ *
+ * Responsibility split:
+ * article.css → layout only: margin, border-radius, overflow, shadow
+ * hljs CSS → everything visual: background, color, padding, font-size
+ *
+ * font-size:0 on pre prevents inherited line-height:1.8 from inflating
+ * the spacing between lines (line-height is computed from font-size,
+ * so zeroing it on the wrapper element neutralises the inheritance).
+ * font-size is restored on pre code.
+ */
+
+:where(.content-card .card-content) pre {
+ margin: 1.5rem 0;
+ padding: 0; /* hljs sets padding:1em on code.hljs — don't double it */
+ border-radius: var(--radius-md);
+ overflow-x: auto;
+ box-shadow: var(--shadow-md);
+ font-size: 0; /* neutralise inherited line-height */
+ line-height: 1;
+ background: none; /* hljs controls background (#fff or #282c34) */
+}
+
+:where(.content-card .card-content) pre code {
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+ font-size: 0.9rem; /* restore font-size for hljs to work from */
+ line-height: 1.5;
+ word-break: normal;
+ white-space: pre;
+ /* NO color, NO background, NO padding — hljs owns these */
+}
+
+/* --- Tables --- */
+
+.content-card .card-content .table-wrapper,
+.content-card .card-content figure.table {
+ overflow-x: auto;
+ margin: 1.5rem 0;
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-sm);
+}
+
+.content-card .card-content table {
width: 100%;
border-collapse: collapse;
- margin: 1rem 0;
+ font-size: 0.95rem;
}
-.article-content th,
-.article-content td {
- border: 1px solid var(--border-light);
- padding: 0.5rem;
+.content-card .card-content th {
+ background: var(--bg-secondary);
+ font-weight: 700;
+ text-align: left;
+ padding: 0.875rem 1rem;
+ border-bottom: 2px solid var(--border-medium);
+ color: var(--text-primary);
+ white-space: nowrap;
}
-.article-content th {
+.content-card .card-content td {
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid var(--border-light);
+ color: var(--text-secondary);
+ vertical-align: top;
+}
+
+.content-card .card-content tr:last-child td {
+ border-bottom: none;
+}
+
+.content-card .card-content tbody tr:hover td {
background: var(--bg-secondary);
}
-.article-content img {
+/* CKEditor 5 table with header row class */
+.content-card .card-content table.ck-table-resized th,
+.content-card .card-content table.ck-table-resized td {
+ border: 1px solid var(--border-light);
+}
+
+/* --- Images (CKEditor wraps in ) --- */
+
+.content-card .card-content figure.image {
+ margin: 1.5rem auto;
+ text-align: center;
+ max-width: 100%;
+}
+
+.content-card .card-content figure.image img,
+.content-card .card-content img {
max-width: 100%;
height: auto;
- margin: 1rem 0;
- border-radius: 4px;
+ display: block;
+ margin: 0 auto;
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-md);
+ cursor: pointer; /* triggers the click-to-enlarge modal */
}
-.article-content pre {
- background: #2d2d2d;
- color: #ccc;
- padding: 1rem;
- border-radius: 4px;
- overflow-x: auto;
- margin: 1rem 0;
+.content-card .card-content figure.image figcaption {
+ margin-top: 0.5rem;
+ font-size: 0.875rem;
+ color: var(--text-light);
+ font-style: italic;
+ text-align: center;
}
-.article-content code {
- font-family: 'Courier New', monospace;
- background: #2d2d2d;
- color: #ccc;
- padding: 0.2rem 0.4rem;
- border-radius: 3px;
- font-size: 0.9em;
+/* CKEditor alignment classes */
+.content-card .card-content figure.image.image-style-align-left {
+ float: left;
+ margin: 0.5rem 1.5rem 1rem 0;
+ max-width: 45%;
}
-.article-content pre code {
- padding: 0;
- background: none;
- color: inherit;
+.content-card .card-content figure.image.image-style-align-right {
+ float: right;
+ margin: 0.5rem 0 1rem 1.5rem;
+ max-width: 45%;
+}
+
+/* Clearfix after floated images */
+.content-card .card-content::after {
+ content: '';
+ display: table;
+ clear: both;
+}
+
+/* --- Horizontal rule --- */
+
+.content-card .card-content hr {
+ border: none;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, var(--border-medium), transparent);
+ margin: 2rem 0;
+}
+
+/* --- Alert/info boxes (CKEditor custom classes if used) --- */
+
+.content-card .card-content .info-box {
+ padding: 1rem 1.25rem;
+ border-radius: var(--radius-md);
+ margin: 1.5rem 0;
+ border-left: 4px solid var(--accent);
+ background: rgba(0, 168, 255, 0.08);
+ color: var(--text-secondary);
+ font-size: 0.95rem;
+}
+
+/* ===== COMMENTS SECTION ===== */
+
+.comments-section {
+ margin-top: 3rem;
+}
+
+.comments-section h3 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin-bottom: 1.5rem;
+ color: var(--text-primary);
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.comment-card {
+ background: var(--bg-secondary);
+ border-radius: var(--radius-lg);
+ padding: 1.25rem 1.5rem;
+ margin-bottom: 1rem;
+ border: 1px solid var(--border-light);
+ border-left: 3px solid var(--secondary);
+}
+
+.comment-author {
+ font-weight: 600;
+ color: var(--text-primary);
+ font-size: 0.95rem;
+}
+
+.comment-date {
+ font-size: 0.8rem;
+ color: var(--text-light);
+ margin-left: 0.75rem;
+}
+
+.comment-body {
+ margin-top: 0.75rem;
+ color: var(--text-secondary);
+ line-height: 1.6;
+}
+
+.comment-form-card {
+ background: var(--bg-card);
+ border-radius: var(--radius-xl);
+ padding: 2rem;
+ box-shadow: var(--shadow-md);
+ border: 1px solid var(--border-light);
+ margin-top: 2rem;
+ position: relative;
+}
+
+.comment-form-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 4px;
+ background: var(--gradient-secondary);
+ border-radius: var(--radius-xl) var(--radius-xl) 0 0;
+}
+
+.comment-form-card h4 {
+ font-size: 1.25rem;
+ font-weight: 700;
+ margin-bottom: 1.5rem;
+ color: var(--text-primary);
+}
+
+/* ===== RESPONSIVE ===== */
+
+@media (max-width: 768px) {
+ .content-card .card-content h2 {
+ font-size: 1.5rem;
+ }
+
+ .content-card .card-content h3 {
+ font-size: 1.25rem;
+ }
+
+ .content-card .card-content figure.image.image-style-align-left,
+ .content-card .card-content figure.image.image-style-align-right {
+ float: none;
+ max-width: 100%;
+ margin: 1rem auto;
+ }
+
+ /* :where() keeps specificity at zero so hljs still wins on mobile */
+ :where(.content-card .card-content) pre {
+ box-shadow: none;
+ margin: 1rem 0;
+ }
+
+ .content-card .card-content th,
+ .content-card .card-content td {
+ padding: 0.6rem 0.75rem;
+ font-size: 0.875rem;
+ }
+
+ .article-meta {
+ gap: 1rem;
+ font-size: 0.8rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .content-card .card-content {
+ font-size: 1rem;
+ }
+
+ .content-card .card-content h2 {
+ font-size: 1.375rem;
+ }
+
+ .content-card .card-content h3 {
+ font-size: 1.125rem;
+ }
}
\ No newline at end of file
diff --git a/static/programmer/js/theme-switcher.js b/static/programmer/js/theme-switcher.js
index 6226fda..5386bbe 100644
--- a/static/programmer/js/theme-switcher.js
+++ b/static/programmer/js/theme-switcher.js
@@ -1,78 +1,72 @@
-// Theme Switcher Script
-document.addEventListener('DOMContentLoaded', function() {
- const themeToggle = document.getElementById('theme-toggle');
+// theme-switcher.js
+document.addEventListener('DOMContentLoaded', function () {
+
+ const themeToggle = document.getElementById('theme-toggle');
const mobileThemeToggle = document.getElementById('mobile-theme-toggle');
- const themeCSS1C = document.getElementById('theme-css-1c');
+ const themeCSS1C = document.getElementById('theme-css-1c');
+
+ // FIX: Derive the base path once so we never rely on the current href value.
+ // href.replace() was silently failing when the filename didn't match exactly.
+ const hljsBasePath = themeCSS1C
+ ? themeCSS1C.href.replace(/1c-(light|dark)\.min\.css$/, '')
+ : null;
+
+ // console.log('Initial href:', themeCSS1C?.href);
+ // console.log('Computed basePath:', hljsBasePath);
+
+ // ── Theme application ────────────────────────────────────────────────────
+
+ function applyTheme(isDark) {
+ if (isDark) {
+ document.body.classList.add('theme-dark');
+ if (themeCSS1C && hljsBasePath) {
+ themeCSS1C.href = hljsBasePath + '1c-dark.min.css';
+ }
+ } else {
+ document.body.classList.remove('theme-dark');
+ if (themeCSS1C && hljsBasePath) {
+ themeCSS1C.href = hljsBasePath + '1c-light.min.css';
+ }
+ }
+
+ // FIX: CSS defines checked = dark (moon icon).
+ // Previously the handlers were calling the wrong function on check/uncheck.
+ if (themeToggle) themeToggle.checked = isDark;
+ if (mobileThemeToggle) mobileThemeToggle.checked = isDark;
+
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
+ }
+
+ // ── Initialise from saved preference ────────────────────────────────────
- // Проверяем сохраненную тему в localStorage
const savedTheme = localStorage.getItem('theme');
+ applyTheme(savedTheme === 'dark');
- // Устанавливаем светлую тему по умолчанию
- if (savedTheme === 'dark') {
- switchToDarkTheme();
- } else {
- switchToLightTheme(); // Светлая тема по умолчанию
- }
+ // ── Event listeners ──────────────────────────────────────────────────────
- // Обработчик переключения темы для десктопного переключателя
+ // FIX: Both toggles now call the same applyTheme() — no duplicated logic,
+ // and both are always kept in sync with each other automatically.
if (themeToggle) {
- themeToggle.addEventListener('change', function() {
- if (this.checked) {
- switchToLightTheme();
- } else {
- switchToDarkTheme();
- }
+ themeToggle.addEventListener('change', function () {
+ applyTheme(this.checked);
});
}
- // Обработчик переключения темы для мобильного переключателя
if (mobileThemeToggle) {
- mobileThemeToggle.addEventListener('change', function() {
- if (this.checked) {
- switchToLightTheme();
- } else {
- switchToDarkTheme();
- }
- // Синхронизируем оба переключателя
- if (themeToggle) {
- themeToggle.checked = this.checked;
- }
+ mobileThemeToggle.addEventListener('change', function () {
+ applyTheme(this.checked);
});
}
- function switchToLightTheme() {
- document.body.classList.remove('theme-dark');
- if (themeCSS1C) {
- themeCSS1C.href = themeCSS1C.href.replace('1c-dark.min.css', '1c-light.min.css');
- }
- if (themeToggle) themeToggle.checked = true;
- if (mobileThemeToggle) mobileThemeToggle.checked = true;
- localStorage.setItem('theme', 'light');
- // 👇 Обновляем иконку ИКС
- updateIKSTheme();
+ // ── CSS error handling ───────────────────────────────────────────────────
+
+ // FIX: was referencing undefined variable `themeCSS` instead of `themeCSS1C`
+ if (themeCSS1C) {
+ themeCSS1C.onerror = function () {
+ console.error('Ошибка загрузки CSS файла темы подсветки кода');
+ // Fall back to light theme
+ applyTheme(false);
+ };
}
- function switchToDarkTheme() {
- document.body.classList.add('theme-dark');
- if (themeCSS1C) {
- themeCSS1C.href = themeCSS1C.href.replace('1c-light.min.css', '1c-dark.min.css');
- }
- if (themeToggle) themeToggle.checked = false;
- if (mobileThemeToggle) mobileThemeToggle.checked = false;
- localStorage.setItem('theme', 'dark');
- // 👇 Обновляем иконку ИКС
- updateIKSTheme();
- }
-
- // Синхронизация переключателей при загрузке
- if (themeToggle && mobileThemeToggle) {
- mobileThemeToggle.checked = themeToggle.checked;
- }
-
- // Обработка ошибок загрузки CSS
- themeCSS.onerror = function() {
- console.error('Ошибка загрузки CSS файла темы');
- // Восстанавливаем светлую тему по умолчанию при ошибке
- themeCSS1C.href = themeCSS1C.href.replace('1c-light.min.css', '1c-dark.min.css');
- };
});
\ No newline at end of file