fix: форматирование, опечатка в статье
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
|
@ -16,7 +16,7 @@ showComments: true
|
|||
└── dev ← development (dev.blog.ru)
|
||||
```
|
||||
|
||||
**Принцип:** Одна папка, две ветки. Переключение через `git checkout`, не через разные директории.
|
||||
>[!IMPORTANT] **Принцип:** Одна папка, две ветки. Переключение через `git checkout`, не через разные директории.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -395,4 +395,4 @@ git push origin main
|
|||
|
||||
---
|
||||
|
||||
**Помни:** Dev для экспериментов, main для проверенного контента. Всегда тестируй перед публикацией.
|
||||
>[!IMPORTANT] **Важно:** `Dev` для экспериментов, `main` для проверенного контента. Всегда тестируй перед публикацией.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ series_order: 6
|
|||
|
||||
Мораль была проста: **чисти за собой**. Сегодня делаем именно это - переносим Gitea из старого namespace в `oakazanin` и удаляем старый namespace навсегда.
|
||||
|
||||
Заодно разберём универсальный алгоритм миграции, который работает для любого сервиса. Показываю на примере Gitea, но всё описанное применимо к любому stateful сервису — PostgreSQL, MySQL, Nextcloud, MinIO. Принципы одинаковые: экспортируй манифесты, поменяй namespace, создай новое перед удалением старого.
|
||||
Заодно разберём универсальный алгоритм миграции, который работает для любого сервиса. Показываю на примере Gitea, но всё описанное применимо к любому stateful сервису - PostgreSQL, MySQL, Nextcloud, MinIO. Принципы одинаковые: экспортируй манифесты, поменяй namespace, создай новое перед удалением старого.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -311,16 +311,16 @@ curl -s -o /dev/null -w "%{http_code}" https://git.example.com # 200
|
|||
- Данные остаются нетронутыми
|
||||
|
||||
**Nextcloud:**
|
||||
- Аналогично — файлы на NFS не переносятся
|
||||
- Аналогично - файлы на NFS не переносятся
|
||||
- Только манифесты меняют namespace
|
||||
- Zero downtime если создаёшь новое до удаления старого
|
||||
|
||||
**MinIO (S3-хранилище):**
|
||||
- Stateful, работает через PV/PVC
|
||||
- Те же принципы — новый namespace, тот же NFS путь
|
||||
- Те же принципы - новый namespace, тот же NFS путь
|
||||
|
||||
**Stateless сервисы (nginx, API):**
|
||||
- Ещё проще — нет PV/PVC вообще
|
||||
- Ещё проще - нет PV/PVC вообще
|
||||
- Только Deployment + Service, меняешь namespace, готово
|
||||
|
||||
Главный принцип: **данные живут на PV, который привязан к NFS. Namespace меняется, путь на NFS остаётся.**
|
||||
|
|
|
|||
|
|
@ -898,10 +898,10 @@
|
|||
|
||||
|
||||
<div class="article-content max-w-prose mb-20">
|
||||
<p>Привет! Меня зовут Олег. За 20 лет в IT проблемы росли вместе со мной: от «почему не печатает принтер» до «почему три сервера не могут договориться, кто из них главный». Техподдержка научила терпению — там быстро понимаешь, что «не работает» может означать что угодно, от выключенного монитора до сбоя на другом континенте. Сети научили начинать диагностику с розетки, а не с теории заговора. Системное администрирование — что между «всё работает» и «всё сломалось» обычно стоит один непрочитанный warning в логах трёхнедельной давности.</p>
|
||||
<p>Сейчас я инфраструктурный инженер. Строю и чиню серверную инфраструктуру: много enterprise, ещё больше Open Source, изрядно импортозамещённого — в общем, всё что работает на Linux, с Linux и благодаря Linux. Работаю в Главгосэкспертизе России. Общаюсь с серверами на Bash, Python и прочих машинопонятных языках. Иногда серверы даже отвечают. Живу в Москве.</p>
|
||||
<p>Дома — лаборатория. Нет, не стойка в подвале (хотя идея заманчивая). Один сервер, но серьёзный: виртуализация, Kubernetes, хранилище, мониторинг — всё по-взрослому, но если что-то упало — нет многомиллионных издержек и мир не остановился. Идеальный полигон: придумал, реализовал, сломал, починил, задокументировал. Полный цикл.</p>
|
||||
<p>Этот блог — не enterprise-гайды для внедрения в банке. Это production, но в человеческом масштабе: домашний сервер, небольшая компания, стартап на трёх разработчиках. Решения, которые реально работают — просто без бюджета на команду SRE из десяти человек — инженеров надёжности, чьи зарплаты съедают бюджет стартапа за квартал. Документирую грабли, чтобы вы на них не наступали. Чем больнее наступил сам — тем подробнее статья.</p>
|
||||
<p>Привет! Меня зовут Олег. За 20 лет в IT проблемы росли вместе со мной: от «почему не печатает принтер» до «почему три сервера не могут договориться, кто из них главный». Техподдержка научила терпению - там быстро понимаешь, что «не работает» может означать что угодно, от выключенного монитора до сбоя на другом континенте. Сети научили начинать диагностику с розетки, а не с теории заговора. Системное администрирование - что между «всё работает» и «всё сломалось» обычно стоит один непрочитанный warning в логах трёхнедельной давности.</p>
|
||||
<p>Сейчас я инфраструктурный инженер. Строю и чиню серверную инфраструктуру: много enterprise, ещё больше Open Source, изрядно импортозамещённого - в общем, всё что работает на Linux, с Linux и благодаря Linux. Работаю в Главгосэкспертизе России. Общаюсь с серверами на Bash, Python и прочих машинопонятных языках. Иногда серверы даже отвечают. Живу в Москве.</p>
|
||||
<p>Дома - лаборатория. Нет, не стойка в подвале (хотя идея заманчивая). Один сервер, но серьёзный: виртуализация, Kubernetes, хранилище, мониторинг - всё по-взрослому, но если что-то упало - нет многомиллионных издержек и мир не остановился. Идеальный полигон: придумал, реализовал, сломал, починил, задокументировал. Полный цикл.</p>
|
||||
<p>Этот блог - не enterprise-гайды для внедрения в банке. Это production, но в человеческом масштабе: домашний сервер, небольшая компания, стартап на трёх разработчиках. Решения, которые реально работают - просто без бюджета на команду SRE из десяти человек - инженеров надёжности, чьи зарплаты съедают бюджет стартапа за квартал. Документирую грабли, чтобы вы на них не наступали. Чем больнее наступил сам - тем подробнее статья.</p>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1155,8 +1155,13 @@
|
|||
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~/projects/blog/ ← одна папка, две ветки
|
||||
</span></span><span class="line"><span class="cl"> ├── main ← production (blog.ru)
|
||||
</span></span><span class="line"><span class="cl"> └── dev ← development (dev.blog.ru)</span></span></code></pre></div></div>
|
||||
<p><strong>Принцип:</strong> Одна папка, две ветки. Переключение через <code>git checkout</code>, не через разные директории.</p>
|
||||
<hr>
|
||||
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="important">
|
||||
<div class="flex items-center gap-2 font-semibold text-inherit">
|
||||
<div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z"/></svg></span></div>
|
||||
<div class="grow">
|
||||
<strong>Принцип:</strong> Одна папка, две ветки. Переключение через <code>git checkout</code>, не через разные директории.
|
||||
</div>
|
||||
</div></div><hr>
|
||||
|
||||
<h2 class="relative group">Золотые правила
|
||||
<div id="золотые-правила" class="anchor"></div>
|
||||
|
|
@ -1618,8 +1623,13 @@
|
|||
</span></span><span class="line"><span class="cl">git merge dev --no-edit
|
||||
</span></span><span class="line"><span class="cl">git push origin main</span></span></code></pre></div></div>
|
||||
<hr>
|
||||
<p><strong>Помни:</strong> Dev для экспериментов, main для проверенного контента. Всегда тестируй перед публикацией.</p>
|
||||
|
||||
<div class="admonition relative overflow-hidden rounded-lg border-l-4 my-3 px-4 py-3 shadow-sm" data-type="important">
|
||||
<div class="flex items-center gap-2 font-semibold text-inherit">
|
||||
<div class="flex shrink-0 h-5 w-5 items-center justify-center text-lg"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z"/></svg></span></div>
|
||||
<div class="grow">
|
||||
<strong>Важно:</strong> <code>Dev</code> для экспериментов, <code>main</code> для проверенного контента. Всегда тестируй перед публикацией.
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
|
||||
|
||||
|
|
@ -3285,6 +3295,34 @@
|
|||
|
||||
|
||||
|
||||
<div class="pt-8">
|
||||
<hr class="border-dotted border-neutral-300 dark:border-neutral-600">
|
||||
<div class="flex justify-between pt-3">
|
||||
<span class="flex flex-col">
|
||||
|
||||
</span>
|
||||
<span class="flex flex-col items-end">
|
||||
|
||||
<a
|
||||
class="flex text-right text-neutral-700 hover:text-primary-600 dark:text-neutral dark:hover:text-primary-400"
|
||||
href="/cheatsheets/shortcodes/">
|
||||
<span class="leading-6">
|
||||
Shortcodes <span class="inline-block rtl:rotate-180">→</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<span class="me-6 mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
<time datetime="0001-01-01T00:00:00+00:00">1 января 0001</time>
|
||||
</span>
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="pt-3">
|
||||
<hr class="border-dotted border-neutral-300 dark:border-neutral-600">
|
||||
|
|
|
|||
|
|
@ -1021,6 +1021,285 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<article class="article-link--simple flex flex-col md:flex-row relative">
|
||||
|
||||
<div class="flex-none relative overflow-hidden thumbnail-shadow md:mr-7 thumbnail">
|
||||
<img
|
||||
src="/img/background_hu_7658a5e11b3ad5f2.png"
|
||||
role="presentation"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="not-prose absolute inset-0 w-full h-full object-cover">
|
||||
</div>
|
||||
|
||||
<div class=" mt-3 md:mt-0">
|
||||
<header class="items-center text-start text-xl font-semibold">
|
||||
<a
|
||||
|
||||
href="/cheatsheets/shortcodes/"
|
||||
|
||||
class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2">
|
||||
<h2>
|
||||
Shortcodes
|
||||
|
||||
</h2>
|
||||
</a>
|
||||
|
||||
<div class="ms-2"><span class="flex cursor-pointer">
|
||||
<span
|
||||
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
|
||||
Черновик
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</header>
|
||||
<div class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="flex flex-row flex-wrap items-center">
|
||||
|
||||
|
||||
<time datetime="0001-01-01T00:00:00+00:00">1 января 0001</time><span class="px-2 text-primary-500">·</span><span title="Время чтения">17 минут</span><span class="px-2 text-primary-500">·</span><span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<span
|
||||
id="views_cheatsheets/blowfish-shortcodes/index.md"
|
||||
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
|
||||
title="views"
|
||||
>loading</span
|
||||
>
|
||||
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
||||
<path fill="currentColor" d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></span>
|
||||
</span>
|
||||
<span class="px-2 text-primary-500">·</span><span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<span
|
||||
id="likes_cheatsheets/blowfish-shortcodes/index.md"
|
||||
class="animate-pulse inline-block text-transparent max-h-3 rounded-full -mt-[2px] align-middle bg-neutral-300 dark:bg-neutral-400"
|
||||
title="likes"
|
||||
>loading</span
|
||||
>
|
||||
<span class="inline-block align-text-bottom"><span class="relative block icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path fill="currentColor" d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg></span></span>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="flex flex-row flex-wrap items-center">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a class="relative mt-[0.5rem] me-2" href="/tags/shortcodes/">
|
||||
<span class="flex cursor-pointer">
|
||||
<span
|
||||
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
|
||||
Shortcodes
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
|
||||
<a class="relative mt-[0.5rem] me-2" href="/tags/mermaid/">
|
||||
<span class="flex cursor-pointer">
|
||||
<span
|
||||
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
|
||||
Mermaid
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
|
||||
<a class="relative mt-[0.5rem] me-2" href="/tags/icon/">
|
||||
<span class="flex cursor-pointer">
|
||||
<span
|
||||
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
|
||||
Icon
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
|
||||
<a class="relative mt-[0.5rem] me-2" href="/tags/lead/">
|
||||
<span class="flex cursor-pointer">
|
||||
<span
|
||||
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
|
||||
Lead
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
|
||||
<a class="relative mt-[0.5rem] me-2" href="/tags/docs/">
|
||||
<span class="flex cursor-pointer">
|
||||
<span
|
||||
class="rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400">
|
||||
Docs
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="px-6 pt-4 pb-2"></div>
|
||||
</article>
|
||||
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,16 @@
|
|||
<copyright>© 2026 Олег Казанин</copyright>
|
||||
<lastBuildDate>Mon, 23 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://192.168.11.190:1313/cheatsheets/index.xml" rel="self" type="application/rss+xml" />
|
||||
|
||||
<item>
|
||||
<title>Shortcodes</title>
|
||||
<link>http://192.168.11.190:1313/cheatsheets/shortcodes/</link>
|
||||
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
||||
<author>oakazanin@ya.ru (Олег Казанин)</author>
|
||||
<guid>http://192.168.11.190:1313/cheatsheets/shortcodes/</guid>
|
||||
<description></description>
|
||||
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Git Workflow для Hugo блога</title>
|
||||
<link>http://192.168.11.190:1313/cheatsheets/git-workflow/</link>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
/* Carousel Specific Styles */
|
||||
.ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
@media (width >= 640px) {
|
||||
.sm\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.sm\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.sm\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 853px) {
|
||||
.md\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.md\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.md\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 1024px) {
|
||||
.lg\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.lg\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.lg\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 1280px) {
|
||||
.xl\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.xl\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.xl\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 1536px) {
|
||||
.\32xl\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.\32xl\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.\32xl\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,595 @@
|
|||
/* Gallery Specific Styles */
|
||||
.grid-w10 {
|
||||
width: calc(10% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w15 {
|
||||
width: calc(15% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w20 {
|
||||
width: calc(20% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w25 {
|
||||
width: calc(25% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w30 {
|
||||
width: calc(30% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w33 {
|
||||
width: calc(33% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w35 {
|
||||
width: calc(35% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w40 {
|
||||
width: calc(40% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w45 {
|
||||
width: calc(45% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w50 {
|
||||
width: calc(50% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w55 {
|
||||
width: calc(55% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w60 {
|
||||
width: calc(60% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w65 {
|
||||
width: calc(65% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w66 {
|
||||
width: calc(66% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w70 {
|
||||
width: calc(70% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w75 {
|
||||
width: calc(75% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w80 {
|
||||
width: calc(80% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w85 {
|
||||
width: calc(85% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w90 {
|
||||
width: calc(90% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w95 {
|
||||
width: calc(95% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.grid-w100 {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
|
||||
.gallery figure {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gallery figcaption {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
color: rgb(var(--color-neutral-600));
|
||||
}
|
||||
|
||||
.dark .gallery figcaption {
|
||||
color: rgb(var(--color-neutral-400));
|
||||
}
|
||||
@media (width >= 640px) {
|
||||
.sm\:grid-w10 {
|
||||
width: calc(10% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w15 {
|
||||
width: calc(15% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w20 {
|
||||
width: calc(20% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w25 {
|
||||
width: calc(25% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w30 {
|
||||
width: calc(30% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w33 {
|
||||
width: calc(33% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w35 {
|
||||
width: calc(35% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w40 {
|
||||
width: calc(40% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w45 {
|
||||
width: calc(45% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w50 {
|
||||
width: calc(50% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w55 {
|
||||
width: calc(55% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w60 {
|
||||
width: calc(60% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w65 {
|
||||
width: calc(65% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w66 {
|
||||
width: calc(66% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w70 {
|
||||
width: calc(70% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w75 {
|
||||
width: calc(75% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w80 {
|
||||
width: calc(80% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w85 {
|
||||
width: calc(85% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w90 {
|
||||
width: calc(90% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w95 {
|
||||
width: calc(95% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.sm\:grid-w100 {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
}
|
||||
@media (width >= 853px) {
|
||||
.md\:grid-w10 {
|
||||
width: calc(10% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w15 {
|
||||
width: calc(15% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w20 {
|
||||
width: calc(20% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w25 {
|
||||
width: calc(25% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w30 {
|
||||
width: calc(30% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w33 {
|
||||
width: calc(33% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w35 {
|
||||
width: calc(35% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w40 {
|
||||
width: calc(40% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w45 {
|
||||
width: calc(45% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w50 {
|
||||
width: calc(50% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w55 {
|
||||
width: calc(55% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w60 {
|
||||
width: calc(60% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w65 {
|
||||
width: calc(65% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w66 {
|
||||
width: calc(66% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w70 {
|
||||
width: calc(70% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w75 {
|
||||
width: calc(75% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w80 {
|
||||
width: calc(80% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w85 {
|
||||
width: calc(85% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w90 {
|
||||
width: calc(90% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w95 {
|
||||
width: calc(95% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.md\:grid-w100 {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
}
|
||||
@media (width >= 1024px) {
|
||||
.lg\:grid-w10 {
|
||||
width: calc(10% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w15 {
|
||||
width: calc(15% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w20 {
|
||||
width: calc(20% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w25 {
|
||||
width: calc(25% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w30 {
|
||||
width: calc(30% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w33 {
|
||||
width: calc(33% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w35 {
|
||||
width: calc(35% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w40 {
|
||||
width: calc(40% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w45 {
|
||||
width: calc(45% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w50 {
|
||||
width: calc(50% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w55 {
|
||||
width: calc(55% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w60 {
|
||||
width: calc(60% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w65 {
|
||||
width: calc(65% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w66 {
|
||||
width: calc(66% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w70 {
|
||||
width: calc(70% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w75 {
|
||||
width: calc(75% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w80 {
|
||||
width: calc(80% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w85 {
|
||||
width: calc(85% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w90 {
|
||||
width: calc(90% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w95 {
|
||||
width: calc(95% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.lg\:grid-w100 {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
}
|
||||
@media (width >= 1280px) {
|
||||
.xl\:grid-w10 {
|
||||
width: calc(10% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w15 {
|
||||
width: calc(15% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w20 {
|
||||
width: calc(20% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w25 {
|
||||
width: calc(25% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w30 {
|
||||
width: calc(30% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w33 {
|
||||
width: calc(33% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w35 {
|
||||
width: calc(35% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w40 {
|
||||
width: calc(40% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w45 {
|
||||
width: calc(45% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w50 {
|
||||
width: calc(50% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w55 {
|
||||
width: calc(55% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w60 {
|
||||
width: calc(60% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w65 {
|
||||
width: calc(65% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w66 {
|
||||
width: calc(66% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w70 {
|
||||
width: calc(70% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w75 {
|
||||
width: calc(75% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w80 {
|
||||
width: calc(80% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w85 {
|
||||
width: calc(85% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w90 {
|
||||
width: calc(90% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w95 {
|
||||
width: calc(95% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.xl\:grid-w100 {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
}
|
||||
@media (width >= 1536px) {
|
||||
.\32xl\:grid-w10 {
|
||||
width: calc(10% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w15 {
|
||||
width: calc(15% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w20 {
|
||||
width: calc(20% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w25 {
|
||||
width: calc(25% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w30 {
|
||||
width: calc(30% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w33 {
|
||||
width: calc(33% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w35 {
|
||||
width: calc(35% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w40 {
|
||||
width: calc(40% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w45 {
|
||||
width: calc(45% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w50 {
|
||||
width: calc(50% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w55 {
|
||||
width: calc(55% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w60 {
|
||||
width: calc(60% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w65 {
|
||||
width: calc(65% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w66 {
|
||||
width: calc(66% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w70 {
|
||||
width: calc(70% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w75 {
|
||||
width: calc(75% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w80 {
|
||||
width: calc(80% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w85 {
|
||||
width: calc(85% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w90 {
|
||||
width: calc(90% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w95 {
|
||||
width: calc(95% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
.\32xl\:grid-w100 {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0px !important;
|
||||
}
|
||||
}
|
||||
.ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
@media (width >= 640px) {
|
||||
.sm\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.sm\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.sm\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 853px) {
|
||||
.md\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.md\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.md\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 1024px) {
|
||||
.lg\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.lg\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.lg\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 1280px) {
|
||||
.xl\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.xl\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.xl\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
@media (width >= 1536px) {
|
||||
.\32xl\:ratio-16-9 {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.\32xl\:ratio-21-9 {
|
||||
padding-top: 42.85%;
|
||||
}
|
||||
.\32xl\:ratio-32-9 {
|
||||
padding-top: 28.125%;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,16 @@
|
|||
<copyright>© 2026 Олег Казанин</copyright>
|
||||
<lastBuildDate>Mon, 23 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://192.168.11.190:1313/index.xml" rel="self" type="application/rss+xml" />
|
||||
|
||||
<item>
|
||||
<title>Shortcodes</title>
|
||||
<link>http://192.168.11.190:1313/cheatsheets/shortcodes/</link>
|
||||
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
|
||||
<author>oakazanin@ya.ru (Олег Казанин)</author>
|
||||
<guid>http://192.168.11.190:1313/cheatsheets/shortcodes/</guid>
|
||||
<description></description>
|
||||
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<title>Git Workflow для Hugo блога</title>
|
||||
<link>http://192.168.11.190:1313/cheatsheets/git-workflow/</link>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
(async()=>{const n=document.currentScript,s=n?.getAttribute("data-repo-url"),e=n?.getAttribute("data-repo-id");if(!s||!e)return;if(e.startsWith("forgejo")){console.log("fetch-repo.js: Forgejo server blocks cross-origin requests. Live JavaScript updates are not supported.");return}const o={github:{full_name:"full_name",description:"description",stargazers_count:"stargazers",forks:"forks"},gitlab:{name_with_namespace:"name_with_namespace",description:"description",star_count:"star_count",forks_count:"forks_count"},gitea:{full_name:"full_name",description:"description",stars_count:"stars_count",forks_count:"forks_count"},codeberg:{full_name:"full_name",description:"description",stars_count:"stars_count",forks_count:"forks_count"},forgejo:{full_name:"full_name",description:"description",stars_count:"stars_count",forks_count:"forks_count"},huggingface:{description:"description",likes:"likes",downloads:"downloads"}},i={huggingface:{description:e=>e?.replace(/Dataset Card for .+?\s+Dataset Summary\s+/,"").trim()||e}},t=Object.keys(o).find(t=>e.startsWith(t))||"github",a=o[t];try{const n=await fetch(s,{headers:{"User-agent":"Mozilla/4.0 Custom User Agent"}}),o=await n.json();if(!n.ok){console.error(`fetch-repo.js: HTTP Error: ${n.status} ${n.statusText}`);return}if(!o||typeof o!="object"){console.error("fetch-repo.js: Invalid or empty data received from remote");return}Object.entries(a).forEach(([n,s])=>{const a=document.getElementById(`${e}-${s}`);if(a){let e=o[n];i[t]?.[n]&&(e=i[t][n](e)),e!=null&&e!==""&&(a.innerHTML=e)}})}catch(e){console.error(`fetch-repo.js: ${e}`)}})()
|
||||
|
|
@ -0,0 +1 @@
|
|||
function _getDefaultPackeryOptions(){return{percentPosition:!0,gutter:5,resize:!0}}function _getPackeryOptions(e){const t=_getDefaultPackeryOptions(),{packeryGutter:n,packeryPercentPosition:s,packeryResize:o}=e.dataset;return{percentPosition:s!==0[0]?s==="true":t.percentPosition,gutter:n!==0[0]?parseInt(n,10):t.gutter,resize:o!==0[0]?o==="true":t.resize}}(function(){window.addEventListener("load",function(){let e=[],t=document.querySelectorAll(".gallery");t.forEach(t=>{let n=new Packery(t,_getPackeryOptions(t));e.push(n)})})})()
|
||||
|
|
@ -0,0 +1 @@
|
|||
function initTabs(){tabClickHandler=e=>{const t=e.target.closest(".tab__button");if(!t)return;const n=t.closest(".tab__container"),o=parseInt(t.dataset.tabIndex),i=t.dataset.tabLabel,s=n.dataset.tabGroup;if(s){const e=document.querySelectorAll(`.tab__container[data-tab-group="${s}"]`);e.forEach(e=>{const t=Array.from(e.querySelectorAll(".tab__button")).find(e=>e.dataset.tabLabel===i);if(t){const n=parseInt(t.dataset.tabIndex);activateTab(e,n)}})}else activateTab(n,o)},document.addEventListener("click",tabClickHandler)}function activateTab(e,t){const n=e.querySelectorAll(".tab__button"),s=e.querySelectorAll(".tab__panel");n.forEach((e,n)=>{n===t?(e.classList.add("tab--active"),e.setAttribute("aria-selected","true")):(e.classList.remove("tab--active"),e.setAttribute("aria-selected","false"))}),s.forEach((e,n)=>{n===t?e.classList.add("tab--active"):e.classList.remove("tab--active")})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",initTabs):initTabs()
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
/**
|
||||
* A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint.
|
||||
*
|
||||
* Thx to these as the inspiration
|
||||
* https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html
|
||||
* https://autoplay-youtube-player.glitch.me/
|
||||
*
|
||||
* Once built it, I also found these:
|
||||
* https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍)
|
||||
* https://github.com/Daugilas/lazyYT
|
||||
* https://github.com/vb/lazyframe
|
||||
*/
|
||||
class LiteYTEmbed extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.videoId = this.getAttribute('videoid');
|
||||
|
||||
let playBtnEl = this.querySelector('.lyt-playbtn,.lty-playbtn');
|
||||
// A label for the button takes priority over a [playlabel] attribute on the custom-element
|
||||
this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play';
|
||||
|
||||
this.dataset.title = this.getAttribute('title') || "";
|
||||
|
||||
/**
|
||||
* Lo, the youtube poster image! (aka the thumbnail, image placeholder, etc)
|
||||
*
|
||||
* See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md
|
||||
*/
|
||||
if (!this.style.backgroundImage) {
|
||||
this.style.backgroundImage = `url("https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg")`;
|
||||
this.upgradePosterImage();
|
||||
}
|
||||
|
||||
// Set up play button, and its visually hidden label
|
||||
if (!playBtnEl) {
|
||||
playBtnEl = document.createElement('button');
|
||||
playBtnEl.type = 'button';
|
||||
// Include the mispelled 'lty-' in case it's still being used. https://github.com/paulirish/lite-youtube-embed/issues/65
|
||||
playBtnEl.classList.add('lyt-playbtn', 'lty-playbtn');
|
||||
this.append(playBtnEl);
|
||||
}
|
||||
if (!playBtnEl.textContent) {
|
||||
const playBtnLabelEl = document.createElement('span');
|
||||
playBtnLabelEl.className = 'lyt-visually-hidden';
|
||||
playBtnLabelEl.textContent = this.playLabel;
|
||||
playBtnEl.append(playBtnLabelEl);
|
||||
}
|
||||
|
||||
this.addNoscriptIframe();
|
||||
|
||||
// for the PE pattern, change anchor's semantics to button
|
||||
if(playBtnEl.nodeName === 'A'){
|
||||
playBtnEl.removeAttribute('href');
|
||||
playBtnEl.setAttribute('tabindex', '0');
|
||||
playBtnEl.setAttribute('role', 'button');
|
||||
// fake button needs keyboard help
|
||||
playBtnEl.addEventListener('keydown', e => {
|
||||
if( e.key === 'Enter' || e.key === ' ' ){
|
||||
e.preventDefault();
|
||||
this.activate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// On hover (or tap), warm up the TCP connections we're (likely) about to use.
|
||||
this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true});
|
||||
this.addEventListener('focusin', LiteYTEmbed.warmConnections, {once: true});
|
||||
|
||||
// Once the user clicks, add the real iframe and drop our play button
|
||||
// TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time
|
||||
// We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003
|
||||
this.addEventListener('click', this.activate);
|
||||
|
||||
// Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1
|
||||
// However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe,
|
||||
// so they don't autoplay automatically. Instead we must load an additional 2 sequential JS files (1KB + 165KB) (un-br) for the YT Player API
|
||||
// TODO: Try loading the the YT API in parallel with our iframe and then attaching/playing it. #82
|
||||
this.needsYTApi = this.hasAttribute("js-api") || navigator.vendor.includes('Apple') || navigator.userAgent.includes('Mobi');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a <link rel={preload | preconnect} ...> to the head
|
||||
*/
|
||||
static addPrefetch(kind, url, as) {
|
||||
const linkEl = document.createElement('link');
|
||||
linkEl.rel = kind;
|
||||
linkEl.href = url;
|
||||
if (as) {
|
||||
linkEl.as = as;
|
||||
}
|
||||
document.head.append(linkEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin pre-connecting to warm up the iframe load
|
||||
* Since the embed's network requests load within its iframe,
|
||||
* preload/prefetch'ing them outside the iframe will only cause double-downloads.
|
||||
* So, the best we can do is warm up a few connections to origins that are in the critical path.
|
||||
*
|
||||
* Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267
|
||||
* But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity.
|
||||
*/
|
||||
static warmConnections() {
|
||||
if (LiteYTEmbed.preconnected) return;
|
||||
|
||||
// The iframe document and most of its subresources come right off youtube.com
|
||||
LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com');
|
||||
// The botguard script is fetched off from google.com
|
||||
LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com');
|
||||
|
||||
// Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling.
|
||||
LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net');
|
||||
LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net');
|
||||
|
||||
LiteYTEmbed.preconnected = true;
|
||||
}
|
||||
|
||||
fetchYTPlayerApi() {
|
||||
if (window.YT || (window.YT && window.YT.Player)) return;
|
||||
|
||||
this.ytApiPromise = new Promise((res, rej) => {
|
||||
var el = document.createElement('script');
|
||||
el.src = 'https://www.youtube.com/iframe_api';
|
||||
el.async = true;
|
||||
el.onload = _ => {
|
||||
YT.ready(res);
|
||||
};
|
||||
el.onerror = rej;
|
||||
this.append(el);
|
||||
});
|
||||
}
|
||||
|
||||
/** Return the YT Player API instance. (Public L-YT-E API) */
|
||||
async getYTPlayer() {
|
||||
if(!this.playerPromise) {
|
||||
await this.activate();
|
||||
}
|
||||
|
||||
return this.playerPromise;
|
||||
}
|
||||
|
||||
async addYTPlayerIframe() {
|
||||
this.fetchYTPlayerApi();
|
||||
await this.ytApiPromise;
|
||||
|
||||
const videoPlaceholderEl = document.createElement('div')
|
||||
this.append(videoPlaceholderEl);
|
||||
|
||||
const paramsObj = Object.fromEntries(this.getParams().entries());
|
||||
|
||||
this.playerPromise = new Promise(resolve => {
|
||||
let player = new YT.Player(videoPlaceholderEl, {
|
||||
width: '100%',
|
||||
videoId: this.videoId,
|
||||
playerVars: paramsObj,
|
||||
events: {
|
||||
'onReady': event => {
|
||||
event.target.playVideo();
|
||||
resolve(player);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add the iframe within <noscript> for indexability discoverability. See https://github.com/paulirish/lite-youtube-embed/issues/105
|
||||
addNoscriptIframe() {
|
||||
const iframeEl = this.createBasicIframe();
|
||||
const noscriptEl = document.createElement('noscript');
|
||||
// Appending into noscript isn't equivalant for mysterious reasons: https://html.spec.whatwg.org/multipage/scripting.html#the-noscript-element
|
||||
noscriptEl.innerHTML = iframeEl.outerHTML;
|
||||
this.append(noscriptEl);
|
||||
}
|
||||
|
||||
getParams() {
|
||||
const params = new URLSearchParams(this.getAttribute('params') || []);
|
||||
params.append('autoplay', '1');
|
||||
params.append('playsinline', '1');
|
||||
return params;
|
||||
}
|
||||
|
||||
async activate(){
|
||||
if (this.classList.contains('lyt-activated')) return;
|
||||
this.classList.add('lyt-activated');
|
||||
|
||||
if (this.needsYTApi) {
|
||||
return this.addYTPlayerIframe(this.getParams());
|
||||
}
|
||||
|
||||
const iframeEl = this.createBasicIframe();
|
||||
this.append(iframeEl);
|
||||
|
||||
// Set focus for a11y
|
||||
iframeEl.focus();
|
||||
}
|
||||
|
||||
createBasicIframe(){
|
||||
const iframeEl = document.createElement('iframe');
|
||||
iframeEl.width = 560;
|
||||
iframeEl.height = 315;
|
||||
// No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include
|
||||
iframeEl.title = this.playLabel;
|
||||
iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture';
|
||||
iframeEl.allowFullscreen = true;
|
||||
// Required by Youtube to fix Error 153
|
||||
iframeEl.referrerPolicy = 'strict-origin-when-cross-origin';
|
||||
// AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL
|
||||
// https://stackoverflow.com/q/64959723/89484
|
||||
iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.videoId)}?${this.getParams().toString()}`;
|
||||
return iframeEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the spirit of the `lowsrc` attribute and progressive JPEGs, we'll upgrade the reliable
|
||||
* poster image to a higher resolution one, if it's available.
|
||||
* Interestingly this sddefault webp is often smaller in filesize, but we will still attempt it second
|
||||
* because getting _an_ image in front of the user if our first priority.
|
||||
*
|
||||
* See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md for more details
|
||||
*/
|
||||
upgradePosterImage() {
|
||||
// Defer to reduce network contention.
|
||||
setTimeout(() => {
|
||||
const webpUrl = `https://i.ytimg.com/vi_webp/${this.videoId}/sddefault.webp`;
|
||||
const img = new Image();
|
||||
img.fetchPriority = 'low'; // low priority to reduce network contention
|
||||
img.referrerpolicy = 'origin'; // Not 100% sure it's needed, but https://github.com/ampproject/amphtml/pull/3940
|
||||
img.src = webpUrl;
|
||||
img.onload = e => {
|
||||
// A pretty ugly hack since onerror won't fire on YouTube image 404. This is (probably) due to
|
||||
// Youtube's style of returning data even with a 404 status. That data is a 120x90 placeholder image.
|
||||
// … per "annoying yt 404 behavior" in the .md
|
||||
const noAvailablePoster = e.target.naturalHeight == 90 && e.target.naturalWidth == 120;
|
||||
if (noAvailablePoster) return;
|
||||
|
||||
this.style.backgroundImage = `url("${webpUrl}")`;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
// Register custom element
|
||||
customElements.define('lite-youtube', LiteYTEmbed);
|
||||