From ccb5e6b828927ccce322a114d4de757be23cd5eb Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 14:40:39 +0300 Subject: [PATCH 01/72] chore: update daisyui to v5.7.16 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4dfdfad..1934a29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@tailwindcss/typography": "^0.5.16", "autoprefixer": "^10.5.0", "concurrently": "^9.1.2", - "daisyui": "^5.0.0", + "daisyui": "^5.7.16", "postcss": "^8.5.14" } }, @@ -2671,9 +2671,9 @@ } }, "node_modules/daisyui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.0.tgz", - "integrity": "sha512-U0K9Bac3Bi3zZGm6ojrw12F0vBHTpEgf46zv/BYxLe07hF0Xnx7emIQliwaRBgJuYhY0BhwQ6wSnq5cJXHA2yA==", + "version": "5.7.16", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.7.16.tgz", + "integrity": "sha512-V9CJxvrWIXTDuP/0tpijjWaKaKJNYo2IrULJfTWs3/DjW9rPqswk1n8NCLQRLrTutmlG2Ech7nkJDpCnF+6Dzw==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index 4eaf289..6ad3e51 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@tailwindcss/typography": "^0.5.16", "autoprefixer": "^10.5.0", "concurrently": "^9.1.2", - "daisyui": "^5.0.0", + "daisyui": "^5.7.16", "postcss": "^8.5.14" }, "dependencies": { From dbbbe029f0f0532d59961acb007a44f974772c19 Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 14:44:15 +0300 Subject: [PATCH 02/72] perf: lazy load MathJax and ionicons --- layouts/_default/baseof.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index c5cbac2..0d82d9b 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -48,12 +48,12 @@ {{ end }} - {{ partialCached "core/mathjax.html" . }} - - - - - {{ partial "ui/context-menu.html" . }} - + {{- $hasMath := false -}} + {{- if .Store.Get "hasMathjax" -}} + {{- $hasMath = true -}} + {{- else if .IsPage -}} + {{- if findRE `\$|\\\(|\\\[` .RawContent 1 -}}{{- $hasMath = true -}}{{- end -}} + {{- end -}} + {{ if $hasMath }}{{ partialCached "core/mathjax.html" . }}{{ end }} From 9d441b3cc5ee731f2f817d8278590902e8634871 Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 14:53:46 +0300 Subject: [PATCH 03/72] cleanup: remove legacy theme leftovers --- layouts/_default/baseof.html | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 0d82d9b..0823f76 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -7,11 +7,10 @@ - {{ if .IsHome }} - {{ site.Params.motto }} | {{ site.Title }} - {{ else }} - {{ .Title }} | {{ site.Title }} - {{ end }} + {{- block "title" . -}} + {{- if .IsHome }}{{ site.Params.motto }} | {{ site.Title }} + {{- else }}{{ .Title }} | {{ site.Title }}{{ end -}} + {{- end -}} {{ partial "core/head.html" . }} @@ -22,24 +21,20 @@ -
- {{ partial "header/nav.html" . }} + {{ if or site.Params.backgroundImage site.Params.backgroundImageDark }} +
+ {{ end }} -
-
-
-
- {{ block "main" . }} {{ end }} + {{ partial "header/nav.html" . }} - {{ partial "footer/footer.html" . }} -
-
-
+
+ {{ block "main" . }}{{ end }} + {{ partial "footer/footer.html" . }}
{{ partial "core/scripts.html" . }} - {{ block "js" . }} {{ end }} + {{ block "js" . }}{{ end }} {{ if .Store.Get "hasMermaid" }} + {{ block "css" . }}{{ end }} From b4a49e8379463dfc909675ac91cda3cbcdf54bed Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 15:27:21 +0300 Subject: [PATCH 05/72] cleanup: remove orphaned unused partials --- layouts/partials/footer/contributors.html | 0 layouts/partials/header/navCustomItems.html | 16 --------- layouts/partials/header/renderNavItem.html | 39 --------------------- 3 files changed, 55 deletions(-) delete mode 100644 layouts/partials/footer/contributors.html delete mode 100644 layouts/partials/header/navCustomItems.html delete mode 100644 layouts/partials/header/renderNavItem.html diff --git a/layouts/partials/footer/contributors.html b/layouts/partials/footer/contributors.html deleted file mode 100644 index e69de29..0000000 diff --git a/layouts/partials/header/navCustomItems.html b/layouts/partials/header/navCustomItems.html deleted file mode 100644 index d350d0b..0000000 --- a/layouts/partials/header/navCustomItems.html +++ /dev/null @@ -1,16 +0,0 @@ -{{ $customItems := site.Params.navItems }} -{{ $keys := slice }} - -{{ range $k, $v := $customItems }} -{{ $keys = $keys | append $k }} -{{ end }} - -{{ $restNavItems := complement .navItems $keys }} -{{ $mobile := .mobile }} -{{ range $restNavItems }} - {{ if $mobile }} - {{ partial "renderMobileNavItem.html" . }} - {{ else }} - {{ partial "renderNavItem.html" . }} - {{ end }} -{{ end }} diff --git a/layouts/partials/header/renderNavItem.html b/layouts/partials/header/renderNavItem.html deleted file mode 100644 index c1a082f..0000000 --- a/layouts/partials/header/renderNavItem.html +++ /dev/null @@ -1,39 +0,0 @@ -{{ if and (eq . "about") }} - -{{ with site.GetPage "/about" }} -{{ $aboutPages := .Resources.ByType "page" }} -{{ if gt (len $aboutPages) 0 }} -
- {{- T "about" -}} -
-{{ end }} -{{ end }} - -{{ else if and (eq . "search") (gt (len (where site.Pages "Type" "search")) 0) }} - - - -{{ else if and (eq . "rss") site.Params.rss }} - - - -{{ else if and (eq . "categories") site.Taxonomies.categories }} - - - -{{ else if and (eq . "tags") site.Taxonomies.tags }} -{{ else }} -{{ $navItems := site.Params.navItems }} -{{ $item := index $navItems . }} -{{ with $item }} - {{ if .icon }} - - - - {{ else if .title }} - - {{ .title }} - - {{ end }} -{{ end }} -{{ end }} From 699ea9dfd3c9026841a24011495c213bc558d6ba Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 15:29:17 +0300 Subject: [PATCH 06/72] cleanup: drop dead CSS (list.css, disqus rule, flip-container, dream-alert, custom-search-input) --- src/input.css | 92 --------------------------------------------------- src/list.css | 40 ---------------------- 2 files changed, 132 deletions(-) delete mode 100644 src/list.css diff --git a/src/input.css b/src/input.css index ce95322..b91fdeb 100644 --- a/src/input.css +++ b/src/input.css @@ -13,11 +13,6 @@ @import "./css/syntax.css"; @import "./shortcodes.css"; -@import "./list.css" - -#disqus_thread iframe[src*='ads-iframe'] { - display: none; -} #dream-global-bg { position: fixed; @@ -30,97 +25,10 @@ z-index: -1; } -.flip-container { - width: 100vw; - /* The height of navbar is 112px (80px + 2rem). */ - height: calc(100vh - 80px - 2rem); - max-width: 100%; - perspective: 1000px; -} - -#TableOfContents { - @apply sticky top-24 overflow-y-auto; - - /* 6 + 1 rem */ - max-height: calc(100vh - 7rem); -} - #TableOfContents ul { @apply menu menu-sm; } -.dream-alert { - > .heading { - @apply flex items-center gap-1 text-sm font-medium; - } - - ion-icon { - font-size: 1rem; - } - - &.note { - @apply border-s-blue-600 dark:border-s-blue-500; - - .heading { - @apply text-blue-600 dark:text-blue-500; - } - } - - &.tip { - @apply border-s-green-600 dark:border-s-green-500; - - .heading { - @apply text-green-600 dark:text-green-500; - } - } - - &.important { - @apply border-s-purple-600 dark:border-s-purple-500; - - .heading { - @apply text-purple-600 dark:text-purple-500; - } - } - - &.warning { - @apply border-s-yellow-600 dark:border-s-yellow-500; - - .heading { - @apply text-yellow-600 dark:text-yellow-500; - } - } - - &.caution { - @apply border-s-red-600 dark:border-s-red-500; - - .heading { - @apply text-red-600 dark:text-red-500; - } - } -} - -.custom-search-input { - color: var(--color-base-content); - background-color: var(--color-base-100); - border: 2px solid var(--color-base-300); - border-radius: 64px; - padding: 1rem 1.5rem 1rem 3rem; - font-size: 1.125rem; - width: 100%; - outline: none; - transition: all 0.2s ease; - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); -} - -.custom-search-input::placeholder { - color: oklch(var(--color-base-content) / 0.6); -} - -.custom-search-input:focus { - border-color: var(--color-primary); - box-shadow: 0 0 0 3px oklch(var(--color-primary) / 0.2); -} - @layer components { /* Обертка кода */ .highlight { diff --git a/src/list.css b/src/list.css deleted file mode 100644 index 26f6bc1..0000000 --- a/src/list.css +++ /dev/null @@ -1,40 +0,0 @@ -.cn-list-head {} -.cn-list-abbr { - font-family: 'Pangolin', cursive; - font-size: 44px; line-height: 1; - color: var(--cn-primary); - flex-shrink: 0; - transform: translateY(2px); -} -.cn-list-title { - font-size: clamp(28px, 4vw, 38px); - font-weight: 800; line-height: 1.1; letter-spacing: -0.02em; - color: var(--cn-text); - margin: 0; -} -.cn-list-desc { - font-size: 15px; color: var(--cn-text-muted); - line-height: 1.6; max-width: 56ch; - margin: 0; -} -.cn-list-meta { - display: flex; flex-wrap: wrap; - align-items: center; gap: 14px; - margin-top: 16px; - font-size: 13px; color: var(--cn-text-muted); -} -.cn-list-term, .cn-list-count { - font-family: 'Pangolin', cursive; - font-size: 15px; - color: var(--cn-text); -} -.cn-list-side { - display: flex; flex-direction: column; - align-items: flex-start; - gap: 12px; - flex-shrink: 0; -} -@media (min-width: 768px) { .cn-list-side { align-items: flex-end; } } -.cn-list-tags { display: flex; flex-wrap: wrap; gap: 6px; } - -.cn-lectures .cn-card { min-height: 0; } From 1d462be31c84e0746921603cec17927323ee27e9 Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 16:00:36 +0300 Subject: [PATCH 07/72] fix: append site title suffix on 404 page --- layouts/404.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layouts/404.html b/layouts/404.html index e33ea60..ad3608b 100644 --- a/layouts/404.html +++ b/layouts/404.html @@ -1,4 +1,4 @@ -{{ define "title" }}404 — страница пропала{{ end }} +{{ define "title" }}404 — страница пропала | {{ site.Title }}{{ end }} {{ define "main" }} From 512bf3e7a272fc4426a027686249f18cd55afed7 Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 16:00:36 +0300 Subject: [PATCH 08/72] fix: single-page title cleanup and sidebar layout (no shift when hidden) --- layouts/_default/single.html | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 7113af0..8438f6d 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -1,17 +1,9 @@ -{{ define "title" }}{{ .Title }}{{ end }} -{{ define "css" }}{{ end }} - {{ define "main" }}
- {{ if not .Params.hideLeftSidebar }} - - {{ else }} - {{ end }}
@@ -41,4 +33,4 @@ {{ define "js" }} {{ partial "post/scripts.html" . }} {{ partial "post/ai-widget.html" . }} -{{ end }} \ No newline at end of file +{{ end }} From db760ed91062d3905417ad45914eed94010a9848 Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 16:00:36 +0300 Subject: [PATCH 09/72] refactor: unify listing pages on CSS grid, drop redundant title blocks --- layouts/_default/term.html | 57 ++++++++++++++----------------------- layouts/_default/terms.html | 47 ++++++++++++------------------ 2 files changed, 40 insertions(+), 64 deletions(-) diff --git a/layouts/_default/term.html b/layouts/_default/term.html index 7147dc9..bebaead 100644 --- a/layouts/_default/term.html +++ b/layouts/_default/term.html @@ -1,55 +1,42 @@ -{{ define "title" }} -{{- .Title -}} -{{ end }} - -{{ define "main"}} +{{ define "main" }}
-

- {{- .Title -}} -

-

- {{- T "article" (len .Pages) -}} -

+

{{ .Title }}

+

{{ T "article" (len .Pages) }}

+
{{ $title := .Title }} - {{ range (index site.Taxonomies .Data.Plural).Alphabetical }} - {{ if eq .Page.Title $title }} - - {{ .Page.Title }} - - {{ else }} - - {{ .Page.Title }} - - {{ end }} + {{ if eq .Page.Title $title }} + {{ .Page.Title }} + {{ else }} + {{ .Page.Title }} + {{ end }} {{ end }}
+
+
{{ $paginator := .Paginate .Pages }} -
- {{ range $paginator.Pages }} -
- {{ .Render "summary" }} -
- {{ end }} -
+ {{- /* Была Masonry-раскладка (masonry.pkgd + imagesloaded, ~30 КБ на КАЖДОЙ + странице сайта ради одного этого шаблона). Теперь обычный CSS grid — + такой же, как в list.html и terms.html. */ -}} +
+ {{ range $paginator.Pages }} + {{ .Render "summary" }} + {{ end }} +
{{ if gt $paginator.TotalPages 1 }} -
- {{ partial "ui/paginator.html" (dict "paginator" $paginator) }} -
+
+ {{ partial "ui/paginator.html" (dict "paginator" $paginator) }} +
{{ end }}
{{ end }} - -{{ define "js" }} - -{{ end }} diff --git a/layouts/_default/terms.html b/layouts/_default/terms.html index 32a8acd..b15ab0e 100644 --- a/layouts/_default/terms.html +++ b/layouts/_default/terms.html @@ -1,55 +1,44 @@ -{{ define "title" }} -{{ $title := (strings.FirstUpper .Data.Plural) }} -{{ $translation := T (print "all" $title) }} -{{ default $title $translation -}} -{{ end }} - -{{ define "main"}} +{{ define "main" }}
- +

{{- $title := T (print "all" (strings.FirstUpper .Data.Plural)) }} {{- default (strings.FirstUpper .Data.Plural) $title -}}

- +

- {{- if eq (len .Data.Terms) 1 }} - {{- default (print "1 " .Data.Singular) (T .Data.Singular 1) }} - {{- else }} - {{- default (print (len .Data.Terms) " " .Data.Plural) (T .Data.Singular (len .Data.Terms)) }} - {{- end -}} + {{- $n := len .Data.Terms -}} + {{- default (print $n " " .Data.Plural) (T .Data.Singular $n) -}}

- +
- +
- +
{{ $paginator := .Paginate (where site.RegularPages (print ".Params." .Data.Plural) "ne" nil) }} -
+
{{ range $paginator.Pages }} -
- {{ .Render "summary" }} -
+ {{ .Render "summary" }} {{ end }}
{{ if gt $paginator.TotalPages 1 }} -
- {{ partial "ui/paginator.html" (dict "paginator" $paginator) }} -
+
+ {{ partial "ui/paginator.html" (dict "paginator" $paginator) }} +
{{ end }}
-{{ end }} \ No newline at end of file +{{ end }} From 699d365f35be169107eab34e939051649876428a Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 16:00:48 +0300 Subject: [PATCH 10/72] refactor: consolidate page scripts into site.js/post.js, drop Alpine.js and Masonry --- assets/js/code-copy.js | 39 ----- assets/js/grid.js | 18 -- assets/js/main.js | 56 ------ assets/js/post.js | 74 ++++++++ assets/js/site.js | 214 +++++++++++++++++++++++ assets/js/toc.js | 57 ------ layouts/_default/baseof.html | 2 +- layouts/partials/core/scripts.html | 39 ++--- layouts/partials/header/nav.html | 152 ++++++---------- layouts/partials/post/scripts.html | 26 ++- layouts/partials/post/sidebar-left.html | 29 +-- layouts/partials/post/sidebar-right.html | 22 ++- layouts/partials/ui/context-menu.html | 92 +--------- 13 files changed, 400 insertions(+), 420 deletions(-) delete mode 100644 assets/js/code-copy.js delete mode 100644 assets/js/grid.js delete mode 100644 assets/js/main.js create mode 100644 assets/js/post.js create mode 100644 assets/js/site.js delete mode 100644 assets/js/toc.js diff --git a/assets/js/code-copy.js b/assets/js/code-copy.js deleted file mode 100644 index 79af6ea..0000000 --- a/assets/js/code-copy.js +++ /dev/null @@ -1,39 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const highlightBlocks = document.querySelectorAll(".highlight"); - - highlightBlocks.forEach((block) => { - block.style.position = "relative"; - - const button = document.createElement("button"); - button.className = "copy-code-btn absolute top-3 right-3 p-2 rounded-lg bg-base-content/10 text-base-content/50 hover:bg-base-content/20 hover:text-base-content transition-all opacity-0 group-hover:opacity-100 backdrop-blur-sm"; - button.setAttribute("aria-label", "Копировать код"); - button.setAttribute("title", "Копировать"); - - const iconCopy = ``; - const iconCheck = ``; - - button.innerHTML = iconCopy; - - block.classList.add("group"); - block.appendChild(button); - - button.addEventListener("click", async () => { - const codeElement = block.querySelector("code"); - let textToCopy = codeElement.innerText; - - try { - await navigator.clipboard.writeText(textToCopy); - - button.innerHTML = iconCheck; - button.classList.add("bg-green-500/20", "text-green-500"); - - setTimeout(() => { - button.innerHTML = iconCopy; - button.classList.remove("bg-green-500/20", "text-green-500"); - }, 2000); - } catch (err) { - console.error("Ошибка копирования: ", err); - } - }); - }); -}); \ No newline at end of file diff --git a/assets/js/grid.js b/assets/js/grid.js deleted file mode 100644 index db921d1..0000000 --- a/assets/js/grid.js +++ /dev/null @@ -1,18 +0,0 @@ -function initGrid() { - document.querySelectorAll('.dream-grid').forEach((grid) => { - const msnry = new Masonry(grid, { - itemSelector: '.dream-column' - }); - - if (grid.classList.contains('dream-grid-about')) { - window.aboutMasonry = msnry; - } - - imagesLoaded(grid, () => { - grid.style.opacity = 1; - msnry.layout(); - }); - }); -} - -initGrid(); \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js deleted file mode 100644 index ae65be8..0000000 --- a/assets/js/main.js +++ /dev/null @@ -1,56 +0,0 @@ -document.addEventListener('alpine:init', () => { - Alpine.store('darkMode', { - init() { - const isDark = window.localStorage.getItem('hugo-theme-dream-is-dark'); - if (isDark) { - this.on = isDark; - } else { - this.mql.addEventListener('change', (e) => { - this.on = e.matches ? 'y' : 'n'; - }); - this.on = 'auto'; - } - setTimeout(() => this.setThemeForUtterances(), 6000); - }, - mql: window.matchMedia('(prefers-color-scheme: dark)'), - on: 'n', - - isDark() { - return this.on === 'auto' ? this.mql.matches : this.on === 'y'; - }, - - class() { - return this.isDark() ? 'dark' : 'light'; - }, - - theme() { - return this.isDark() ? window.darkTheme : window.lightTheme; - }, - - iconMap: { n: 'sunny', y: 'moon', auto: 'desktop' }, - - icon() { - return this.iconMap[this.on]; - }, - - toggle(status) { - this.on = status; - if (status === 'auto') { - window.localStorage.removeItem('hugo-theme-dream-is-dark'); - } else { - window.localStorage.setItem('hugo-theme-dream-is-dark', status); - } - this.setThemeForUtterances(); - }, - - setThemeForUtterances() { - const utterances = document.querySelector('iframe.utterances-frame'); - if (utterances) { - utterances.contentWindow.postMessage({ - type: 'set-theme', - theme: this.isDark() ? 'github-dark' : 'github-light' - }, 'https://utteranc.es'); - } - } - }); -}); \ No newline at end of file diff --git a/assets/js/post.js b/assets/js/post.js new file mode 100644 index 0000000..5abf3bb --- /dev/null +++ b/assets/js/post.js @@ -0,0 +1,74 @@ +/* + * Подсветка активного пункта в оглавлении. + * + * Было: Alpine-компонент tocHighlighter() + слушатель scroll с debounce 300 мс, + * который на каждый тик дёргал getBoundingClientRect() для всех заголовков. + * Стало: IntersectionObserver — ноль работы при прокрутке и мгновенный отклик. + */ +(function () { + 'use strict'; + + function init() { + var toc = document.getElementById('TableOfContents'); + if (!toc) return; + + var wrapper = toc.closest('[data-toc]'); + if (!wrapper || getComputedStyle(wrapper).display === 'none') return; + + var activeClasses = (wrapper.dataset.activeClass || '').split(/\s+/).filter(Boolean); + + var links = Array.prototype.slice.call(toc.querySelectorAll('a[href^="#"]')); + var pairs = []; + + links.forEach(function (link) { + var id = decodeURIComponent(link.getAttribute('href').slice(1)); + var heading = id && document.getElementById(id); + if (heading) pairs.push({ link: link, heading: heading }); + }); + if (!pairs.length) return; + + var current = null; + function setActive(index) { + if (index === current || !pairs[index]) return; + if (current !== null) { + activeClasses.forEach(function (c) { pairs[current].link.classList.remove(c); }); + } + activeClasses.forEach(function (c) { pairs[index].link.classList.add(c); }); + current = index; + } + + var visible = new Set(); + + var observer = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + var i = pairs.findIndex(function (p) { return p.heading === entry.target; }); + if (i < 0) return; + if (entry.isIntersecting) visible.add(i); else visible.delete(i); + }); + + if (visible.size) { + setActive(Math.min.apply(null, Array.from(visible))); + return; + } + + // Ни один заголовок не в «активной зоне» — берём последний, что уже проехали. + var last = 0; + for (var i = 0; i < pairs.length; i++) { + if (pairs[i].heading.getBoundingClientRect().top < 100) last = i; + } + setActive(last); + }, { + // Активная зона: полоса от 100 px сверху до 30 % высоты экрана. + rootMargin: '-100px 0px -70% 0px', + threshold: 0 + }); + + pairs.forEach(function (p) { observer.observe(p.heading); }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init, { once: true }); + } else { + init(); + } +})(); diff --git a/assets/js/site.js b/assets/js/site.js new file mode 100644 index 0000000..1547466 --- /dev/null +++ b/assets/js/site.js @@ -0,0 +1,214 @@ +/* + * Общий скрипт сайта: липкая шапка, счётчик звёзд, копирование кода, + * кастомное контекстное меню. + * + * Раньше это были: main.js (стор тёмной темы — удалён, темой рулит ОС), + * grid.js + masonry + imagesLoaded (удалены, сетки на CSS grid), + * code-copy.js и два инлайновых - - - - -{{ $gridJs := resources.Get "js/grid.js" }} -{{ if hugo.IsProduction }} - {{ $gridJs = $gridJs | minify }} -{{ end }} - - -{{ $mainJs := resources.Get "js/main.js" }} -{{ if hugo.IsProduction }} - {{ $mainJs = $mainJs | minify }} -{{ end }} - - -{{ $codeCopyJs := resources.Get "js/code-copy.js" }} -{{ if hugo.IsProduction }} - {{ $codeCopyJs = $codeCopyJs | minify }} -{{ end }} - \ No newline at end of file +{{- /* Один общий бандл вместо трёх отдельных файлов + двух CDN-библиотек. + Masonry и imagesLoaded (~30 КБ) убраны: сетки теперь на CSS grid. + main.js (стор тёмной темы) убран целиком: темой рулит ОС через daisyUI. */}} +{{ $site := resources.Get "js/site.js" }} +{{ if hugo.IsProduction }}{{ $site = $site | minify | fingerprint }}{{ end }} + + +{{- /* Alpine нужен только шорткоду tabs. Если однажды добавите + в shortcodes/tabs.html строку {{ .Page.Store.Set "hasAlpine" true }}, + здесь достаточно будет обернуть скрипт в {{ if .Store.Get "hasAlpine" }}. */}} + + +{{- /* ionicons остаётся: его использует половина шорткодов. */}} + diff --git a/layouts/partials/header/nav.html b/layouts/partials/header/nav.html index a792427..ca72839 100644 --- a/layouts/partials/header/nav.html +++ b/layouts/partials/header/nav.html @@ -1,109 +1,67 @@ -{{ if site.Params.stickyNav }} -
+ diff --git a/layouts/partials/post/scripts.html b/layouts/partials/post/scripts.html index f35f73a..267cd25 100644 --- a/layouts/partials/post/scripts.html +++ b/layouts/partials/post/scripts.html @@ -1,17 +1,13 @@ -{{ $tocJs := resources.Get "js/toc.js" }} -{{ if hugo.IsProduction }}{{ $tocJs = $tocJs | minify }}{{ end }} - +{{ $post := resources.Get "js/post.js" }} +{{ if hugo.IsProduction }}{{ $post = $post | minify | fingerprint }}{{ end }} + {{ if site.Params.imageZoomableInPost }} - {{ if fileExists "layouts/partials/ui/medium-zoom.html" }} - {{ partialCached "ui/medium-zoom.html" . }} - {{ else }} - - {{ end }} -{{ end }} \ No newline at end of file + +{{ end }} diff --git a/layouts/partials/post/sidebar-left.html b/layouts/partials/post/sidebar-left.html index 7f46d49..f9451ef 100644 --- a/layouts/partials/post/sidebar-left.html +++ b/layouts/partials/post/sidebar-left.html @@ -1,27 +1,28 @@ -
+{{- /* Активный пункт теперь считается на сборке, а не Alpine'ом в рантайме. + Порядок — общий для всего сайта (partials/utils/section-pages.html). */ -}} +{{ $pages := partialCached "utils/section-pages.html" . .CurrentSection.RelPermalink }} +{{ $current := .RelPermalink }} + +
- + -
\ No newline at end of file +
diff --git a/layouts/partials/post/sidebar-right.html b/layouts/partials/post/sidebar-right.html index 966bb0e..31b1fcb 100644 --- a/layouts/partials/post/sidebar-right.html +++ b/layouts/partials/post/sidebar-right.html @@ -1,12 +1,10 @@ -
- {{ if site.Params.showTableOfContents }} -
Содержание
-
- {{ .TableOfContents }} -
- {{ end }} -
\ No newline at end of file +{{ if site.Params.showTableOfContents }} +
+
Содержание
+
+ {{ .TableOfContents }} +
+
+{{ end }} diff --git a/layouts/partials/ui/context-menu.html b/layouts/partials/ui/context-menu.html index 6686759..c912bc3 100644 --- a/layouts/partials/ui/context-menu.html +++ b/layouts/partials/ui/context-menu.html @@ -1,22 +1,23 @@ +{{- /* Логика переехала в js/site.js; инлайновых onclick больше нет. */ -}} - - \ No newline at end of file From d7456c47fb19360488b796ce537b4bc155e7c415 Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 16:00:52 +0300 Subject: [PATCH 11/72] fix: unify lecture ordering across list/sidebar/prev-next/numbering, extract shared RU pluralization helper --- layouts/_default/list.html | 41 ++++------ layouts/_default/summary.html | 59 ++++---------- layouts/partials/home/semester-view.html | 96 ++++++++++------------- layouts/partials/post/prev-next.html | 43 +++++----- layouts/partials/ui/section-card.html | 43 ++++------ layouts/partials/utils/plural.html | 19 +++++ layouts/partials/utils/section-pages.html | 20 +++++ 7 files changed, 149 insertions(+), 172 deletions(-) create mode 100644 layouts/partials/utils/plural.html create mode 100644 layouts/partials/utils/section-pages.html diff --git a/layouts/_default/list.html b/layouts/_default/list.html index 2b3fbe9..13859b3 100644 --- a/layouts/_default/list.html +++ b/layouts/_default/list.html @@ -1,45 +1,33 @@ {{ define "main" }} +{{- $pages := partialCached "utils/section-pages.html" . .CurrentSection.RelPermalink -}} +{{- $count := len $pages -}} +{{- $word := partial "utils/plural.html" (dict "count" $count "forms" (slice "лекция" "лекции" "лекций")) -}} +
- {{ $validPages := slice }} - {{ range .Pages }} - {{ if and (not .Draft) (not (strings.Contains .File.LogicalName "_index")) }} - {{ $validPages = $validPages | append . }} - {{ end }} - {{ end }} - - {{ $count := len $validPages }} - {{ $mod10 := mod $count 10 }} - {{ $mod100 := mod $count 100 }} - {{ $word := "лекций" }} - {{ if and (ge $mod100 11) (le $mod100 14) }}{{ $word = "лекций" }} - {{ else if eq $mod10 1 }}{{ $word = "лекция" }} - {{ else if and (ge $mod10 2) (le $mod10 4) }}{{ $word = "лекции" }} - {{ end }} -
- +
- +

{{ .Title }}

- + {{ with .Params.description }}
@@ -61,23 +49,22 @@

-
- {{ .Content }} + {{ . }}

{{ end }} {{ if gt $count 0 }}
- {{ range $validPages.ByWeight.Reverse }} + {{ range $pages }} {{ .Render "summary" }} {{ end }}
@@ -89,4 +76,4 @@

- - \ No newline at end of file + diff --git a/layouts/partials/home/semester-view.html b/layouts/partials/home/semester-view.html index 6f76546..2850ca4 100644 --- a/layouts/partials/home/semester-view.html +++ b/layouts/partials/home/semester-view.html @@ -1,30 +1,25 @@
- {{ $pinnedPages := where site.RegularPages "Params.pinned" true }} - {{ if gt (len $pinnedPages) 0 }} + {{- /* ---------- Закреплённое ---------- */ -}} + {{ with where site.RegularPages "Params.pinned" true }}

Закреплено

- {{ range $pinnedPages }} + {{ range . }} - - {{ if .Params.icon}} -
- -
+ {{ with .Params.icon }} +
+ +
{{ end }}
- - {{ .Title }} - - {{ if .Params.pinned_text }} - - {{ .Params.pinned_text }} - + {{ .Title }} + {{ with .Params.pinned_text }} + {{ . }} {{ end }}
@@ -33,59 +28,48 @@

- {{ if gt $semNum 0 }}{{ $semNum }} Семестр {{ else }}Прочие материалы{{ end }} + {{ if gt $semNum 0 }}{{ $semNum }} Семестр{{ else }}Прочие материалы{{ end }}

- - {{ $count := len $semSubjects }} - {{ $mod10 := mod $count 10 }} - {{ $mod100 := mod $count 100 }} - {{ $word := "предметов" }} - {{ if and (ge $mod100 11) (le $mod100 14) }}{{ $word = "предметов" }} - {{ else if eq $mod10 1 }}{{ $word = "предмет" }} - {{ else if and (ge $mod10 2) (le $mod10 4) }}{{ $word = "предмета" }} - {{ end }} {{ $count }} {{ $word }}
- +
- {{ range $semSubjects }} - {{ $publishedPages := where .Pages "Draft" false }} - {{ if gt (len $publishedPages) 0 }} -
- {{ partial "ui/section-card.html" . }} -
- {{ end }} + {{ range $subjects }} +
+ {{ partial "ui/section-card.html" . }} +
{{ end }}
@@ -96,4 +80,4 @@

Предметов пока

Скоро здесь появятся конспекты.

{{ end }} -
\ No newline at end of file +

diff --git a/layouts/partials/post/prev-next.html b/layouts/partials/post/prev-next.html index 9d6f9d1..af38828 100644 --- a/layouts/partials/post/prev-next.html +++ b/layouts/partials/post/prev-next.html @@ -1,44 +1,47 @@ -{{ if not .Params.hidePrevNext }} +{{- if not .Params.hidePrevNext -}} +{{- /* Было .PrevInSection / .NextInSection — они идут по внутреннему порядку Hugo + (по умолчанию дата по убыванию), из-за чего «Предыдущая» указывала на + следующую лекцию. Теперь prev/next берутся из того же общего порядка, + что и список курса с сайдбаром. */ -}} +{{- $siblings := partialCached "utils/section-pages.html" . .CurrentSection.RelPermalink -}} +{{- $i := -1 -}} +{{- range $k, $p := $siblings -}} + {{- if eq $p.RelPermalink $.RelPermalink }}{{ $i = $k }}{{ end -}} +{{- end -}} +{{- $prev := "" -}} +{{- $next := "" -}} +{{- if ge $i 0 -}} + {{- if gt $i 0 }}{{ $prev = index $siblings (sub $i 1) }}{{ end -}} + {{- if lt $i (sub (len $siblings) 1) }}{{ $next = index $siblings (add $i 1) }}{{ end -}} +{{- end -}} +
- - {{ with .PrevInSection }} + + {{ with $prev }} - Предыдущая - - - {{ .Title }} - - -
+ {{ .Title }}
{{ else }}
{{ end }} - - {{ with .NextInSection }} + {{ with $next }} - Следующая - - - {{ .Title }} - - -
+ {{ .Title }}
{{ else }}
{{ end }}
-{{ end }} \ No newline at end of file +{{- end -}} diff --git a/layouts/partials/ui/section-card.html b/layouts/partials/ui/section-card.html index 86b93dc..edbf047 100644 --- a/layouts/partials/ui/section-card.html +++ b/layouts/partials/ui/section-card.html @@ -1,9 +1,8 @@ {{ $abbr := .Params.abbr }} {{ if not $abbr }} {{ $clean := replaceRE `[^A-Za-zА-Яа-яЁё ]+` "" .Title }} - {{ $words := split $clean " " }} {{ $tmp := "" }} - {{ range first 4 $words }} + {{ range first 4 (split $clean " ") }} {{ if and . (lt (len $tmp) 4) }} {{ $tmp = printf "%s%s" $tmp (substr . 0 1) }} {{ end }} @@ -12,7 +11,6 @@ {{ if not $abbr }}{{ $abbr = "?" }}{{ end }} {{ end }} -{{ $accent := string (.Params.accent | default 1) }} {{ $colors := dict "1" "text-blue-500 bg-blue-500/10 border-blue-500 hover:shadow-blue-500/20" "2" "text-emerald-500 bg-emerald-500/10 border-emerald-500 hover:shadow-emerald-500/20" @@ -21,32 +19,26 @@ "5" "text-amber-500 bg-amber-500/10 border-amber-500 hover:shadow-amber-500/20" "6" "text-cyan-500 bg-cyan-500/10 border-cyan-500 hover:shadow-cyan-500/20" }} -{{ $accentClass := index $colors $accent | default (index $colors "1") }} +{{ $accentClass := index $colors (string (.Params.accent | default 1)) | default (index $colors "1") }} -{{ $count := len (where .Pages "File.LogicalName" "ne" "_index.md") }} -{{ $mod10 := mod $count 10 }} -{{ $mod100 := mod $count 100 }} -{{ $word := "лекций" }} -{{ if and (ge $mod100 11) (le $mod100 14) }} - {{ $word = "лекций" }} -{{ else if eq $mod10 1 }} - {{ $word = "лекция" }} -{{ else if and (ge $mod10 2) (le $mod10 4) }} - {{ $word = "лекции" }} -{{ end }} +{{- /* Было: where .Pages "File.LogicalName" "ne" "_index.md" — .Pages и так не + содержит собственный _index, а вложенные лекции наоборот терялись. + .RegularPages считает то же, что показывает list.html. */ -}} +{{ $count := len .RegularPages }} +{{ $word := partial "utils/plural.html" (dict "count" $count "forms" (slice "лекция" "лекции" "лекций")) }} - +
- +
- +
{{ $abbr }}
- + {{ with .Params.tag }} {{ . }} @@ -59,20 +51,17 @@

- {{ . }} -

+

{{ . }}

{{ end }}

- +
- {{ $hasAuthor := or .Params.author }} - {{ if $hasAuthor }} + {{ with .Params.author }} - {{ .Params.author }} + {{ . }} {{ end }}
@@ -81,4 +70,4 @@

Date: Wed, 5 Aug 2026 16:00:57 +0300 Subject: [PATCH 12/72] perf: preconnect and non-blocking font loading for logo --- layouts/partials/core/head.html | 62 ++++++++++++++++------------ layouts/partials/header/logo.html | 68 +++---------------------------- 2 files changed, 41 insertions(+), 89 deletions(-) diff --git a/layouts/partials/core/head.html b/layouts/partials/core/head.html index c3ed86f..6af635b 100644 --- a/layouts/partials/core/head.html +++ b/layouts/partials/core/head.html @@ -1,5 +1,14 @@ {{ partial "core/og.html" . }} +{{- /* Ранние коннекты к CDN — экономят ~100-200 мс на первом рендере. */}} + + + + +{{- /* Шрифт логотипа. Раньше он тянулся через @import внутри diff --git a/layouts/partials/header/logo.html b/layouts/partials/header/logo.html index f455b6d..d6f7b3b 100644 --- a/layouts/partials/header/logo.html +++ b/layouts/partials/header/logo.html @@ -1,70 +1,14 @@ - - +{{- /* Стили логотипа переехали в input.css (@layer components → .nav-logo-*), + а шрифт Inter грузится ссылкой из . Раньше @import url(...) сидел + внутри \ No newline at end of file diff --git a/layouts/partials/home/search-script.html b/layouts/partials/home/search-script.html deleted file mode 100644 index 4a8325e..0000000 --- a/layouts/partials/home/search-script.html +++ /dev/null @@ -1,158 +0,0 @@ - \ No newline at end of file From e1978b5fb11357738d3002996b79684a02f8ccbb Mon Sep 17 00:00:00 2001 From: notakeith Date: Wed, 5 Aug 2026 16:01:07 +0300 Subject: [PATCH 14/72] style: extract logo, copy-button and search-result CSS into input.css --- src/input.css | 112 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 9 deletions(-) diff --git a/src/input.css b/src/input.css index b91fdeb..911452c 100644 --- a/src/input.css +++ b/src/input.css @@ -1,14 +1,20 @@ @import 'tailwindcss' source(none); @source "../layouts"; +/* JS теперь тоже содержит классы (сообщения поиска, состояния кнопок) — сканируем и его. */ +@source "./js"; @plugin '@tailwindcss/typography'; @plugin "daisyui" { themes: emerald --default, forest --prefersdark; } +/* Тёмная тема целиком на ОС: и утилиты dark:*, и сама daisyUI-тема forest + переключаются через prefers-color-scheme. JS в этом не участвует. */ @custom-variant dark (@media (prefers-color-scheme: dark)); +/* В Tailwind v4 legacy-конфиг обычно уже не нужен — проверьте, что в + tailwind.config.js осталось что-то кроме content/darkMode, иначе строку можно убрать. */ @config '../tailwind.config.js'; @import "./css/syntax.css"; @@ -29,6 +35,70 @@ @apply menu menu-sm; } +/* ---------------------------------------------------------------- логотип */ +/* Переехало из partials/header/logo.html: раньше From 3b864749b6596bd8eee04dba23b8e3c00ae9dde7 Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 14:20:46 +0300 Subject: [PATCH 52/72] refactor: adapt list.html to use subject groups and teacher-switch --- layouts/_default/list.html | 55 +++++++++++++++----------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/layouts/_default/list.html b/layouts/_default/list.html index dd5fee7..751325a 100644 --- a/layouts/_default/list.html +++ b/layouts/_default/list.html @@ -4,12 +4,11 @@ {{- $word := partial "utils/plural.html" (dict "count" $count "forms" (slice "лекция" "лекции" "лекций")) -}} {{- $accent := partial "utils/accent.html" . -}} {{- $slug := path.Base (strings.TrimSuffix "/" .RelPermalink) -}} -{{- $subject := string (.Params.subject | default $slug) -}} +{{- $g := partial "utils/subject-group.html" . -}} +{{- $multi := gt (len $g.variants) 1 -}}
- -
+ data-cn-course data-subject="{{ $g.key }}" data-variant="{{ $slug }}">
{{ end }} - {{ $teachers := .Params.teachers }} - {{ if not $teachers }}{{ with .Params.teacher }}{{ $teachers = slice . }}{{ end }}{{ end }} - {{ with $teachers }} + {{ if $multi }}
- {{ if eq (len .) 1 }} -
- - {{ index . 0 }} -
- {{ else }} -
- Лектор - - - - {{ range . }} - {{ $surname := index (split . " ") 0 }} - - {{ end }} -
- {{ end }} + {{ partial "ui/teacher-switch.html" (dict "group" $g "current" .) }}
+ {{ else }} + {{ $teachers := .Params.teachers }} + {{ if not $teachers }}{{ with .Params.teacher }}{{ $teachers = slice . }}{{ end }}{{ end }} + {{ with $teachers }} +
+ +
+ {{ range . }}{{ . }}{{ end }} +
+
+ {{ end }} {{ end }} {{ with .Params.description }} @@ -77,13 +67,14 @@

-
+
- Всего - {{ $count }} {{ $word }} + + {{ if $multi }}У этого лектора{{ else }}Всего{{ end }} + + {{ $count }} {{ $word }}
@@ -106,10 +97,6 @@

- - {{ else }}
From fbf2033693c8bdf5adba635765efcb901dff91b7 Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 14:20:50 +0300 Subject: [PATCH 53/72] refactor: rebuild index.json with subject grouping --- layouts/index.json | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/layouts/index.json b/layouts/index.json index 2eb57b5..b277517 100644 --- a/layouts/index.json +++ b/layouts/index.json @@ -1,17 +1,23 @@ {{- $index := slice -}} -{{- with site.GetPage "section" "posts" -}} - {{- range .Sections -}} - {{- if and (not .Draft) (gt (len .RegularPages) 0) -}} - {{- $index = $index | append (dict - "id" .RelPermalink - "title" .Title - "content" (replaceRE `\s+` " " (or .Plain .Description) | chomp) - "tags" (.Params.tags | default slice) - "html" (partial "ui/section-card.html" .) - ) -}} - {{- end -}} +{{- /* Предметы: одна запись на группу, а не на каждую папку-вариант. + В content попадают ещё и фамилии преподавателей — чтобы поиск + по «Соколова» находил предмет. */ -}} +{{- range partialCached "utils/subject-groups.html" . "cn-all-groups" -}} + {{- $g := . -}} + {{- $text := slice (delimit $g.teachers " ") -}} + {{- range $g.variants -}} + {{- $text = $text | append (delimit .authors " ") -}} + {{- $text = $text | append (replaceRE `\s+` " " (or .page.Plain (string .page.Description)) | chomp) -}} {{- end -}} + + {{- $index = $index | append (dict + "id" $g.primary.RelPermalink + "title" $g.title + "content" (chomp (replaceRE `\s+` " " (delimit $text " "))) + "tags" ($g.primary.Params.tags | default slice) + "html" (partial "ui/section-card.html" $g) + ) -}} {{- end -}} {{- range where (where site.RegularPages "Type" "posts") "Draft" false -}} @@ -24,4 +30,4 @@ ) -}} {{- end -}} -{{- $index | jsonify -}} +{{- $index | jsonify -}} \ No newline at end of file From 5a0fce83c905d5a6410d0cffcabecef33ad8ad44 Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 14:21:01 +0300 Subject: [PATCH 54/72] refactor: update semester-view and author to use subject groups --- layouts/partials/home/semester-view.html | 14 +++++--------- layouts/partials/post/author.html | 24 +----------------------- 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/layouts/partials/home/semester-view.html b/layouts/partials/home/semester-view.html index fa479d4..b9be934 100644 --- a/layouts/partials/home/semester-view.html +++ b/layouts/partials/home/semester-view.html @@ -28,14 +28,10 @@

Предметов пока

Скоро здесь появятся конспекты.

{{ end }} -

+

\ No newline at end of file diff --git a/layouts/partials/post/author.html b/layouts/partials/post/author.html index 3df7742..ed38e2c 100644 --- a/layouts/partials/post/author.html +++ b/layouts/partials/post/author.html @@ -1,26 +1,4 @@ -{{- $raw := slice -}} -{{- if .Params.authors -}} - {{- $raw = .Params.authors -}} -{{- else if .Params.author -}} - {{- $s := replaceRE `\s+&\s+|\s+и\s+|\s*,\s*` "," (string .Params.author) -}} - {{- range split $s "," -}} - {{- with trim . " " }}{{ $raw = $raw | append (dict "name" .) }}{{ end -}} - {{- end -}} -{{- end -}} - -{{- $authors := slice -}} -{{- range $raw -}} - {{- $entry := . -}} - {{- if not (reflect.IsMap $entry) }}{{ $entry = dict "name" (trim (string $entry) " ") }}{{ end -}} - {{- $name := $entry.name -}} - {{- $handle := $entry.github | default "" -}} - {{- if and (not $handle) (findRE `^[A-Za-z0-9][A-Za-z0-9-]{0,38}$` $name) }}{{ $handle = $name }}{{ end -}} - {{- $avatar := $entry.avatar | default "" -}} - {{- if and (not $avatar) $handle }}{{ $avatar = printf "https://github.com/%s.png?size=64" $handle }}{{ end -}} - {{- $link := $entry.link | default "" -}} - {{- if and (not $link) $handle }}{{ $link = printf "https://github.com/%s" $handle }}{{ end -}} - {{- if $name }}{{ $authors = $authors | append (dict "name" $name "avatar" $avatar "link" $link) }}{{ end -}} -{{- end -}} +{{- $authors := partial "utils/authors.html" .Params -}} {{- $n := len $authors -}} {{- if gt $n 0 -}} From c881c188f363494ad852c65d06dd775e4be9fcc7 Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 14:21:06 +0300 Subject: [PATCH 55/72] style: adjust note-badge colors for consistency --- layouts/partials/ui/note-badge.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layouts/partials/ui/note-badge.html b/layouts/partials/ui/note-badge.html index ba9944f..f0a627f 100644 --- a/layouts/partials/ui/note-badge.html +++ b/layouts/partials/ui/note-badge.html @@ -15,7 +15,7 @@ {{- if eq $type "ai" -}} {{- $label = "Нейроконспект" -}} {{- $icon = "sparkles-outline" -}} - {{- $style = "background: var(--tint-4); color: var(--ink-4);" -}} + {{- $style = "background: var(--tint-2); color: var(--ink-2);" -}} {{- $tip = "Нейроконспект — сгенерирован моделью ИИ." -}} {{- with $model }}{{ $tip = printf "%s Модель: %s." $tip . }}{{ end -}} {{- with $source }}{{ $tip = printf "%s Источник: %s." $tip . }}{{ end -}} @@ -23,7 +23,7 @@ {{- else if or (eq $type "ai-pro") (eq $type "aipro") -}} {{- $label = "Нейроконспект" -}} {{- $icon = "sparkles" -}} - {{- $style = "background: var(--tint-2); color: var(--ink-2);" -}} + {{- $style = "background: var(--tint-5); color: var(--ink-5);" -}} {{- $tip = "Нейроконспект моделью повышенной точности." -}} {{- with $model }}{{ $tip = printf "%s Модель: %s." $tip . }}{{ end -}} {{- with $source }}{{ $tip = printf "%s Источник: %s." $tip . }}{{ end -}} @@ -31,7 +31,7 @@ {{- else -}} {{- $label = "Авторский" -}} {{- $icon = "create-outline" -}} - {{- $style = "" -}} + {{- $style = "background: var(--tint-1); color: var(--ink-1);" -}} {{- if $model -}} {{- $tip = printf "Авторский конспект. ИИ помогал только с оформлением и структурой. Модель: %s." $model -}} {{- else -}} From b9f5dea3d9b8169a4b7a78175177c7bb4744340f Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 14:29:59 +0300 Subject: [PATCH 56/72] fix: add missing site.js for teacher-switch --- assets/js/site.js | 114 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/assets/js/site.js b/assets/js/site.js index 3ab61e6..6988ece 100644 --- a/assets/js/site.js +++ b/assets/js/site.js @@ -305,3 +305,117 @@ if (e.key === 'Escape') hide(); }); })(); + + +/* + * Выбор варианта конспекта: преподаватель + автор. + * + * Модель: одна папка в content/posts — один вариант; варианты одного + * предмета связаны ключом `subject` во front matter. + * + * Что делает этот модуль: + * 1. запоминает, какой вариант человек открывал последним (localStorage); + * 2. на главной перевешивает ссылку заголовка карточки на этот вариант + * и подсвечивает соответствующую строку; + * 3. открывает шторку выбора внутри курса. + * + * Всё это — прогрессивное улучшение: без JS ссылки ведут на основной + * вариант, а шторка не открывается (кнопка просто ничего не делает). + */ +(function () { + 'use strict'; + + var PREFIX = 'cn:teacher:'; + + function remember(subject, variant) { + if (!subject || !variant) return; + try { localStorage.setItem(PREFIX + subject, variant); } catch (e) { } + } + + function recall(subject) { + try { return localStorage.getItem(PREFIX + subject); } catch (e) { return null; } + } + + // Мы внутри курса — значит этот вариант и есть «последний открытый». + function trackCurrent() { + var root = document.querySelector('[data-cn-course]'); + if (!root) return; + remember(root.dataset.subject, root.dataset.variant); + } + + function escapeAttr(value) { + if (window.CSS && CSS.escape) return CSS.escape(value); + return value.replace(/["\\]/g, '\\$&'); + } + + function applyToCards(scope) { + var root = scope || document; + root.querySelectorAll('[data-subject-card]').forEach(function (card) { + var saved = recall(card.dataset.subject); + if (!saved) return; + + var row = card.querySelector('[data-variant="' + escapeAttr(saved) + '"]'); + if (!row) return; + + var url = row.dataset.url || row.getAttribute('href'); + var link = card.querySelector('[data-subject-link]'); + if (link && url) link.setAttribute('href', url); + + card.querySelectorAll('[data-remembered]').forEach(function (el) { + el.removeAttribute('data-remembered'); + }); + row.dataset.remembered = '1'; + }); + } + + // Нужно поиску: карточки в выдаче вставляются через innerHTML уже после init. + window.cnApplySubjectCards = applyToCards; + + function initSheet() { + var sheet = document.getElementById('cn-teacher-sheet'); + if (!sheet) return; + + document.addEventListener('click', function (e) { + var opener = e.target.closest('[data-teacher-open]'); + if (opener) { + e.preventDefault(); + if (typeof sheet.showModal === 'function') sheet.showModal(); + else sheet.setAttribute('open', ''); + return; + } + + var item = e.target.closest('.cn-sheet__item'); + if (item && sheet.contains(item)) { + remember(sheet.dataset.subject, item.dataset.variant); + } + }); + + // Клик по затемнению (сам занимает весь экран) — закрыть. + sheet.addEventListener('click', function (e) { + if (e.target === sheet) sheet.close(); + }); + } + + // Клик по строке варианта в карточке на главной — тоже выбор. + function initCardRows() { + document.addEventListener('click', function (e) { + var row = e.target.closest('[data-subject-card] [data-variant]'); + if (!row) return; + var card = row.closest('[data-subject-card]'); + if (card) remember(card.dataset.subject, row.dataset.variant); + }); + } + + function init() { + trackCurrent(); + applyToCards(); + initCardRows(); + initSheet(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init, { once: true }); + } else { + init(); + } +})(); From 8d006d29bd5e4e6e674b0027d106178ba89ccb07 Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 14:30:22 +0300 Subject: [PATCH 57/72] fix: update search.js for new index.json structure --- assets/js/search.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/js/search.js b/assets/js/search.js index c85ed89..2b5400c 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -108,6 +108,8 @@ grid.replaceChildren(fragment); + if (window.cnApplySubjectCards) window.cnApplySubjectCards(grid); + countEl.textContent = found; resultsBar.classList.toggle('hidden', found === 0); noResults.classList.toggle('hidden', found > 0); @@ -194,4 +196,4 @@ } else { init(); } -})(); +})(); \ No newline at end of file From 0dfaa8a61081149baa10435ff88e8127cf6dd289 Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 14:31:05 +0300 Subject: [PATCH 58/72] style: add subject card and teacher-switch styles --- src/input.css | 104 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/src/input.css b/src/input.css index 5893893..1b4a014 100644 --- a/src/input.css +++ b/src/input.css @@ -559,22 +559,106 @@ a[data-cn-tip], button[data-cn-tip], [data-cn-tip] a { cursor: pointer; } flex items-center justify-center text-sm font-bold uppercase; } -.cn-teacher-list { @apply flex flex-col gap-1 min-w-0; } +/* ── Карточка предмета на главной ───────────────────────────────────── + Одна карточка = один предмет. Внутри — варианты «преподаватель + автор», + раскрываются нативным
. */ -.cn-teacher-list__row { - @apply flex items-center gap-1.5 text-sm text-base-content/70 leading-snug min-w-0; +.cn-subject { + @apply relative flex flex-col h-full bg-base-100 rounded-2xl border border-base-300 + shadow-sm overflow-hidden transition-shadow duration-300; } -.cn-teacher-list__row[data-remembered="1"] { - @apply font-semibold text-base-content; -} +.cn-subject:hover { @apply shadow-md; } -.cn-teacher-list__dot { - @apply w-1.5 h-1.5 rounded-full shrink-0 opacity-0; +.cn-subject__bar { + @apply absolute left-0 top-0 bottom-0 w-1.5 opacity-0 transition-opacity duration-300; background-color: var(--cn-ink); } -.cn-teacher-list__row[data-remembered="1"] .cn-teacher-list__dot { @apply opacity-100; } +.cn-subject:hover .cn-subject__bar { @apply opacity-100; } + +.cn-subject__body { @apply relative z-10 p-5 flex-1 flex flex-col; } + +.cn-subject__top { @apply flex justify-between items-start gap-3 mb-4; } + +.cn-subject__abbr { + @apply flex items-center justify-center w-10 h-10 rounded-xl shrink-0 + font-black text-xs tracking-widest; +} + +.cn-subject__teachers-count { + @apply text-sm font-bold text-base-content/70 whitespace-nowrap; +} + +.cn-subject__tag { + @apply px-2 py-1 text-[9px] font-bold uppercase tracking-wider + bg-base-200 text-base-content/50 rounded-md; +} + +.cn-subject__title { @apply text-lg md:text-xl font-bold leading-tight m-0 mb-2; } + +.cn-subject__title a { + @apply no-underline text-base-content line-clamp-2 transition-colors; +} + +.cn-subject__title a:hover { color: var(--cn-ink); } + +.cn-subject__desc { @apply text-sm text-base-content/60 leading-relaxed m-0 line-clamp-3; } + +.cn-subject__single { + @apply flex items-start gap-1.5 mt-3 text-sm font-medium text-base-content/70; +} + +.cn-subject__disc { @apply mt-4; } + +.cn-subject__summary { + @apply flex items-center justify-between gap-2 list-none cursor-pointer select-none + text-sm text-base-content/60 transition-colors; +} + +.cn-subject__summary::-webkit-details-marker { display: none; } +.cn-subject__summary:hover { @apply text-base-content; } + +.cn-subject__chev { @apply text-base opacity-50 transition-transform duration-200; } +.cn-subject__disc[open] .cn-subject__chev { transform: rotate(180deg); } + +.cn-subject__rows { @apply flex flex-col gap-2 mt-3; } + +.cn-subject__row { + @apply grid items-center gap-x-3 gap-y-1 p-3 rounded-xl no-underline + bg-base-200/50 text-base-content transition-colors; + grid-template-columns: minmax(0, 1fr) auto; +} + +.cn-subject__row:hover { @apply bg-base-200; } + +.cn-subject__row[data-remembered="1"] { + background-color: var(--cn-ink-08); + box-shadow: inset 0 0 0 1px var(--cn-ink-20); +} + +.cn-subject__row-name { @apply font-bold text-[15px] leading-snug truncate; } +.cn-subject__row-badge { @apply justify-self-end; } + +.cn-subject__row-author { + @apply flex items-center gap-1.5 min-w-0 text-sm text-base-content/50; +} + +.cn-subject__row-count { + @apply justify-self-end whitespace-nowrap text-sm text-base-content/50; +} + +.cn-subject__foot { + @apply relative z-10 px-5 py-3 flex items-center justify-end + border-t border-base-300/50 bg-base-200/30 + text-xs font-medium text-base-content/50; +} + +@media (prefers-reduced-motion: reduce) { + .cn-subject, + .cn-subject__bar, + .cn-subject__chev { transition: none; } +} .cn-contributors { @apply w-full mb-6; } @@ -626,4 +710,4 @@ a[data-cn-tip], button[data-cn-tip], [data-cn-tip] a { cursor: pointer; } .cn-authors-more > summary::-webkit-details-marker { display: none; } .cn-authors-more > summary .cn-less-label { display: none; } .cn-authors-more[open] > summary .cn-more-label { display: none; } -.cn-authors-more[open] > summary .cn-less-label { display: inline; } +.cn-authors-more[open] > summary .cn-less-label { display: inline; } \ No newline at end of file From 00d8786439aa3e7ec140bda6e131193544fd3c23 Mon Sep 17 00:00:00 2001 From: notakeith Date: Fri, 7 Aug 2026 15:58:00 +0300 Subject: [PATCH 59/72] feat: migrate to KaTeX from MathJax --- layouts/_default/_markup/render-codeblock-math.html | 5 +---- layouts/_default/_markup/render-passthrough.html | 2 ++ layouts/partials/core/head.html | 10 ++++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 layouts/_default/_markup/render-passthrough.html diff --git a/layouts/_default/_markup/render-codeblock-math.html b/layouts/_default/_markup/render-codeblock-math.html index 38ebb84..d251cf8 100644 --- a/layouts/_default/_markup/render-codeblock-math.html +++ b/layouts/_default/_markup/render-codeblock-math.html @@ -1,4 +1 @@ -

- {{- .Inner | safeHTML -}} -

-{{ .Page.Store.Set "hasMathjax" true }} +{{- transform.ToMath .Inner (dict "throwOnError" false "errorColor" "#c0392b" "displayMode" true) -}} diff --git a/layouts/_default/_markup/render-passthrough.html b/layouts/_default/_markup/render-passthrough.html new file mode 100644 index 0000000..7a7bf28 --- /dev/null +++ b/layouts/_default/_markup/render-passthrough.html @@ -0,0 +1,2 @@ +{{- $opts := dict "throwOnError" false "errorColor" "#c0392b" "displayMode" (eq .Type "block") -}} +{{- transform.ToMath .Inner $opts -}} diff --git a/layouts/partials/core/head.html b/layouts/partials/core/head.html index d192442..eaff5c4 100644 --- a/layouts/partials/core/head.html +++ b/layouts/partials/core/head.html @@ -48,6 +48,16 @@ {{ end }} +{{- $hasMath := false -}} +{{- with .RawContent -}} + {{- if or (findRE `\$|\\\(|\\\[` . 1) (in . "```math") -}} + {{- $hasMath = true -}} + {{- end -}} +{{- end -}} +{{ if $hasMath }} + +{{ end }} + {{ if or site.Params.backgroundImage site.Params.backgroundImageDark }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/layouts/partials/core/scripts.html b/layouts/partials/core/scripts.html index 945147d..e4cae49 100644 --- a/layouts/partials/core/scripts.html +++ b/layouts/partials/core/scripts.html @@ -1,7 +1,3 @@ {{ $site := resources.Get "js/site.js" }} {{ if hugo.IsProduction }}{{ $site = $site | minify | fingerprint }}{{ end }} - - - - - + \ No newline at end of file diff --git a/layouts/partials/home/semester-view.html b/layouts/partials/home/semester-view.html index b9be934..f64c8f4 100644 --- a/layouts/partials/home/semester-view.html +++ b/layouts/partials/home/semester-view.html @@ -11,7 +11,7 @@

{{ with .Params.icon }}
- + {{ partial "ui/icon.html" (dict "name" .) }}
{{ end }} diff --git a/layouts/partials/post/ai-widget.html b/layouts/partials/post/ai-widget.html index 348d5ed..f37f8f7 100644 --- a/layouts/partials/post/ai-widget.html +++ b/layouts/partials/post/ai-widget.html @@ -208,7 +208,7 @@ @@ -310,7 +310,7 @@ navigator.clipboard.writeText(prompt).then(function () { btn.innerHTML = ` - + {{ partial "ui/icon.html" (dict "name" "checkmark-circle") }} Скопировано!`; diff --git a/layouts/partials/post/header.html b/layouts/partials/post/header.html index 21a2ade..eb00307 100644 --- a/layouts/partials/post/header.html +++ b/layouts/partials/post/header.html @@ -2,7 +2,7 @@ {{ if not .Params.hideBreadcrumbs }}