<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
    <channel>
      <title>&gt; scclie://
notes</title>
      <link>https://shiza.sccl.cc</link>
      <description>All notes (full content) on &gt; scclie://
notes</description>
      <generator>Quartz -- quartz.jzhao.xyz</generator>
      <item>
    <title>XKB GUI config generator: xkb.sccl.cc</title>
    <link>https://shiza.sccl.cc/en/it/xkb.sccl</link>
    <guid>https://shiza.sccl.cc/en/it/xkb.sccl</guid>
    <description><![CDATA[ <h1 id="background">Background<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#background" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Historically, you need at least 2 layouts at a computer - English and Russian.</p>
<p>On top of that, I’ve been using Colemak-type layouts (Vanilla / DH / CAWS) for a while, plus custom Russian modifications like RulemakDH or RulemakCAWS (see <a href="../../en/Peripheral/Keyboards/Rulemak-CAWS" class="internal" data-slug="en/Peripheral/Keyboards/Rulemak-CAWS">Rulemak-CAWS.md</a>)</p>
<p>And all of this across two different matrices - regular Staggered ANSI and a split Columnar one.</p>
<p>Still haven’t reached any kind of ideal. It’s just clear that ANSI and Split keyboards need different layouts.</p>
<p>It would be great to ditch ANSI keyboards completely. But a couple times a week I have to use a laptop, and lugging the split around every time isn’t always convenient.</p>
<p>So I constantly have to tweak layout configs. Change them. Adjust for different keyboards. Different tasks.</p>
<hr>
<h1 id="xkb-what-it-is-and-why-its-pain">XKB: what it is and why it’s pain<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#xkb-what-it-is-and-why-its-pain" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>XKB (X Keyboard Extension) is the standard keyboard layout system on Linux. It’s under the hood of both X11 and Wayland. The entire keyboard - from keypress to character on screen - goes through XKB.</p>
<p>You can define multiple layers (Base, Shift, AltGr, AltGr+Shift). Assign any symbols to any keys. Dead keys, compose sequences, modifier remapping. Whatever you want.</p>
<p>XKB configuration means text files. It looks roughly like this:</p>
<pre><code>key &#x3C;AD01> {
    type[group1]= "ALPHABETIC",
    symbols[Group1]= [ q, Q, backslash, division ]
};
key &#x3C;AD02> {
    type[group1]= "ALPHABETIC",
    symbols[Group1]= [ w, W, U0312, division ]
};
</code></pre>
<p>One block per key. One symbol per layer. Times 50+ keys.</p>
<p>And you sit there with a keysym table, manually transcribing symbols, constantly double-checking so you don’t mess up. Inconvenient, easy to make mistakes, etc etc.</p>
<p>So yeah, that’s exactly what drove me to build <a href="https://xkb.sccl.cc" class="external">xkb.sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<hr>
<h1 id="xkbscclcc">xkb.sccl.cc<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#xkbscclcc" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>A visual XKB layout editor for Linux. In the browser.</p>
<p><img src="../../pictures/it/xkb_sccl_cc.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Nothing special - just a QoL thing. Click the buttons you need, copy the config, off you go.</p>
<p>Built with <a href="https://zine-ssg.io" class="external">Zine<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - the same Zig SSG that powers the main <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Pure HTML+CSS+JS, no frameworks, everything client-side.</p>
<p>All standard XKB symbols for binding are already on the site. But XKB can take any Unicode character, so you’re not limited to just those.</p>
<p><img src="../../pictures/it/xkb_sccl_bind.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>The idea is simple: you click a key on the on-screen keyboard, a keysym search pops up, you pick the symbol. Repeat for each layer. Done - hit “copy xkb config” or download the <code>.xkb</code> file.</p>
<hr>
<h1 id="how-to-use">How to use<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#how-to-use" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Generated a config - two ways to apply:</p>
<p><strong>Quick (temporary):</strong></p>
<pre><code>xkbcomp my-layout.xkb $DISPLAY
</code></pre>
<p>Applies immediately. Until reboot or relogin.</p>
<p><strong>Persistent:</strong><br>
Drop the <code>.xkb</code> file in <code>/etc/X11/xkb/symbols/</code>, edit <code>evdev.xml</code> so the system knows about your layout. Or use your DE’s keyboard settings.</p>
<p>On <a href="https:/shiza./sccl.cc/en/it/os/sccl_nix" class="external">NixOS with sccl_nix<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> the layout is already parametrized - you can drop the generated config alongside and hook it up via <code>sccl.keyboard</code>.</p>
<hr>
<h1 id="contributing">Contributing<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#contributing" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>The site stores configs in localdata in its own format. You can download it as JSON at any time.</p>
<p>If you’ve cooked up a decent layout - <a href="https://codeberg.org/scclie/xkb.sccl/pulls" class="external">create a PR<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>: drop the JSON into <code>assets/presets/</code> and the layout shows up in the presets list on the site.<br>
<small>you can PR on github too, but it’s not quite as convenient for me.</small><br></p>
<p><small>maybe I’ll add direct import from existing xkb configs later. would be nice.</small><br></p>
<p>Everything is, of course, <a href="https://codeberg.org/scclie/xkb.sccl" class="external">open source<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p> ]]></description>
    <pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>XKB GUI config generator: xkb.sccl.cc</title>
    <link>https://shiza.sccl.cc/ru/it/xkb.sccl</link>
    <guid>https://shiza.sccl.cc/ru/it/xkb.sccl</guid>
    <description><![CDATA[ <h1 id="предыстория">Предыстория<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#предыстория" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Так исторически сложилось, что за компьютером нужно минимум 2 лейаута - Английский и Русский.</p>
<p>К тому же уже давно использую раскладки вида Colemak (Vanilla / DH / CAWS) и свои модификации для русского типа RulemakDH иль RulemakCAWS (см <a href="../../ru/Peripheral/Keyboards/Rulemak-CAWS" class="internal" data-slug="ru/Peripheral/Keyboards/Rulemak-CAWS">Rulemak-CAWS.md</a>)</p>
<p>Да всё это на двух разных матрицах - обычной Staggered ANSI и раздельной Columnar.</p>
<p>Пока до какого-то идеала дойти не удалось. Ясно лишь что для ANSI и Split клавиатуры нужны разные раскладки.</p>
<p>Было бы круто конечно полностью отказаться от ANSI клавиатур. Но пару раз в неделю приходится пользоваться ноутбуком, и каждый раз таскать с собой сплит не всегда удобно.</p>
<p>Соответсвенно конфиги раскладок мне надо постоянно редачить. Менять. Подгонять под разные клавы. Под разные задачи.</p>
<hr>
<h1 id="xkb-что-это-и-почему-боль">XKB: что это и почему боль<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#xkb-что-это-и-почему-боль" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>XKB (X Keyboard Extension) - это стандартная система клавиатурных раскладок в Linux. Она под капотом у X11 и Wayland. Вся клавиатура - от нажатия клавиши до символа на экране - проходит через XKB.</p>
<p>Можно определить несколько слоёв (Base, Shift, AltGr, AltGr+Shift). Можно назначать любые символы на любые клавиши. Можно dead keys, compose sequences, переопределение модификаторов. Всё что угодно.</p>
<p>Конфигурация XKB - это текстовые файлы. Выглядит это примерно так:</p>
<pre><code>key &#x3C;AD01> {
    type[group1]= "ALPHABETIC",
    symbols[Group1]= [ q, Q, backslash, division ]
};
key &#x3C;AD02> {
    type[group1]= "ALPHABETIC",
    symbols[Group1]= [ w, W, U0312, division ]
};
</code></pre>
<p>На каждую кнопку - свой блок. На каждый слой - свой символ. И так 50+ кнопок.</p>
<p>И ты сидишь с таблицей keysym-ов, ручками переписываешь символы, постоянно сверяясь чтоб не ошибиться. Неудобно, легко ошибиться и т.д и т.п..</p>
<p>Собсна, именно это задолбало настолько, что появился <a href="https://xkb.sccl.cc" class="external">xkb.sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<hr>
<h1 id="xkbscclcc">xkb.sccl.cc<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#xkbscclcc" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Визуальный редактор раскладок XKB для Linux. В вебе.</p>
<p><img src="../../pictures/it/xkb_sccl_cc.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Ничего особенного - просто QoL штучка. Натыкал нужные кнопки, скопировал конфиг, пользуешься.</p>
<p>На чём сделано: <a href="https://zine-ssg.io" class="external">Zine<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - тот же SSG на Zig, на котором работает основной <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Чистый HTML+CSS+JS, никаких фреймворков, всё на клиенте.</p>
<p>Все стандартные символы из XKB для бинда уже есть на сайте. Но XKB умеет вбивать любые Unicode символы, т.ч. только ими можно не ограничиваться.</p>
<p><img src="../../pictures/it/xkb_sccl_bind.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Принцип простой: тыкаешь на кнопку на экранной клавиатуре, появляется поиск по keysym-ам, выбираешь нужный символ. Повторяешь для каждого слоя. Готово - жмёшь “copy xkb config” или скачиваешь <code>.xkb</code> файл.</p>
<hr>
<h1 id="как-юзать">Как юзать<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#как-юзать" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Сгенерил конфиг - дальше два пути:</p>
<p><strong>Быстрый (временно):</strong></p>
<pre><code>xkbcomp my-layout.xkb $DISPLAY
</code></pre>
<p>Применится сразу. До перезагрузки или перелогина.</p>
<p><strong>Персистентный:</strong><br>
Кладёшь <code>.xkb</code> файл в <code>/etc/X11/xkb/symbols/</code>, правишь <code>evdev.xml</code> чтоб система знала про твою раскладку. Или через DE-настройки клавиатуры.</p>
<p>На <a href="../../ru/it/os/sccl_nix" class="internal alias" data-slug="ru/it/os/sccl_nix">NixOS со sccl_nix</a> раскладка уже параметризована - сгенеренный конфиг можно положить рядом и подключить через <code>sccl.keyboard</code>.</p>
<hr>
<h1 id="contributing">Contributing<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#contributing" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Сайт хранит конфиг в localdata в своём формате. В любой момент его можно скачать как JSON.</p>
<p>Если запилил годную раскладку - <a href="https://codeberg.org/scclie/xkb.sccl/pulls" class="external">создавай PR<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>: скидываешь JSON в <code>assets/presets/</code> и раскладка появляется на сайте в списке пресетов.<br>
<small>pr можно и на github, но мне не совсем удобно.</small><br></p>
<p><small>потом может добавлю прямой импорт из существующего xkb конфига. было б удобно.</small><br><br>
Всё естестна <a href="https://codeberg.org/scclie/xkb.sccl" class="external">опенсурс<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p> ]]></description>
    <pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Shoving 88x31 images into CSS</title>
    <link>https://shiza.sccl.cc/en/it/88x31-in-CSS</link>
    <guid>https://shiza.sccl.cc/en/it/88x31-in-CSS</guid>
    <description><![CDATA[ <h1 id="well-fuck-all-this-then">Well fuck all this then?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#well-fuck-all-this-then" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>My site <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> has a whole history of optimizations.</p>
<p>First there was the migration from Astro to Zine - <a href="../../en/it/Migration-from-Astro-to-Zine" class="internal alias" data-slug="en/it/Migration-from-Astro-to-Zine">separate shitpost about that</a>. Then squeezing everything into 1 RTT - <a href="../../en/it/14kb-cap" class="internal alias" data-slug="en/it/14kb-cap">already wrote a deep article on that</a>.</p>
<p><small>tl;dr: the site is compressed to 13kb gzip, loads in a single TCP packet, one round trip for data.</small><br></p>
<p>So the site flies. But I wanted to add 88x31 badges.</p>
<p>You know, those tiny buttons everyone puts in their site footer. Like “Powered by something”, links to friends, clubs like 512kb.club and 14kb.club.</p>
<p>It’s web classics. How do you even live without them.</p>
<p>But there’s a catch: the site is optimized down to one request. And now you have a dozen badges, each 200++ bytes but each a separate HTTP request.</p>
<p>Gotta figure something out.</p>
<hr>
<h1 id="88x31-where-it-even-came-from">88x31: where it even came from<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#88x31-where-it-even-came-from" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>The 88 by 31 pixel format is one of the oldest web traditions.</p>
<p>It all started with Netscape. In 1996 they launched the “<a href="https://web.archive.org/web/19961026040131/http://www3.netscape.com/comprod/mirror/netscape_now_program.html" class="external">Netscape Now!<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>” program - buttons you could put on your site to show you’re using their browser.</p>
<p>88x31, gray background, beveled edges, “Netscape Now!” text.</p>
<p>Then others picked it up. Microsoft with Internet Explorer. GeoCities for their users. Then “Best viewed in…”, “Powered by…” - off it went.</p>
<p>The browser wars era made these buttons ubiquitous. It was a way to show affiliation: I use this browser, I’m hosted on this platform, I’m in this webring.</p>
<p>Now the format is having a second birth. <a href="https://neocities.org" class="external">Neocities<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, indie web revival, all sorts of clubs (512kb.club, 14kb.club, no-js.club) - each has its own badge. And people are plastering them all over their sites.</p>
<p><a href="https://neonaut.neocities.org/cyber/88x31" class="external">neonaut.neocities.org<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> has compiled an archive of 13,051 badges from 11,011 Neocitizens. 82 megabytes of pure 88x31. They even have a counter for dead sites ;k</p>
<p><small>btw, this is the same neonaut who wrote the essay on the <a href="https://neonaut.neocities.org/cyber/88x31" class="external">history of 88x31<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - that’s where the facts above come from. recommended.</small><br></p>
<hr>
<h1 id="the-problem-67-requests">The problem: 67 requests<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-problem-67-requests" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Usually badges are inserted as <code>&#x3C;img src="..."></code> and no one gives a shit.</p>
<p>It looks something like this:</p>
<p><img src="../../pictures/it/88x31_http_requests.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Each badge is a separate HTTP request. Font, styles, script, 67 images.</p>
<p>And each request is a full cycle: DNS lookup (if cold), TCP handshake, TLS handshake. Even if the image weighs a measly 200 bytes - the overhead is dozens of times larger than the image itself.</p>
<p>At least most people don’t hotlink and host the badges themselves.<br>
<em>otherwise it would be a complete fucking disaster</em></p>
<p>But even with local hosting, 67 requests is a shitton. If you have HTTP/2 and keep-alive, some of the cost gets amortized. But the initial setup still exists. And each request is a delay.</p>
<p>And my site is tightly optimized for 1 RTT. I can’t just casually add 67 requests.<br>
<small>the philosophy doesn’t allow it.</small><br></p>
<p>Plus, besides speed, there’s a bonus of bypassing Russian <a href="https://en.wikipedia.org/wiki/Deep_packet_inspection" class="external">TSPU<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - the first packet doesn’t get caught if it fits in initcwnd.</p>
<hr>
<h1 id="what-do-we-do">What do we do<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#what-do-we-do" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Obviously cramming all images into the same 1 RTT won’t work - too fat.</p>
<p>Inlining in HTML isn’t an option either - each badge in base64 adds ~33% to the weight, and we’d(I’d) fly past initcwnd.</p>
<p>But you can do it differently: collect all badges into a single CSS file as base64 and load it with ONE request.</p>
<p>Second request, yeah. But you can have 100 badges - still one request.</p>
<p>The idea: at site build time, a generator reads a config, fetches current badges (or takes local ones), converts to base64, stuffs into CSS as <code>background-image</code>.</p>
<hr>
<h1 id="how-it-works">How it works<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#how-it-works" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>The CSS is generated at site build time. Input is a config:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-light github-dark"><code data-language="json" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "badges"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "url"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https://example.com/assets/example.png"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "href"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https://example.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "alt"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"example.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "file"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"1.png"</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      },</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "href"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https://test.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "alt"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"test.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "local"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"../img/test.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "file"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"2.png"</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ]</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre></figure>
<p>Two modes:</p>
<ul>
<li><code>url</code> - fetches the badge from the specified site</li>
<li><code>local</code> - takes a local file (if the badge is already downloaded and in the repo)</li>
</ul>
<p>The generator walks the config, downloads/reads files, converts to base64, generates CSS:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="css" data-theme="github-light github-dark"><code data-language="css" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.badge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">display</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">inline-block</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">width</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">88</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">px</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">height</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">31</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">px</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">image-rendering</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">pixelated</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">image-rendering</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">crisp-edges</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.badge-0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">background</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data:image/gif;base64,R0lGODdhWAAfAHcAACH...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">no-repeat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.badge-1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">background</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data:image/png;base64,iVBORw0KGgoAAAA...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">no-repeat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* and so on */</span></span></code></pre></figure>
<p>Then in the HTML, instead of 67 <code>&#x3C;img></code> tags, we insert 67 <code>&#x3C;a class="badge badge-N"></code> links with a CSS background image.</p>
<p>Applied as a style:<br>
<img src="../../pictures/it/base64style.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<hr>
<h1 id="the-profit">The profit<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-profit" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Instead of 67 separate requests per badge, we get:</p>
<p><strong>Main site:</strong> 1 HTTP request, 13kb gzip, 1 RTT. That’s how it was - <a href="../../en/it/14kb-cap" class="internal alias" data-slug="en/it/14kb-cap">already wrote about this</a>.</p>
<p><strong>Badges:</strong> 1 HTTP request, one CSS file with all images in base64.</p>
<p><img src="../../pictures/it/imgs_in_css.webp" width="auto" height="auto" alt="" loading="lazy"><br>
<em>the “data:” lines are not http requests - the browser decodes base64 into images locally.</em></p>
<p>Total: TWO requests for the entire site with all badges. Instead of 68+.</p>
<p>If badges grow way past 10 - the CSS file might not fit in one initcwnd and go to 2 RTT. But the difference between 1 and 2 RTT isn’t dramatic when it’s not the main content. Badges in the footer - the user won’t see them right away anyway. <em>plus I have a fallback with buttons without images</em></p>
<p>The downside: base64 adds ~33% to binary data size. For 88x31 badges (usually &#x3C; 1kb) this is pocket change. But if you have a hundred badges at 5kb each - it’ll start to show.</p>
<p>Also the images don’t cache separately. On page refresh - badges reload together with the CSS. But CSS gets cached by the browser too, so in practice you don’t feel the difference.</p>
<p>In my case the benefit is clear: the site stays fast, badges are there, everything looks as it should.</p> ]]></description>
    <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Пихаем 88x31 картинки в CSS</title>
    <link>https://shiza.sccl.cc/ru/it/88x31-in-CSS</link>
    <guid>https://shiza.sccl.cc/ru/it/88x31-in-CSS</guid>
    <description><![CDATA[ <h1 id="ну-и-нахуй-всё-это">Ну и нахуй всё это?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#ну-и-нахуй-всё-это" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>У меня на сайте <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> есть целая история оптимизаций.</p>
<p>Сначала был переезд с Astro на Zine - <a href="../../ru/it/Migration-from-Astro-to-Zine" class="internal alias" data-slug="ru/it/Migration-from-Astro-to-Zine">об этом отдельный шитпост</a>. Потом впихивание всего в 1 RTT - <a href="../../ru/it/14kb-cap" class="internal alias" data-slug="ru/it/14kb-cap">тут уже навалена глубокая статья</a>.</p>
<p><small>если кратко: сайт ужат до 13kb gzip, грузится одним TCP-пакетом, один round trip на данные.</small><br></p>
<p>Вроде сайт летает. Но хочется добавить 88x31 баннеры.</p>
<p>Ну эти маленькие кнопочки, которые все пихают в футер сайта. Типа “Powered by something”, ссылки на друзей, клубы типа 512kb.club и 14kb.club.</p>
<p>Это ж классика веба. Как без них.</p>
<p>Но есть нюанс: сайт оптимизирован до одного запроса. А тут у тебя дцать баннеров, каждый по 200++ байт но каждый - отдельный HTTP-запрос.</p>
<p>Нужно чота придумывать.</p>
<hr>
<h1 id="88x31-откуда-вообще">88x31: откуда вообще<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#88x31-откуда-вообще" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Формат 88 на 31 пиксель - это одна из старейших традиций веба.</p>
<p>Началось всё с Netscape. В 1996 году они запустили программу “<a href="https://web.archive.org/web/19961026040131/http://www3.netscape.com/comprod/mirror/netscape_now_program.html" class="external">Netscape Now!<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>” - кнопки которые можно было вставить на сайт чтоб показать что ты юзаешь их браузер.</p>
<p>Потом идею подхватили. Microsoft со своим Internet Explorer. GeoCities для своих пользователей. Потом “Best viewed in…”, “Powered by…” - понеслась.</p>
<p>Эпоха browser wars сделала эти кнопки вездесущими. Это был способ показать принадлежность: я юзаю этот браузер, я хостюсь на этой платформе, я состою в этом веб-ринге.</p>
<p>Сейчас формат переживает второе рождение. <a href="https://neocities.org" class="external">Neocities<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, indie web revival, всякие клубы (512kb.club, 14kb.club, no-js.club) - у каждого свой баннер. И народ массово клеит их на сайты.</p>
<p><a href="https://neonaut.neocities.org/cyber/88x31" class="external">neonaut.neocities.org<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> собрал архив из 13 051 баннера с 11 011 неоситизенов. 82 мегабайта чистого 88x31. Есть даже счётчик умерших сайтов ;k</p>
<p><small>к слову, это тот же neonaut который написал эссе про <a href="https://neonaut.neocities.org/cyber/88x31" class="external">историю 88x31<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - оттуда и факты выше. рекомендую.</small><br></p>
<hr>
<h1 id="проблема-67-запросов">Проблема: 67 запросов<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#проблема-67-запросов" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Обычно баннеры вставляют <code>&#x3C;img src="..."></code> и похуй.</p>
<p>Выглядит это примерно так:</p>
<p><img src="../../pictures/it/88x31_http_requests.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Каждый баннер - отдельный HTTP-запрос. Шрифт, стили, скрипт, 67 картинок.</p>
<p>И каждый запрос это полный цикл: DNS lookup (если холодный), TCP handshake, TLS handshake. Даже если картинка весит жалкие 200 байт - накладные расходы в десятки раз больше самой картинки.</p>
<p>Благо большинство не хотлинкит, а хостит баннеры у себя.<br>
<em>а то ваще пиздец бы был</em></p>
<p>Но даже с локальным хостингом 67 запросов - это дохуя. Если у тебя HTTP/2 и keep-alive, часть расходов амортизируется. Но первый setup всё равно есть. И каждый запрос это задержка.</p>
<p>А у меня сайт жёстко оптимизирован под 1 RTT. Я не могу просто взять и добавить 67 запросов.<br>
<small>философия не позволяет.</small><br></p>
<p>Тем более помимо скорости тут ещё и бонус в виде обхода расиянских <a href="https://en.wikipedia.org/wiki/Deep_packet_inspection" class="external">ТСПУ<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - первый пакет не ловится, если влезает в initcwnd.</p>
<hr>
<h1 id="чо-делаем">Чо делаем<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#чо-делаем" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Ясен хуй впихнуть все картинки в тот же 1 RTT не получится - слишком жирно.</p>
<p>Инлайнить в HTML тоже не вариант - каждый баннер в base64 добавит ~33% к весу, и мы(я) вылетим за initcwnd.</p>
<p>Но можно сделать по-другому: собрать все баннеры в один CSS-файл base64 и подгрузить его ОДНИМ запросом.</p>
<p>Второй запрос, да. Но зато баннеров может быть хоть 100 - это всё равно один запрос.</p>
<p>Идея: при билде сайта генератор читает конфиг, фетчит актуальные баннеры (или берёт локальные), конвертит в base64, пихает в CSS как <code>background-image</code>.</p>
<hr>
<h1 id="как-работает">Как работает<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#как-работает" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>CSS генерится при билде сайта. На входе конфиг:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-light github-dark"><code data-language="json" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "badges"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "url"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https://example.com/assets/example.png"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "href"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https://example.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "alt"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"example.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "file"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"1.png"</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      },</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "href"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"https://test.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "alt"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"test.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "local"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"../img/test.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "file"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"2.png"</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    ]</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre></figure>
<p>Два режима:</p>
<ul>
<li><code>url</code> - фетчит баннер с указанного сайта</li>
<li><code>local</code> - берёт локальный файл (если баннер уже скачан и лежит в репе)</li>
</ul>
<p>Генератор обходит конфиг, скачивает/читает файлы, конвертит в base64, генерит CSS:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="css" data-theme="github-light github-dark"><code data-language="css" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.badge</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">display</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">inline-block</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">width</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">88</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">px</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">height</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">31</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">px</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">image-rendering</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">pixelated</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">image-rendering</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">crisp-edges</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.badge-0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">background</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data:image/gif;base64,R0lGODdhWAAfAHcAACH...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">no-repeat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.badge-1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">background</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">url</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">data:image/png;base64,iVBORw0KGgoAAAA...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">no-repeat</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* и так далее */</span></span></code></pre></figure>
<p>Дальше в HTML вместо 67 <code>&#x3C;img></code> тегов вставляем 67 <code>&#x3C;a class="badge badge-N"></code> ссылок с фоновой картинкой из CSS.</p>
<p>Применяется как стиль:<br>
<img src="../../pictures/it/base64style.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<hr>
<h1 id="профит">Профит<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#профит" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Вместо 67 отдельных запросов на каждый баннер получаем:</p>
<p><strong>Основной сайт:</strong> 1 HTTP-запрос, 12kb gzip, 1 RTT. Ну это как и было - <a href="../../ru/it/14kb-cap" class="internal alias" data-slug="ru/it/14kb-cap">про это я уже писал</a>.</p>
<p><strong>Баннеры:</strong> 1 HTTP-запрос, один CSS-файл со всеми картинками в base64.</p>
<p><img src="../../pictures/it/imgs_in_css.webp" width="auto" height="auto" alt="" loading="lazy"><br>
<em>строки “data:” не являются http-запросами - это браузер декодит base64 в картинку локально.</em></p>
<p>Итого: ДВА запроса на весь сайт со всеми баннерами. Вместо 68+.</p>
<p>Если баннеров станет сильно больше 10 - CSS-файл может не влезть в один initcwnd и уйти на 2 RTT. Но разница между 1 и 2 RTT не драматична, когда это не основной контент. Баннеры в футере - пользователь их всё равно не видит сразу. <em>к тому же у меня есть fallback с кнопочками без картинки</em></p>
<p>Из минусов: base64 добавляет ~33% к размеру бинарных данных. Для 88x31 баннеров (обычно &#x3C; 1kb) это копейки. Но если у тебя сотня баннеров по 5kb каждый - уже начнёт быть заметно.</p>
<p>Ну и картинки не кешируются отдельно. При обновлении страницы - баннеры грузятся заново вместе с CSS. Но CSS тоже кешируется браузером, т.ч. на практике разница не чувствуется.</p>
<p>В моём случае профит очевиден: сайт остаётся быстрым, баннеры есть, всё выглядит как надо.</p> ]]></description>
    <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>14kb ceiling: how TCP invented the small web</title>
    <link>https://shiza.sccl.cc/en/it/14kb-cap</link>
    <guid>https://shiza.sccl.cc/en/it/14kb-cap</guid>
    <description><![CDATA[ <h1 id="why-14-kb">Why 14 KB?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#why-14-kb" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Here, real requests for reference.</p>
<p><strong>A site that fits in 14kb:</strong><br>
<img src="../../pictures/it/site_download.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p><strong>One that doesn’t:</strong><br>
<img src="../../pictures/it/big_site_download.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Download - that’s the actual site content.<br>
1 ms vs 63 ms - 63x speed difference, with only a 7x weight difference.</p>
<p>How the hell?</p>
<p>This braindump is an attempt to dig into this topic down to the guts. TCP, DNS, TLS, slow start, the 1986 congestion collapse. And along the way, the philosophy of small web: why a tiny site is respect for the user, not a numbers circlejerk.</p>
<p><small>and I’ll talk about my own sccl.cc optimization</small><br></p>
<hr>
<h1 id="whatamhow-tcp">Whatamhow TCP<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#whatamhow-tcp" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Before the server sends even a single byte of HTML, a fuckton of things happen.</p>
<h2 id="three-way-handshake">Three-way handshake<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#three-way-handshake" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Every TCP connection starts with a three-way handshake:</p>
<ol>
<li>Client sends <code>SYN</code></li>
<li>Server replies <code>SYN-ACK</code></li>
<li>Client finishes with <code>ACK</code></li>
</ol>
<p>Three messages back and forth. Only then can you send data.<br>
That’s +1 RTT just to say hello.</p>
<p>There’s TCP Fast Open (TFO) - lets you cram data into the SYN packet. But it only works for repeat connections and needs support on both sides. Rare in practice.</p>
<h2 id="congestion-collapse-1986">Congestion collapse 1986<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#congestion-collapse-1986" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>In early 1986, the internet nearly died.</p>
<p>Node count passed 5,000, bandwidth was uneven. TCP without congestion control just flooded the channel with packets. Routers dropped them, hosts resent, routers dropped more - avalanche.</p>
<p>Throughput dropped 1,000x. The network became unusable.</p>
<p>They called it <strong>congestion collapse</strong>.</p>
<p>In 1988, Van Jacobson <a href="https://ee.lbl.gov/papers/congavoid.pdf" class="external">published “Congestion Avoidance and Control”<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> describing four algorithms that saved the internet: slow-start, congestion avoidance, fast retransmit, fast recovery.</p>
<p>These four became mandatory parts of TCP. They essentially prevented the internet from collapsing in the 80s and 90s as traffic grew exponentially.</p>
<h2 id="slow-start">Slow start<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#slow-start" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The idea is simple: the server has no idea what the channel bandwidth to the client is.</p>
<p>If you blast 100 packets immediately - you can clog the network and trigger that same congestion collapse. So you start small and ramp up.</p>
<p>The server maintains a variable called <strong>cwnd</strong> (congestion window) - how many packets it can send without waiting for ACK.</p>
<p><strong>initcwnd</strong> - the starting value of this window:</p>
<ul>
<li>Originally: 1 segment</li>
<li>RFC 3390: 4 segments</li>
<li>RFC 6928 (2013): <strong>10 segments</strong></li>
</ul>
<p>Google <a href="https://datatracker.ietf.org/doc/html/rfc6928" class="external">pushed the increase to 10<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> in 2013 through experiments showing it doesn’t make things worse. Some CDNs (Cloudflare, Fastly) <a href="https://www.cdnplanet.com/blog/initcwnd-settings-major-cdn-providers/" class="external">set it to 30 or more<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Then exponential growth:</p>
<p>Server sent 10 packets - waits for ACK<br>
ACK arrives - window doubles to 20<br>
Sent 20 - waits<br>
Window to 40, then 80…</p>
<p>Each cycle is one RTT.<br>
The bigger the response, the more rounds.<br>
If the response fits in initcwnd - it flies out as a single packet with no waiting - one round trip.</p>
<p>For large files (images, video) this isn’t a problem - TCP has time to ramp up over a few RTTs. But most website files are under 100kb - and that’s where the pain starts.</p>
<p>Look: a 100mb file - first couple RTTs it ramps up, then pumps at full speed. Those first RTTs are a drop in the ocean.</p>
<p>A 30kb file - lands right where slow start is just beginning to ramp. Instead of one RTT - two. Instead of ~50ms - ~100ms.</p>
<p>And if there’s a dozen such files, each on a new connection - each goes through this dance from scratch.</p>
<hr>
<h1 id="who-burns-what">Who burns what<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#who-burns-what" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Before the server even gets to sending HTML - a whole ritual. Every step is an RTT.</p>
<p><strong>DNS: +1 RTT</strong><br>
Domain resolution. Usually cached, so in most cases 0ms. But a cold lookup - honest RTT.</p>
<p><strong>TCP handshake: +1 RTT</strong><br>
SYN, SYN-ACK, ACK. No way around it.</p>
<p><strong>TLS: +2 RTT (or +1 with TLS 1.3)</strong><br>
TLS 1.2: 2 RTT. TLS 1.3: 1 RTT. With 0-RTT resumption you can get it down to 0, but only for repeat connections.</p>
<p>Btw, the certificate itself can weigh several kilobytes and not fit in initcwnd. <a href="https://sirupsen.com/napkin/problem-15" class="external">Simon Hørup Eskildsen analyzed<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> the Danish newspaper information.dk - certificate was 6908 bytes, initcwnd was 3 (old Linux). The certificate didn’t fit in the window, and the TLS handshake took 3 RTT instead of 2. Just because the server waited for ACK mid-certificate.</p>
<p>Fuck.</p>
<p><strong>Total:</strong> minimum 3-4 RTT before the first byte of HTML. And we haven’t even started sending content. Once we do - slow start kicks in.</p>
<h2 id="satellite">Satellite<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#satellite" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>There’s a <a href="https://endtimes.dev/why-your-website-should-be-under-14kb-in-size/" class="external">great example<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> from Nathaniel at endtimes.dev:</p>
<p>Oil rig. Dudes forgot their D&#x26;D dice and want to hit missingdice.com over satellite internet.</p>
<p>Packet path:</p>
<pre><code>phone -> WiFi router -> dish -> satellite (35,786 km) -> ground station -> server
</code></pre>
<p>One RTT: <code>1ms (router) + 120ms (ground-to-satellite) + 120ms (satellite-to-ground) + 60ms (ground-to-server)</code> - roughly 300ms one way.</p>
<p>Full round trip: <strong>612ms</strong>. That’s one RTT.</p>
<p>With 4 RTTs (DNS + TCP + 2xTLS) before the first byte: 2.5 seconds.<br>
With slow start over several rounds - all of 4-5 seconds.</p>
<p>For a dice website.</p>
<p>You see it now, right?</p>
<hr>
<h1 id="the-magic-of-14600">The magic of 14,600<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-magic-of-14600" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Let’s count:</p>
<ul>
<li>MTU Ethernet: 1500 bytes</li>
<li>IP header: 20 bytes</li>
<li>TCP header: 20 bytes</li>
<li>Payload: 1500 - 40 = <strong>1460 bytes</strong> per packet</li>
<li>initcwnd = 10 packets</li>
<li>10 x 1460 = <strong>14,600 bytes</strong> = ~14.25 KB</li>
</ul>
<p>That’s where the number comes from.</p>
<h2 id="step-function">Step function<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#step-function" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>It’s not linear.</p>
<p>14kb - 1 RTT.<br>
15kb - 2 RTT.<br>
29kb - 2 RTT.<br>
30kb - 3 RTT.</p>
<p>Every time you cross a window boundary - a whole RTT gets added. The difference between 14kb and 15kb can be bigger than between 15kb and 40kb.</p>
<p>The fun part: HTML gets compressed with gzip. 27kb raw easily becomes 12kb gzip. And 12kb - still in initcwnd, one RTT.</p>
<p><a href="https://14kbclub.com" class="external">14KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> counts <strong>uncompressed</strong>. So by club rules you need to fit in 14kb raw HTML.</p>
<p>But in reality, a 20-30kb raw site with gzip is almost as fast as a “pure” 14kb site. The difference - nanoseconds on decompression.</p>
<p>Though counting compressed would make more sense, imo. Whatever.</p>
<h2 id="what-about-http2-http3">What about HTTP/2, HTTP/3?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#what-about-http2-http3" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>There’s this idea that multiplexing kills the 14kb rule. It doesn’t.</p>
<p>HTTP/2 lets you send multiple requests over one TCP connection. But TCP slow start is still there - it’s a TCP thing, not HTTP. The first request still goes through slow start.</p>
<p>HTTP/3 (QUIC) runs over UDP. But the <a href="https://datatracker.ietf.org/doc/id/draft-ietf-quic-recovery-26.html" class="external">QUIC spec recommends the same initcwnd of 10 packets<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Plus, the HTTPS handshake and HTTP/2 preface eat part of the initial window. <a href="https://www.tunetheweb.com/blog/critical-resources-and-the-first-14kb/" class="external">Barry Pollard analyzed in detail<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> that by the time HTML is sent, the window may already be larger than 10 - ACKs were being sent during TLS negotiation. So “exactly 14kb” is a simplification.</p>
<p>But the mechanics are the same. Understanding them beats obsessing over the number.</p>
<hr>
<h1 id="small-web-is-not-about-kilobytes">Small web is not about kilobytes<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#small-web-is-not-about-kilobytes" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>The small site thing - it’s not just about speed. It’s about philosophy.</p>
<h2 id="clubs">Clubs<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#clubs" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>There’s a whole ecosystem: <a href="https://1mb.club" class="external">1MB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://512kb.club" class="external">512KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://250kb.club" class="external">250KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://14kbclub.com" class="external">14KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Plus various no-js.club, nocss.club and such.</p>
<p>The 512KB Club homepage <a href="https://512kb.club" class="external">says it outright<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>:</p>
<blockquote>
<p>The internet has become a <strong>bloated mess</strong>. Huge JavaScript libraries, countless client-side queries and overly complex frontend frameworks are par for the course these days.</p>
</blockquote>
<p>And they’re right.</p>
<p>NYT - a newspaper layout, text and images - <a href="https://512kb.club/assets/images/nytimes-report.pdf" class="external">weighs 9 megabytes over 891 requests<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, half of which is JavaScript. That’s fucked up.</p>
<p>A site that by nature can’t be heavy, somehow turned into a monster. And ok, if it were a webgl experiment. It’s a newspaper. Text and pictures.</p>
<h2 id="motherfucking-website">Motherfucking website<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#motherfucking-website" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>If you haven’t seen <a href="https://motherfuckingwebsite.com" class="external">motherfuckingwebsite.com<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - go look. One HTML file, no CSS, no JS. Black text on white background. 5kb.</p>
<p>Then came <a href="https://bestmotherfucking.website" class="external">bestmotherfucking.website<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - minimal CSS, 78 bytes inline. Then <a href="https://thebestmotherfucking.website" class="external">thebestmotherfucking.website<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - with a dark theme, but 132kb, kinda too much.</p>
<p>There’s also <a href="https://justfuckingusehtml.com" class="external">justfuckingusehtml.com<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - 29kb of pure HTML. No frameworks, no bundlers, no transpilers. Just HTML.</p>
<p>This whole lineup - not just a meme. It’s a manifesto.</p>
<h2 id="respect">Respect<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#respect" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Remember the satellite example? 612ms per RTT. Someone on an oil rig wants to visit a site - and it takes 5 seconds to load.</p>
<p>Not because the content is heavy.<br>
Because the developer didn’t give a shit.</p>
<p>Threw in 5 megabytes of JS, three 200kb fonts, PNG images instead of WebP, tracking, ads.</p>
<p>A small site is respect for the user. For their time. For their data plan. For their battery. For the fact that not everyone has fiber and 5G.</p>
<p><a href="https://hpbn.co/building-blocks-of-tcp/" class="external">Ilya Grigorik in HPBN<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> writes:</p>
<blockquote>
<p>No bit is faster than one that is not sent.</p>
</blockquote>
<p>It’s not about “make your site gray and dull”. It’s about “don’t drag a fuckload of kilobytes the user doesn’t need”.</p>
<p>By the way, the 512KB Club gives a Green Team badge to sites under 100kb. sccl.cc at 27kb raw would qualify. But I wanted more.</p>
<hr>
<h1 id="optimizing-scclcc">Optimizing sccl.cc<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#optimizing-scclcc" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>I have this little site <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Built on <a href="https://zine-ssg.io" class="external">Zine<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - an SSG in Zig. Three pages: contacts, projects, peripherals.</p>
<p><small>before migrating to Zine it was on Astro and weighed 150+kb, see <a href="../../en/it/Migration-from-Astro-to-Zine" class="internal" data-slug="en/it/Migration-from-Astro-to-Zine">Migration-from-Astro-to-Zine.md</a></small><br></p>
<p>What the server was originally sending:</p>
<ul>
<li>HTML (~15kb)</li>
<li>Space Mono + Martian Mono woff2 (~40kb)</li>
<li>Avatar PNG (~2mb)</li>
<li>Favicon PNG (~1mb)</li>
</ul>
<p><img src="../../pictures/it/50kb_scclcc.webp" width="auto" height="auto" alt="" loading="lazy"><br>
<small>screenshot is already a bit optimized, it was worse :skull:</small><br></p>
<p>Four requests per page for content that weighs practically nothing. And this is with keep-alive and HTTP/2 - setup once, but every new connection, every new tab - all over again.</p>
<p>Plan: everything in one HTML. Base64 inline all the things. Zero external requests. Fit in initcwnd with compression.</p>
<h2 id="font">Font<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#font" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The fattest thing. 2 fonts + bold variants = 40kb.</p>
<p>Honestly, custom fonts are fucking unnecessary most of the time. But aesthetics won - decided to keep one, stripping the file down to ASCII only.</p>
<p>Tool: <code>pyftsubset</code> from <a href="https://github.com/fonttools/fonttools" class="external">fonttools<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Strips out non-latin ranges, characters not used on the site.</p>
<p>Result: <strong>4.2kb</strong>, in base64 - 5.7kb of text in the HTML.</p>
<h2 id="avatar">Avatar<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#avatar" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Pixel art, rendered at 400%, PNG with alpha channel. Fat.</p>
<p>Shakalizer, three colors, PNG - 1.9kb. Convert to WebP - <strong>702 bytes</strong>.</p>
<p>Base64: <strong>940 bytes</strong>.</p>
<h2 id="favicon">Favicon<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#favicon" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Same tricks. 199 bytes in PNG (for tiny images it beats WebP).</p>
<p>Base64: <strong>268 bytes</strong>.</p>
<h2 id="js">JS<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#js" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Animated buttons, animated background, an easter egg with animation. All for looks. Actually useful stuff - almost none.</p>
<p>But I wanted to keep the functionality. Everything went inline. ~4kb of scripts the site would work fine without. But pretty.</p>
<h2 id="result">Result<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#result" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Before:</strong> 4 requests, ~50kb</p>
<p><strong>After:</strong> 1 request, <strong>12kb gzip</strong> / 27kb raw</p>
<p>Fits in initcwnd. One TCP data packet after the handshakes - and the site is ready.</p>
<p><img src="../../pictures/it/12kb_scclcc.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<hr>
<h1 id="the-inline-dilemma">The inline dilemma<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-inline-dilemma" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Inlining everything is a tradeoff. There’s a cost.</p>
<h2 id="caching">Caching<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#caching" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>A font in base64 inside HTML - doesn’t get cached. When navigating to another page, the browser downloads it again, because it’s part of the HTML.</p>
<p>With a separate file, the font would be cached after the first load. The second page would load without it.</p>
<h2 id="when-to-choose-what">When to choose what<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#when-to-choose-what" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Everything in one HTML (base64 fonts)</strong><br>
2 pages at 14kb each<br>
Each - 1 RTT<br>
But every time you eat the full 14kb, even if the font repeats</p>
<p><strong>HTML + separate font</strong><br>
2 pages at 8kb + 4kb font separate<br>
First visit: 2 requests<br>
Navigation: only 8kb, font cached</p>
<p>In the real internet, you won’t feel the difference between 14kb and 8kb - both fit in one initcwnd.</p>
<p>But delays between requests - you will.</p>
<h2 id="overhead">Overhead<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#overhead" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Inline weighs a bit more than an external dependency - base64 adds ~33% to binary data. If you’re already not exceeding initcwnd with external resources - you don’t fucking need inline.</p>
<p>It makes sense when the difference between “fit in one RTT” and “didn’t fit” is critical.</p>
<p>For sccl.cc the difference was dramatic: 4 requests vs 1, 50kb vs 12kb. Worth it.</p>
<hr>
<h1 id="bottom-line">Bottom line<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#bottom-line" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>14kb is not a magic number. It’s a consequence of TCP architecture, designed for reliability and fairness, not for first-load speed.</p>
<p>Understanding the transport layer gives you more than blindly following “the 14kb rule”. Sometimes you need inline, sometimes separate files with caching. Sometimes 30kb raw with gzip is almost the same as 14kb without.</p>
<p>But the philosophy remains: less is faster. And the less crap you drag onto your site, the more respect for the user.</p> ]]></description>
    <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Потолок 14kb: как TCP придумал small web</title>
    <link>https://shiza.sccl.cc/ru/it/14kb-cap</link>
    <guid>https://shiza.sccl.cc/ru/it/14kb-cap</guid>
    <description><![CDATA[ <h1 id="почему-14-kb">Почему 14 KB?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#почему-14-kb" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Вот в пример реальные запросы.</p>
<p><strong>Вот сайт который влезает в 14кб:</strong><br>
<img src="../../pictures/it/site_download.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p><strong>А вот который не влезает:</strong><br>
<img src="../../pictures/it/big_site_download.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Download - это и есть контент сайта.<br>
1 ms против 63 ms - разница скорости в 63 раза, при разнице веса всего 7 раз.</p>
<p>С хуя ли?</p>
<p>Ета писанина по верхам о потрохах TCP, DNS, TLS, slow start, congestion collapse 1986 года. И заодно филососия small web: почему маленький сайт - это уважение к пользователю, а не дрочка на циферки.</p>
<p><small>ну и немного про решения на sccl.cc в конце.</small><br></p>
<hr>
<h1 id="чотамкак-tcp">Чотамкак TCP<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#чотамкак-tcp" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Прежде чем сервер отдаст хоть байт HTML, происходит овердохуя всего.</p>
<h2 id="три-рукопожатия">Три рукопожатия<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#три-рукопожатия" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Любое TCP-соединение начинает с three-way handshake:</p>
<ol>
<li>Клиент шлёт <code>SYN</code></li>
<li>Сервер отвечает <code>SYN-ACK</code></li>
<li>Клиент добивает <code>ACK</code></li>
</ol>
<p>Три сообщения туда-сюда. И только потом можно слать данные.<br>
Это +1 RTT просто на то чтоб поздороваться.</p>
<p>Есть TCP Fast Open (TFO) - позволяет впихнуть данные в SYN-пакет. Но работает только для повторных соединений и требует поддержки с обеих сторон. В реальности редкость.</p>
<h2 id="congestion-collapse-1986">Congestion collapse 1986<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#congestion-collapse-1986" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>В начале 1986 интернет почти умер.</p>
<p>Количество узлов перевалило за 5000, bandwidth был неравномерным. TCP без контроля перегрузки просто забивал канал пакетами. Маршрутизаторы дропали, хосты перепосылали, дропали ещё больше - лавина.</p>
<p>Пропускная способность падала в 1000 раз. Сеть становилась нефункциональной.</p>
<p>Это назвали <strong>congestion collapse</strong>.</p>
<p>В 1988 Ван Якобсон (Van Jacobson) <a href="https://ee.lbl.gov/papers/congavoid.pdf" class="external">опубликовал статью “Congestion Avoidance and Control”<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> где описал четыре алгоритма, которые спасли интернет: slow-start, congestion avoidance, fast retransmit, fast recovery.</p>
<p>Именно эти четыре штуки стали обязательной частью TCP. И по сути предотвратили коллапс интернета в 80-х и 90-х, пока трафик рос экспоненциально.</p>
<h2 id="slow-start">Slow start<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#slow-start" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Идея простая: сервер не знает, какая пропускная способность у канала до клиента.</p>
<p>Если сразу вжарить 100 пакетов - можно забить сеть и получить тот самый congestion collapse. Поэтому начинаем с малого и разгоняемся.</p>
<p>Сервер держит переменную <strong>cwnd</strong> (congestion window) - сколько пакетов можно отправить не дожидаясь ACK.</p>
<p><strong>initcwnd</strong> - начальное значение окна:</p>
<ul>
<li>Изначально: 1 сегмент</li>
<li>RFC 3390: 4 сегмента</li>
<li>RFC 6928 (2013): <strong>10 сегментов</strong></li>
</ul>
<p>Google в 2013 <a href="https://datatracker.ietf.org/doc/html/rfc6928" class="external">продавил повышение до 10<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> через эксперименты, показав что хуже не становится. Некоторые CDN (Cloudflare, Fastly) <a href="https://www.cdnplanet.com/blog/initcwnd-settings-major-cdn-providers/" class="external">ставят 30 и больше<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Дальше экспоненциальный рост:</p>
<p>Сервер отправил 10 пакетов - ждёт ACK<br>
Пришёл ACK - окно удвоилось до 20<br>
Отправил 20 - ждёт<br>
Окно до 40, потом до 80…</p>
<p>Каждый такой цикл - это один RTT.<br>
Чем больше ответ, тем больше раундов.<br>
А если ответ влезает в initcwnd - улетает одним пакетом без ожидания - получаем один round trip.</p>
<p>На больших файлах (картинки, видео) это не проблема - TCP успевает разогнаться за несколько RTT. Но в большинстве случаев на сайтах файлы меньше 100kb - и вот тут начинается боль.</p>
<p>Например: файл на 100mb - первые пару RTT разгоняется, потом льёт на полной скорости. Эти первые RTT - капля в море.</p>
<p>Файл на 30kb - как раз попадает в зону, где slow start только начал разгоняться. Вместо одного RTT - два. Вместо ~50ms - ~100ms.</p>
<p>А если таких файлов дцать и каждый в новом соединении - каждый проходит этот путь заново.</p>
<hr>
<h1 id="кто-сколько-жрёт">Кто сколько жрёт<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#кто-сколько-жрёт" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Прежде чем сервер вообще дойдёт до отправки HTML - целый ритуал. Каждый этап - RTT.</p>
<p><strong>DNS: +1 RTT</strong><br>
Резолвинг домена. Обычно кешируется, т.ч. в большинстве случаев 0ms. Но cold lookup - честный RTT.</p>
<p><strong>TCP handshake: +1 RTT</strong><br>
SYN, SYN-ACK, ACK. Без вариантов.</p>
<p><strong>TLS: +2 RTT (или +1 с TLS 1.3)</strong><br>
TLS 1.2: 2 RTT. TLS 1.3: 1 RTT. С 0-RTT resumption можно и ваще 0, но это для повторных соединений.</p>
<p>Кста, сертификат может сам по себе весить несколько килобайт и не влезать в initcwnd. <a href="https://sirupsen.com/napkin/problem-15" class="external">Simon Hørup Eskildsen разбирал<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> датскую газету information.dk - сертификат 6908 байт, initcwnd был 3 (старый линукс). Сертификат не влез в окно, и TLS handshake занял не 2 а 3 RTT. Просто потому что сервер ждал ACK посреди отправки сертификата.</p>
<p><strong>Итого:</strong> минимум 3-4 RTT до первого байта HTML. И это мы ещё не начали слать контент. А когда начали - включается slow start.</p>
<h2 id="спутник">Спутник<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#спутник" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Есть <a href="https://endtimes.dev/why-your-website-should-be-under-14kb-in-size/" class="external">отличный пример<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> от Nathaniel с endtimes.dev:</p>
<p>Нефтяная платформа. Чуваки забыли кости для D&#x26;D и хотят зайти на missingdice.com через спутниковый интернет.</p>
<p>Путь пакета:</p>
<pre><code>телефон -> WiFi-роутер -> тарелка -> спутник (35 786 км) -> наземная станция -> сервер
</code></pre>
<p>Один RTT: <code>1ms (роутер) + 120ms (земля-спутник) + 120ms (спутник-земля) + 60ms (наземный-сервер)</code> - примерно 300ms в одну сторону.</p>
<p>Полный круг: <strong>612ms</strong>. И это один RTT.</p>
<p>С 4 RTT (DNS + TCP + 2xTLS) до первого байта: 2.5 секунды.<br>
С slow start на несколько раундов - все 4-5 секунд.</p>
<p>На сайт с костями.</p>
<p>ок?</p>
<hr>
<h1 id="магия-14-600">Магия 14 600<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#магия-14-600" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Считаем:</p>
<ul>
<li>MTU Ethernet: 1500 байт</li>
<li>Заголовок IP: 20 байт</li>
<li>Заголовок TCP: 20 байт</li>
<li>Полезная нагрузка: 1500 - 40 = <strong>1460 байт</strong> на пакет</li>
<li>initcwnd = 10 пакетов</li>
<li>10 x 1460 = <strong>14 600 байт</strong> = ~14.25 KB</li>
</ul>
<p>Вот откуда число.</p>
<h2 id="step-function">Step function<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#step-function" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Это не линейно.</p>
<p>14kb - 1 RTT.<br>
15kb - 2 RTT.<br>
29kb - 2 RTT.<br>
30kb - 3 RTT.</p>
<p>Каждый раз когда перепрыгиваешь границу окна - добавляется целый RTT. Разница между 14kb и 15kb может быть больше, чем между 15kb и 40kb.</p>
<p>Прикол в том, что HTML сжимается gzip-ом. 27kb raw легко превращаются в 12kb gzip. И 12kb - всё ещё в initcwnd, один RTT.</p>
<p><a href="https://14kbclub.com" class="external">14KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> считает <strong>uncompressed</strong>. Т.е. по правилам клуба надо влезть в 14kb сырым HTML.</p>
<p>Но в реальности сайт на 20-30kb raw с gzip почти не уступает “чистому” 14kb сайту. Разница - наносекунды на decompression.</p>
<p>Хотя кабута логичнее было бы считать compressed. Ну да ладно.</p>
<h2 id="а-http2-http3">А HTTP/2, HTTP/3?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#а-http2-http3" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Есть мнение что мультиплексирование убивает правило 14kb. Это не так.</p>
<p>HTTP/2 позволяет слать несколько запросов в одном TCP-соединении. Но TCP slow start никуда не делся - это свойство TCP, а не HTTP. Первый запрос всё равно проходит через slow start.</p>
<p>HTTP/3 (QUIC) работает поверх UDP. Но <a href="https://datatracker.ietf.org/doc/id/draft-ietf-quic-recovery-26.html" class="external">спецификация QUIC рекомендует тот же initcwnd в 10 пакетов<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Плюс HTTPS-рукопожатие и HTTP/2-префейс съедают часть начального окна. <a href="https://www.tunetheweb.com/blog/critical-resources-and-the-first-14kb/" class="external">Barry Pollard детально разбирал<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> что к моменту отправки HTML окно уже может быть больше 10 - ACKи слались во время TLS-переговоров. Т.ч. “ровно 14kb” - это упрощение.</p>
<p>Но механика та же. Понимать её полезнее чем дрочить на циферки.</p>
<hr>
<h1 id="small-web---это-не-про-килобайты">Small web - это не про килобайты<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#small-web---это-не-про-килобайты" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Тема маленьких сайтов - она не только про скорость. Она про философию.</p>
<h2 id="клубы">Клубы<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#клубы" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Есть целая экосистема: <a href="https://1mb.club" class="external">1MB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://512kb.club" class="external">512KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://250kb.club" class="external">250KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://14kbclub.com" class="external">14KB Club<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. И ещё всякие no-js.club, nocss.club.</p>
<p>512KB Club на главной <a href="https://512kb.club" class="external">пишет прямым текстом<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>:</p>
<blockquote>
<p>The internet has become a <strong>bloated mess</strong>. Huge JavaScript libraries, countless client-side queries and overly complex frontend frameworks are par for the course these days.</p>
</blockquote>
<p>И ведь правда.</p>
<p>NYT - газетный лейаут, текст и картинки - <a href="https://512kb.club/assets/images/nytimes-report.pdf" class="external">весит 9 мегабайт на 891 запросе<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, половина из которых JavaScript. Это пиздец.</p>
<p>Сайт, который по природе не может быть тяжёлым, зачем-то превращён в монстра. И ладно бы это был webgl-эксперимент. Это газета. Текст и картинки.</p>
<h2 id="motherfucking-website">Motherfucking website<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#motherfucking-website" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Если не видели <a href="https://motherfuckingwebsite.com" class="external">motherfuckingwebsite.com<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - сходите. Один HTML-файл, без CSS, без JS. Чёрный текст на белом фоне. 5kb.</p>
<p>Потом появился <a href="https://bestmotherfucking.website" class="external">bestmotherfucking.website<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - минимальный CSS, 78 байт инлайн. Потом <a href="https://thebestmotherfucking.website" class="external">thebestmotherfucking.website<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - уже с тёмной темой, но 132kb, кабута многовато.</p>
<p>Есть ещё <a href="https://justfuckingusehtml.com" class="external">justfuckingusehtml.com<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - 29kb чистого HTML. Никаких фреймворков, сборщиков, транспайлеров. Just HTML.</p>
<p>Вся эта линейка - не просто мем. Это манифест.</p>
<h2 id="уважение">Уважение<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#уважение" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Выше был пример со спутником - 612ms на RTT. Человек на нефтяной платформе хочет зайти на сайт - и сайт грузится 5 секунд.</p>
<p>Не потому что контент тяжёлый.<br>
Потому что разработчику было похуй.</p>
<p>Кинул 5 мегабайт JS, три шрифта по 200kb, картинки в PNG вместо WebP, трекинг, рекламу.</p>
<p>Маленький сайт - это уважение к пользователю. К его времени. К его трафику. К его батарейке. К тому что не у всех оптоволокно и 5G.</p>
<p><a href="https://hpbn.co/building-blocks-of-tcp/" class="external">Ilya Grigorik в HPBN<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> пишет:</p>
<blockquote>
<p>No bit is faster than one that is not sent.</p>
</blockquote>
<p>Это не про “сделать сайт серым и унылым”. Это про “не тащить лютые килобайты того, что пользователю нахуй не нужно”.</p>
<p>К слову, 512KB Club даёт звание Green Team сайтам до 100kb. sccl.cc со своими 27kb raw влез бы. Но хотелось большего.</p>
<hr>
<h1 id="оптимизация-scclcc">Оптимизация sccl.cc<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#оптимизация-scclcc" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>У меня есть сайтец <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Запилен на <a href="https://zine-ssg.io" class="external">Zine<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - SSG на Zig. Три страницы: contacts, projects, peripherals.</p>
<p><small>до переезда на Zine был Astro и сайт весил 150+кб, см <a href="../../ru/it/Migration-from-Astro-to-Zine" class="internal" data-slug="ru/it/Migration-from-Astro-to-Zine">Migration-from-Astro-to-Zine.md</a></small><br></p>
<p>Что сервер отдавал изначально:</p>
<ul>
<li>HTML (~15kb)</li>
<li>Space Mono + Martian Mono woff2 (~40kb)</li>
<li>Аватарка PNG (~2mb)</li>
<li>Фавиконка PNG (~1mb)</li>
</ul>
<p><img src="../../pictures/it/50kb_scclcc.webp" width="auto" height="auto" alt="" loading="lazy"><br>
<small>на скрине уже немного заоптемайзенный, было хуже :skull:</small><br></p>
<p>Четыре запроса на страницу с контентом, который по факту весит нихрена. И это с keep-alive и HTTP/2 - setup один раз, но каждое новое соединение, каждая вкладка - всё заново.</p>
<p>План: всё в один HTML. Base64 инлайн всего. Ноль внешних запросов. Влезть в initcwnd со сжатием.</p>
<h2 id="шрифт">Шрифт<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#шрифт" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Самое жирное. 2 шрифта + bold-версии = 40kb.</p>
<p>По-хорошему кастомные шрифты нахуй не нужны. Но эстетика победила - решено оставить один, выпилив из файла всё кроме ASCII.</p>
<p>Инструмент: <code>pyftsubset</code> из <a href="https://github.com/fonttools/fonttools" class="external">fonttools<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Выпиливаются нелатинские диапазоны, символы которых нет на сайте.</p>
<p>Итог: <strong>4.2kb</strong>, в base64 - 5.7kb текста в HTML.</p>
<h2 id="аватарка">Аватарка<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#аватарка" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Пиксельарт, зарендерен в 400%, PNG с альфа-каналом. Жирно.</p>
<p>Шаколизатор, три цвета, PNG - 1.9kb. Конвертация в WebP - <strong>702 байта</strong>.</p>
<p>Base64: <strong>940 байт</strong>.</p>
<h2 id="фавиконка">Фавиконка<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#фавиконка" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Те же манипуляции. 199 байт в PNG (для мелких картинок он лучше WebP).</p>
<p>Base64: <strong>268 байт</strong>.</p>
<h2 id="js">JS<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#js" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Анимированные кнопки, анимированный фон, пасхалка с анимацией. Всё для красоты. Реально полезного - кот наплакал.</p>
<p>Но функционал хотелось сохранить. Всё ушло в инлайн. ~4kb скриптов, без которых сайт работал бы не хуже. Но красиво.</p>
<h2 id="итог">Итог<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#итог" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>До:</strong> 4 запроса, ~50kb</p>
<p><strong>После:</strong> 1 запрос, <strong>12kb gzip</strong> / 27kb raw</p>
<p>Влезает в initcwnd. Один TCP-пакет данных после рукопожатий - и сайт готов.</p>
<p><img src="../../pictures/it/12kb_scclcc.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<hr>
<h1 id="дилемма-инлайна">Дилемма инлайна<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#дилемма-инлайна" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Инлайн всего - компромисс. Есть цена.</p>
<h2 id="кеширование">Кеширование<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#кеширование" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Шрифт в base64 внутри HTML - не кешируется. При переходе на другую страницу браузер качает его заново, потому что он часть HTML.</p>
<p>С отдельным файлом шрифт закешировался бы после первого раза. Вторая страница грузилась бы без него.</p>
<h2 id="когда-что-выбирать">Когда что выбирать<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#когда-что-выбирать" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Всё в одном HTML (base64 шрифты)</strong><br>
2 страницы по 14kb каждая<br>
Каждая - 1 RTT<br>
Но каждый раз жрёшь полные 14kb, даже если шрифт повторяется</p>
<p><strong>HTML + отдельный шрифт</strong><br>
2 страницы по 8kb + шрифт 4kb отдельно<br>
Первый заход: 2 запроса<br>
Переходы: только 8kb, шрифт закеширован</p>
<p>В реальном интернете разницу между 14kb и 8kb не почувствовать - оба влезают в один initcwnd.</p>
<p>А вот делей между запросами - да.</p>
<h2 id="overhead">Overhead<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#overhead" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Инлайн весит чуть больше внешней зависимости - base64 добавляет ~33% к бинарным данным. Если ты и так не превышаешь initcwnd с внешними ресурсами - инлайн нахуй не нужен.</p>
<p>Он имеет смысл когда разница между “влез в один RTT” и “не влез” критична.</p>
<p>В случае sccl.cc разница была драматической: 4 запроса против 1, 50kb против 12kb. Оно того стоило.</p>
<hr>
<h1 id="чо-в-итоге">Чо в итоге<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#чо-в-итоге" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>14kb - не magic number. Это следствие архитектуры TCP, которая проектировалась для надёжности и честности, а не для скорости первой загрузки.</p>
<p>Понимание транспортного уровня даёт больше чем слепое следование “правилу 14kb”. Где-то нужен инлайн, где-то отдельные файлы с кешированием. Где-то 30kb raw с gzip - почти то же что 14kb без сжатия.</p>
<p>Но философия остаётся: чем меньше - тем быстрее. И чем меньше говна тащишь на сайт, тем больше уважения к пользователю.</p> ]]></description>
    <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Self-hosted password manager on Cloudflare Workers</title>
    <link>https://shiza.sccl.cc/en/it/Self-hosted-password-manager</link>
    <guid>https://shiza.sccl.cc/en/it/Self-hosted-password-manager</guid>
    <description><![CDATA[ <h1 id="why">Why?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#why" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Options for storing passwords:</p>
<ol>
<li>In your head…</li>
<li>In a text file - <em>secure</em></li>
<li>Google Chrome / Firefox / … - <em>passwords stored who knows where</em></li>
<li>Bitwarden / 1Password / ProtonPass - <em>okay, but you gotta pay, and data on someone else’s servers</em></li>
<li>Your own server - <em>pog</em></li>
</ol>
<hr>
<h1 id="why-not-just-run-regular-bitwarden-on-a-vps">Why not just run regular Bitwarden on a VPS?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#why-not-just-run-regular-bitwarden-on-a-vps" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>You can. Bitwarden officially can be spun up with</p>
<pre><code>docker run bitwarden/self-host
</code></pre>
<p>But:</p>
<ul>
<li>Need a VPS - from $5/mo</li>
<li>Need Docker, need to make sure it doesn’t crash, need to update, need to backup</li>
<li>For one person - overkill, but cool</li>
</ul>
<p>What I want: free, nothing to admin, and it just works.</p>
<hr>
<h1 id="nodewarden---bitwarden-compatible-server-on-cloudflare-workers">NodeWarden - Bitwarden-compatible server on Cloudflare Workers<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#nodewarden---bitwarden-compatible-server-on-cloudflare-workers" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><img src="../../pictures/it/nodewarden.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p><a href="https://github.com/shuaiplus/nodewarden" class="external">NodeWarden<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - a fork of the Bitwarden server that runs directly on <strong>Cloudflare Workers</strong>.</p>
<p>So:</p>
<ul>
<li>Free (Workers - 100k requests/day)</li>
<li>Nothing to admin</li>
<li>Cloudflare updates the infrastructure itself</li>
<li>Database - Cloudflare D1 (free: 5 GB, 5 million rows read/mo)</li>
</ul>
<p>Compatible with all Bitwarden clients: browser extension, mobile app, CLI.</p>
<hr>
<h1 id="deployment">Deployment<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#deployment" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Briefly:</p>
<ol>
<li>Fork <a href="https://github.com/shuaiplus/nodewarden" class="external">shuaiplus/nodewarden<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</li>
<li>Clone locally: <code>git clone &#x3C;your-fork> &#x26;&#x26; cd &#x3C;your-repo> &#x26;&#x26; npm install</code></li>
<li>Edit <code>wrangler.toml</code>: <code>name</code>, <code>database_name</code> etc.</li>
<li>D1 database is created automatically on first deploy - wrangler will spin up <code>database_name</code> from config.</li>
<li>Add <code>JWT_SECRET</code>: Dashboard <span>→</span> your worker <span>→</span> Settings <span>→</span> Variables. Any string >=32 chars, e.g. <code>openssl rand -base64 32</code>.</li>
<li>Deploy: Cloudflare Dashboard <span>→</span> Workers &#x26; Pages <span>→</span> Create <span>→</span> Pages <span>→</span> Connect to GitHub, select your fork. Cloudflare pulls the code, builds and deploys on every push to <code>main</code>. Build command - <code>npm run build</code>, worker deploys alongside Pages. No GitHub Actions needed.</li>
</ol>
<p><strong>Important:</strong> Cloudflare has a free plan, but to create R2 you need to attach a credit card.</p>
<p>NodeWarden has a <code>deploy:kv</code> mode that uses KV instead of R2 (file attachments). Database is D1 in both cases - nothing to attach, fully free. (if you can, you can attach an empty card, but the free limits are more than enough)</p>
<hr>
<h1 id="is-it-safe">Is it safe?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#is-it-safe" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><em>overall: yes</em></p>
<p>In detail:</p>
<p>Under the hood it’s the standard Bitwarden end-to-end encryption scheme:</p>
<ul>
<li>Master password <span>→</span> KDF <span>→</span> master key (symmetric, AES-256). Lives only in the browser, never leaves.</li>
<li>Master key <span>→</span> HKDF <span>→</span> encryption key - encrypts all passwords, TOTP keys, notes. The server only sees encrypted garbage.</li>
<li>Additionally, an asymmetric key pair is generated: public key stored openly on the server, private key is encrypted with the encryption key and also sent to the server (needed for sync between devices).</li>
<li>Master password <span>→</span> KDF <span>→</span> client hash - sent to the server. The server salts it again (server hash) and stores only that.</li>
<li>The server cannot decrypt the vault. At all. It has no keys - only a hash for verifying the password at login.</li>
</ul>
<p>The server only stores encrypted data. Even if the database is stolen - the attacker gets useless data that can’t be cracked without the master key.</p>
<hr>
<h1 id="caveats">Caveats<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#caveats" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>There are nuances:</p>
<p><strong>Dependency on CF and GitHub</strong></p>
<p>Cloudflare <em>theoretically</em> could modify your worker if they get hacked or cf itself gets compromised.</p>
<p>How realistic that is - depends on your paranoia and hygiene.</p>
<hr>
<h1 id="features">Features<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#features" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>NodeWarden supports most Bitwarden features. Here’s what’s available:</p>
<h2 id="send">Send<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#send" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Create a temporary link with text or a file attached.</p>
<p>Burns after time or number of views.</p>
<p>You can optionally password-protect the link. Sends can be manually revoked at any time.</p>
<h2 id="backup-center">Backup Center<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#backup-center" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Schedule automatic backups.<br>
Backups are encrypted - without the master password they’re useless.</p>
<h2 id="totp-verification-codes">TOTP (Verification Codes)<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#totp-verification-codes" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Built-in TOTP generator - attach to a password and use instead of Google Authenticator or similar.</p>
<h2 id="passkeys">Passkeys<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#passkeys" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Replace the master password with a WebAuthn key.</p>
<h2 id="2fa-on-login">2FA on login<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#2fa-on-login" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Two-factor authentication at login.</p>
<p>But if someone steals the database + password, 2FA won’t help.</p>
<h2 id="api-key">API Key<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#api-key" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>For Bitwarden CLI and automation.</p>
<hr>
<h1 id="verdict">Verdict?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#verdict" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><strong>Pros:</strong></p>
<ul>
<li>Free</li>
<li>Nothing to admin</li>
<li>E2E encryption</li>
<li>Bitwarden compatibility (any client)</li>
<li>TOTP, Send, Passkeys, 2FA, Backup Center, API Key</li>
<li>Cloud backups (WebDAV/S3)</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Dependency on Cloudflare and GitHub</li>
<li>No password sharing between users (each sees only their own vault)</li>
</ul>
<p>A solid “set it and forget it” option for those who don’t want to pay for Bitwarden Premium, don’t want to give passwords to Google, and don’t want to admin a VPS.</p> ]]></description>
    <pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Self-hosted менеджер паролей на Cloudflare Workers</title>
    <link>https://shiza.sccl.cc/ru/it/Self-hosted-password-manager</link>
    <guid>https://shiza.sccl.cc/ru/it/Self-hosted-password-manager</guid>
    <description><![CDATA[ <h1 id="нахуя">Нахуя?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#нахуя" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Варианты хранить пароли:</p>
<ol>
<li>В голове…</li>
<li>В текстовом файле - <em>секурно</em></li>
<li>Google Chrome / Firefox / … - <em>пароли хранятся хрен пойми у кого</em></li>
<li>Bitwarden / 1Password / ProtonPass - <em>норм, но нада платить, и данные на чужих серверах</em></li>
<li>Свой сервер - <em>имба</em></li>
</ol>
<hr>
<h1 id="почему-не-поставить-обычный-bitwarden-на-vps">Почему не поставить обычный Bitwarden на VPS?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#почему-не-поставить-обычный-bitwarden-на-vps" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Можно. Bitwarden официально можно поднять через</p>
<pre><code>docker run bitwarden/self-host
</code></pre>
<p>Но:</p>
<ul>
<li>Нада VPS - от 300-500 руб/мес</li>
<li>Нада Docker, нада следить чтоб не упало, нада обновлять, нада бэкапить</li>
<li>Для одного человека - жирнo, но круто</li>
</ul>
<p>А хочется: бесплатно, ничего не админить, и чтоб работало.</p>
<hr>
<h1 id="nodewarden---bitwarden-совместимый-сервер-на-cloudflare-workers">NodeWarden - Bitwarden-совместимый сервер на Cloudflare Workers<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#nodewarden---bitwarden-совместимый-сервер-на-cloudflare-workers" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><img src="../../pictures/it/nodewarden.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p><a href="https://github.com/shuaiplus/nodewarden" class="external">NodeWarden<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - форк Bitwarden-сервера, который работает прямо на <strong>Cloudflare Workers</strong>.</p>
<p>То есть:</p>
<ul>
<li>Бесплатно ( Workers - 100к запросов/день )</li>
<li>Ничего не надо админить</li>
<li>Cloudflare сам апдейтит инфраструктуру</li>
<li>База данных - Cloudflare D1 (бесплатно: 5 ГБ, 5 млн строк чтения/мес)</li>
</ul>
<p>Совместим со всеми Bitwarden-клиентами: браузерное расширение, мобильное приложение, CLI.</p>
<hr>
<h1 id="разворачивание">Разворачивание<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#разворачивание" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Кратенько:</p>
<ol>
<li>Форкаешь <a href="https://github.com/shuaiplus/nodewarden" class="external">shuaiplus/nodewarden<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</li>
<li>Клонируешь локально: <code>git clone &#x3C;свой-форк> &#x26;&#x26; cd &#x3C;твоя-репа> &#x26;&#x26; npm install</code></li>
<li>Правишь <code>wrangler.toml</code>: <code>name</code>, <code>database_name</code> и т.д.</li>
<li>D1-база создаётся автоматом при первом деплое - wrangler сам поднимет <code>database_name</code> из конфига.</li>
<li>Добавляешь <code>JWT_SECRET</code>: Dashboard <span>→</span> свой воркер <span>→</span> Settings <span>→</span> Variables. Любая строка >=32 символов, например <code>openssl rand -base64 32</code>.</li>
<li>Деплой: Cloudflare Dashboard <span>→</span> Workers &#x26; Pages <span>→</span> Create <span>→</span> Pages <span>→</span> Connect to GitHub, выбираешь форк. Cloudflare сам забирает код, билдит и деплоит при каждом пуше в <code>main</code>. Билд-команда - <code>npm run build</code>, воркер деплоится вместе с Pages. Никаких GitHub Actions не нужно.</li>
</ol>
<p><strong>Важный момент:</strong> у Cloudflare бесплатный план, но чтобы создать R2 - нужна привязка карты.</p>
<p>В NodeWarden есть режим <code>deploy:kv</code>, который вместо R2 (файловые вложения) юзает KV. База данных в обоих случаях D1 - ниче привязывать ненад, фулл бесплатно. (если есть возможностость, можно и привязать пустую карту, но лимитов и так с головой)</p>
<hr>
<h1 id="это-безопасно">Это безопасно?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#это-безопасно" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><em>в целом: да</em></p>
<p>Подробнее:</p>
<p>Под капотом стандартная Bitwarden-схема end-to-end шифрования:</p>
<ul>
<li>Master password <span>→</span> KDF <span>→</span> master key (symmetric, AES-256). Живёт только в браузере, наружу не выходит никогда.</li>
<li>Master key <span>→</span> HKDF <span>→</span> encryption key - им шифруются все пароли, TOTP-ключи, заметки. Сервер видит только зашифрованный мусор.</li>
<li>Дополнительно генерится пара асимметричных ключей: public key хранится на сервере открыто, private key шифруется encryption key-ем и тоже улетает на сервер (нужен для синхра между устройствами).</li>
<li>Master password <span>→</span> KDF <span>→</span> client hash - улетает на сервер. Сервер солит его ещё раз (server hash) и хранит только его.</li>
<li>Сервер не может расшифровать vault. Вообще. У него нет ключей - только хэш для проверки пароля при логине.</li>
</ul>
<p>Сервер хранит только зашифрованные данные. Даже если базу стащат - у злоумышленника будут только бесполезные данные, которые без мастер ключа он не вскроет.</p>
<hr>
<h1 id="подводные-камни">Подводные камни<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#подводные-камни" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Есть нюансы:</p>
<p><strong>Зависмость от Cf и GitHub</strong></p>
<p>Со стороны Cloudflare <em>теоретически</em> может модифицировать твой worker, если ломанут git или сам cf.</p>
<p>Насколько это реально - вопрос к паранойе и твоей гигиене.</p>
<hr>
<h1 id="фишки">Фишки<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#фишки" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>NodeWarden поддерживает большинство фич Bitwarden. Вот чо есть:</p>
<h2 id="send">Send<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#send" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Можно создать временную ссылку, к которой можно приложить текст или файл.</p>
<p>Сгорает по времени или по количеству открытий.</p>
<p>Поверх ссылки можно навесить пароль (опционально). В любой момент Send можно отозвать вручную.</p>
<h2 id="backup-center">Backup Center<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#backup-center" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Можно настроить автоматические бекапы по расписанию.<br>
Бекапы шифрованные, без мастер-пароля с ними ниче не сделают.</p>
<h2 id="totp-verification-codes">TOTP (Verification Codes)<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#totp-verification-codes" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Встроенный TOTP-генератор - можно приписать к паролю и юзать вместо условного Google Authenticator</p>
<h2 id="passkeys">Passkeys<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#passkeys" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Можно заменить мастер-пароль на ключ доступа (WebAuthn).</p>
<h2 id="2fa-на-вход">2FA на вход<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#2fa-на-вход" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Двухфакторка при логине.</p>
<p>Но если кто-то утащит базу+пароль, то 2fa уже не поможет.</p>
<h2 id="api-key">API Key<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#api-key" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Для Bitwarden CLI и автоматизации</p>
<hr>
<h1 id="заебись">Заебись?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#заебись" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><strong>Плюсы:</strong></p>
<ul>
<li>Бесплатно</li>
<li>Ничего не надо админить</li>
<li>E2E шифрование</li>
<li>Bitwarden-совместимость (любой клиент)</li>
<li>TOTP, Send, Passkeys, 2FA, Backup Center, API Key</li>
<li>Бекапы в облако (WebDAV/S3)</li>
</ul>
<p><strong>Минусы:</strong></p>
<ul>
<li>Зависимость от Cloudflare и GitHub</li>
<li>Нет шеринга паролей между юзерами (каждый видит только свой vault)</li>
</ul>
<p>Хороший вариант “по<em>вставил и забыл</em>” для тех, кто не хочет платить за Bitwarden Premium, не хочет отдавать пароли гуглу, и не хочет админить VPS.</p> ]]></description>
    <pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>sccl.cc: bye Astro, hello Zig Zag Zine</title>
    <link>https://shiza.sccl.cc/en/it/Migration-from-Astro-to-Zine</link>
    <guid>https://shiza.sccl.cc/en/it/Migration-from-Astro-to-Zine</guid>
    <description><![CDATA[ <p>My site <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> was originally on <a href="https://astro.build" class="external">Astro<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Astro is a cool thing.<br>
For quickly whipping up something nice it’s fine, but over time it started pissing me off:</p>
<p><strong>Heavy runtime.</strong> To build a site you need Node.js, npm, <code>node_modules</code> at a hundred megabytes. For some static pages.</p>
<p><strong>Complexity.</strong> Astro does everything: islands, ssr, endpoints, middleware. And I have a site with three pages: contacts, projects, peripherals - I don’t need much.</p>
<h1 id="alternatives">Alternatives?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#alternatives" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>I stumbled upon <a href="https://zine-ssg.io" class="external">Zine<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Zine is an SSG in Zig. One binary, no dependencies.</p>
<p>Templates use SuperHTML - it’s valid html with templating added. No <code>{{ }}</code>, no Pug/Jade. Just html with attributes like <code>:text</code>, <code>:if</code>, <code>:loop</code>.</p>
<p>Content uses SuperMD - extended .md without needing to drop into html.</p>
<h1 id="migration-process">Migration process<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#migration-process" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Overall the migration was pretty simple - rewrite layouts from Astro to SuperHTML, port over existing scripts. The most tedious part is that astro uses its fucking <code>.astro</code> files with their weird mix of js and html, while SuperHTML is just html with extras.</p>
<h2 id="deploy">Deploy<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#deploy" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Zine is compiled into a standalone statically-linked binary. No dependencies, no runtimes - download and run. It’s not in <code>npm</code>, you can’t install it from any package manager. So you gotta deliver it to the runner somehow.</p>
<p>Options:</p>
<ul>
<li>download with curl on every build</li>
<li>commit to the repository</li>
</ul>
<p>First option is janky. Second sounds more reliable…</p>
<h3 id="stuff-it-in-the-repo">Stuff it in the repo<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#stuff-it-in-the-repo" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>First thought - just commit zine to the repo so it builds out of the box:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-light github-dark"><code data-language="bash" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /tmp/zine</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bin/zine</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bin/zine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> commit</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> push</span></span>
<span data-line=""><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> remote: error: File bin/zine is 152.19 MB; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">this</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> exceeds</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> GitHub's file size limit</span></span></code></pre></figure>
<p>And then you remember GitHub doesn’t allow files over 100 MB.</p>
<p><em>time to self-host git</em><br>
<small><em>maybe… later…</em></small><br></p>
<p>OK, rollback, pretend nothing happened. Check Zine docs - they recommend <code>kristoff-it/setup-zine@v1</code> in GitHub Actions.</p>
<h3 id="github-actions">GitHub Actions<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#github-actions" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>Throw <code>kristoff-it/setup-zine@v1</code> into gh actions:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="yaml" data-theme="github-light github-dark"><code data-language="yaml" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uses</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kristoff-it/setup-zine@v1</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  with</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v0.11.3</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">zine release</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uses</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">cloudflare/wrangler-action@v3</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  with</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    apiToken</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">${{ secrets.CLOUDFLARE_API_TOKEN }}</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    accountId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">${{ secrets.CLOUDFLARE_ACCOUNT_ID }}</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    command</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pages deploy public/ --project-name sccl-cc</span></span></code></pre></figure>
<p>Makes sense: action installs zine, zine builds, wrangler pushes to CF Pages.</p>
<p>The only secrets needed are <code>CLOUDFLARE_API_TOKEN</code> and <code>CLOUDFLARE_ACCOUNT_ID</code></p>
<p>(Permissions: <code>Account → Cloudflare Pages → Edit</code>).</p>
<p><em>Should’ve follow the docs, but I wanted to do it without gh actions.</em></p>
<h1 id="benefits">Benefits?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#benefits" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><strong>Speed.</strong> Build in milliseconds instead of seconds.</p>
<p><strong>Simplicity.</strong> No <code>node_modules</code>. No <code>package-lock.json</code>. No hundreds of dependencies. One binary, a content folder, a templates folder.</p>
<p><strong>Control.</strong> I know every line of generated HTML. No hidden JS that Astro adds by default.</p>
<h1 id="downsides">Downsides?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#downsides" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Zine is in beta.</p>
<p>Documentation is sparse in places. Community is small. If something breaks - you’re the first to notice.</p>
<p>But for a personal site it’s great.</p>
<p>One binary, a couple .smd files, one layout, and done.</p>
<p>How I ended up deploying this thing is described in <a href="../../en/it/How-to-ZINE-on-CF" class="internal" data-slug="en/it/How-to-ZINE-on-CF">How-to-ZINE-on-CF.md</a>.</p> ]]></description>
    <pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>sccl.cc: пока Astro, привет Zig Zag Zine</title>
    <link>https://shiza.sccl.cc/ru/it/Migration-from-Astro-to-Zine</link>
    <guid>https://shiza.sccl.cc/ru/it/Migration-from-Astro-to-Zine</guid>
    <description><![CDATA[ <p>Мой сайт <a href="https://sccl.cc" class="external">sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> изначально был на <a href="https://astro.build" class="external">Astro<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Astro - штука прикольная.<br>
Для быстрого клепания чего-нить красивого неплох, но со временем он меня начал бесить:</p>
<p><strong>Тяжёлый рантайм.</strong> Чтобы собрать сайт, надо тянуть Node.js, npm, <code>node_modules</code> на сотню мегабайт. Для одной статики.</p>
<p><strong>Сложность.</strong> Astro умеет всё: острова, ssr, эндпоинты, middleware. А у меня сайтец на три страницы: контакты, проекты, периферия, многого ненадо.</p>
<h1 id="альтернативы">Альтернативы?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#альтернативы" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Мне попался <a href="https://zine-ssg.io" class="external">Zine<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>.</p>
<p>Zine - это SSG на Zig. Один бинарник, без зависимостей.</p>
<p>Шаблоны на SuperHTML - это валидный html, в который добавили темплейтинг. Никаких <code>{{ }}</code>, никаких Pug/Jade. Просто html с атрибутами типа <code>:text</code>, <code>:if</code>, <code>:loop</code>.</p>
<p>Контент на SuperMD - расширенный .md без необходимости лезть в html.</p>
<h1 id="процесс-переезда">Процесс переезда<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#процесс-переезда" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>В целом переехать достачно просто - над переписать лэйауты с Astro на SuperHTML, перекинуть уже имеющеся скрипты.  Самое муторное эт, что astro юзает свои блядские <code>.astro</code> файлы со своей странной смесью js и html, а SuperHTML это просто html с плюхами.</p>
<h2 id="деплой">Деплой<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#деплой" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Zine скомпилен в standalone-бинарник со статической линковкой. Никаких зависимостей и рантаймов - скачал, запустил. В <code>npm</code> его нет, из пакетных менеджеров не установишь. Значит над как-то доставлять его на раннер.</p>
<p>Мысли:</p>
<ul>
<li>качать curl-ом при каждом билде</li>
<li>закоммитить в репозиторий</li>
</ul>
<p>Первый вариант - костыль. Второй звучит надёжнее…</p>
<h3 id="пихаем-в-репу">Пихаем в репу<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#пихаем-в-репу" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>Первая мысль была - закоммитить zine в репозиторий, чтоб билдилось из коробки:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-light github-dark"><code data-language="bash" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">cp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> /tmp/zine</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bin/zine</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bin/zine</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> &#x26;&#x26; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> commit</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> push</span></span>
<span data-line=""><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> remote: error: File bin/zine is 152.19 MB; </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">this</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> exceeds</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> GitHub's file size limit</span></span></code></pre></figure>
<p>И тут вспоминается, что GitHub не пускает больше 100 МБ.</p>
<p><em>пора хостить свой git</em><br>
<small><em>может быть… потом…</em></small><br></p>
<p>Ок, откатываем, делаем вид, что ничего не было. Лезем в доку Zine - там рекомендуют <code>kristoff-it/setup-zine@v1</code> в GitHub Actions.</p>
<h3 id="github-actions">GitHub Actions<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#github-actions" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>Пихаем <code>kristoff-it/setup-zine@v1</code> в gh actions:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="yaml" data-theme="github-light github-dark"><code data-language="yaml" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uses</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">kristoff-it/setup-zine@v1</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  with</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">v0.11.3</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">zine release</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">- </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">uses</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">cloudflare/wrangler-action@v3</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">  with</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    apiToken</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">${{ secrets.CLOUDFLARE_API_TOKEN }}</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    accountId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">${{ secrets.CLOUDFLARE_ACCOUNT_ID }}</span></span>
<span data-line=""><span style="--shiki-light:#22863A;--shiki-dark:#85E89D">    command</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">pages deploy public/ --project-name sccl-cc</span></span></code></pre></figure>
<p>Всё логично: экшен ставит zine, zine билдит сайт, wrangler пушит на cf pages.</p>
<p>Из секретов нужны только <code>CLOUDFLARE_API_TOKEN</code> и <code>CLOUDFLARE_ACCOUNT_ID</code></p>
<p>(права <code>Account → Cloudflare Pages → Edit</code>).</p>
<p><em>над было сразу по доке следовать, но хотелось изначально без gh actions делать</em></p>
<h1 id="плюшки">Плюшки?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#плюшки" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p><strong>Скорость.</strong> Билд за миллисекунды вместо секунд.</p>
<p><strong>Простота.</strong> Нет <code>node_modules</code>. Нет <code>package-lock.json</code>. Нет сотен зависимостей. Один бинарник, папка с контентом, папка с шаблонами.</p>
<p><strong>Контроль.</strong> Я знаю, что в каждой строчке сгенерированного HTML. Никакого скрытого JS, который Astro добавляет по умолчанию.</p>
<h1 id="минусы">Минусы?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#минусы" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Zine в бете.</p>
<p>Документация местами бедная. Сообщество маленькое. Если что-то сломалось - ты первый, кто это заметил.</p>
<p>Но для личного сайта это топ.</p>
<p>Один бинарник, пара .smd-файлов, один лэйаут и готово.</p> ]]></description>
    <pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Sakuli60lp</title>
    <link>https://shiza.sccl.cc/en/Peripheral/Keyboards/Sakuli60lp</link>
    <guid>https://shiza.sccl.cc/en/Peripheral/Keyboards/Sakuli60lp</guid>
    <description><![CDATA[ <blockquote class="callout info" data-callout="info">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>Under development... </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<p>Stage: Design.</p>
</div>
</div>
</blockquote>
<h3 id="guts-and-their-cost">Guts and their cost<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#guts-and-their-cost" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<ul>
<li>1x Dongle Nordic Nrf52840 with Oled screen :bongocat: - 1500 RUR</li>
<li>2x Controllers, Chinese product in the form of nice!nano v2, also on nrf52840 - 1300 rubles</li>
<li>58x Kailh 1350 hotswap sockets - ~500 RUR</li>
<li>58x Kailh Deep sea silent low profile switches - 2000 RUR</li>
<li>58x diodes 1N4148 - 100 rub.</li>
<li>2 batteries 503450 1000mah - 200 rubles (wholesale)</li>
<li>2x toggle switches - 10 rubles (wholesale)</li>
<li>screws-threads-wires - some pennies, because… are bought only in hundreds-thousands</li>
<li>connectors for batteries - 2 rubles (wholesale)</li>
<li>a couple of hundred grams of PETG plastic - 200 rubles?<br>
== ~&#x3C;6000 rub.</li>
</ul>
<ul>
<li>many man-hours of work on the development and assembly of various tools and equipment</li>
</ul>
<p>Crap without a pcb - hardcore handwired<br>
In general, I don’t see any point in making a board at all for mechanics; this is done only in large-scale production, because it’s faster and cheaper, but it doesn’t mean it’s better and of higher quality. Without a board, you can also screw up any keyboard shape, change the height and tilt of the buttons, etc.</p>
<p>Here is ZMK software<br>
Presumably the charge should last up to several months of daily work-gaming for 15 hours a day. (the calculator is generally hesit for 10 months, but let’s be realistic)</p> ]]></description>
    <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Scan Code &amp; Virtual Key: The Anglocentric Conspiracy of Operating Systems</title>
    <link>https://shiza.sccl.cc/en/it/os/Keyboard-layout-input-problem</link>
    <guid>https://shiza.sccl.cc/en/it/os/Keyboard-layout-input-problem</guid>
    <description><![CDATA[ <p>How did they manage to fuck up something as basic as keyboard input?</p>
<p>Seriously. A keyboard is what we interact with a computer through. Every day. For hours. For years. And the input system is still built on the assumption that everyone uses QWERTY.</p>
<p>Switched to Colemak? Hotkeys in games are broken. Switched to Rulemak? Aseprite has no idea what’s going on. Using non-QWERTY in some old program? Welcome to hell.</p>
<p>And it’s not a bug. It’s a feature. An anglocentric feature that’s 40 years old.</p>
<hr>
<h1 id="how-it-should-work">How it should work<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#how-it-should-work" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>There’s hardware. There are keys. Each key has a physical position.</p>
<p>A scan code is simply the number of that position. Pressed the key in the top left corner - got a scan code. Doesn’t matter what letter is painted on it. Doesn’t matter what language. It’s just “button number 16”.</p>
<p><strong>The right approach:</strong> a program (game, editor, whatever) listens to scan codes. Hotkey Ctrl+Z is bound to the physical position of the Z key - not the <code>Z</code> symbol, not the <code>VK_Z</code> virtual code, but the scan code. Then the hotkey works the same in any layout.</p>
<p><strong>What went wrong:</strong> somewhere in the late 80s, the industry decided it was more convenient to work with symbols and virtual codes. Because “well everyone uses QWERTY anyway, what’s the big deal”.</p>
<p>And ever since, every layer of abstraction added on top of scan codes has created problems for anyone not on QWERTY.</p>
<p><small>PS/2 keyboards by the way support three scan code sets: XT (set 1), AT (set 2), and 3270 (set 3). Modern keyboards send set 2, the motherboard controller translates to set 1. Live with this knowledge.</small><br></p>
<hr>
<h1 id="windows-three-layers-of-pain">Windows: three layers of pain<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#windows-three-layers-of-pain" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Keyboard input on Windows goes through three levels:</p>
<pre><code>scan_code -> virtual_key -> Symbol
</code></pre>
<p><strong>Scan code</strong> - the only sane thing. Assigned by the keyboard driver, doesn’t change with layout. Modern drivers can remap the matrix, but that’s rare.</p>
<p><strong>Virtual key</strong> - useless bullshit. Appeared back in Windows 3.1. The problem then was: there’s an app written for QWERTY, and a user in France is on AZERTY. Hotkeys don’t work. Microsoft’s solution: let’s create a layer that maps physical keys to their “QWERTY equivalent”. Like “what letter would be on this key if the layout were QWERTY”.</p>
<p>Originally the idea was reasonable. But vk should have died with Win16 applications.</p>
<p>It didn’t die.</p>
<p>It’s still in WinAPI. <code>GetKeyState(VK_CONTROL)</code>, <code>WM_KEYDOWN</code> with VK codes. All native Windows software sits on VK. And you can’t just ignore VK because the entire WinAPI works through it.</p>
<p><strong>Symbol</strong> - actual characters according to the layout. This part’s fine - you press a key with Shift/AltGr applied, you get a letter.</p>
<p>The problem is there are two abstraction layers between scan code and the application. And every app picks its own.</p>
<hr>
<h1 id="games-every-engine-jerks-off-in-its-own-way">Games: every engine jerks off in its own way<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#games-every-engine-jerks-off-in-its-own-way" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="directinput">DirectInput<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#directinput" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>This fucking outdated DirectInput maps by VK.</p>
<p>It’s an API from the 90s. Microsoft themselves <a href="https://learn.microsoft.com/en-us/windows/win32/directinput/directinput-portal" class="external">declared it deprecated<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> back in 2005. They recommend XInput for gamepads and Raw Input for keyboards.</p>
<p>But it’s still alive. Some games are still on it. Everything on DirectInput is garbage for non-QWERTY layouts.</p>
<h2 id="unity">Unity<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#unity" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Fucking Unity invented their own Input Manager. <code>Input.getKey</code> is anally tied to Symbol.</p>
<p>That means a hotkey is bound not to a key, but to whatever letter that key produces in the CURRENT layout. Switch the layout - hotkeys are gone.</p>
<p>Supposedly they updated the input system to the “New Input System”. Now you can grab keys through scan codes like <code>Keyboard.current.wKey</code>. But shit still happens with custom layouts in some situations anyway.</p>
<p>So they sort of fixed it, but not completely. Classic.</p>
<h2 id="unreal-engine">Unreal Engine<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#unreal-engine" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>In UE, if the dev doesn’t slam that “use scan code” checkbox - input will be through VK.</p>
<p>Default is VK. Meaning every Unreal game is potentially broken. Depends on whether the dev gave a shit.</p>
<h2 id="source-engine">Source Engine<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#source-engine" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>But grandpa GabeN in Source (TF2, CS2, Portal…) uses scan codes for movement input.</p>
<p>And it just works. On any layout. Without rebinding.</p>
<p><small>no matter how much you hate Valve - they do know how to do some things right.</small><br></p>
<hr>
<h1 id="good-examples">Good examples<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#good-examples" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Besides Source, there are decent folks out there.</p>
<p><strong>Minecraft.</strong> Up until somewhere around 1.9-1.12 (don’t remember exactly) Minecraft had different input code - everything was bound to VK.</p>
<p>So if your layout wasn’t QWERTY (and in some layouts the vk differs, like French and German) - you had to rebind. Every time. Every update.</p>
<p>Then somewhere around 1.12+ they rewrote it to scan codes. And everything started working beautifully. Because the physical W key is always the physical W key, no matter what letter your layout says it is.</p>
<p><em>minecraft bedrock can go fuck itself, we’ve got DirectInput yo</em></p>
<hr>
<h1 id="software-aseprite-and-the-rest">Software: Aseprite and the rest<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#software-aseprite-and-the-rest" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>And fucking Aseprite, goddammit, uses Symbol.</p>
<p>You’re drawing pixel art. You switch to a Russian layout (or any non-QWERTY). And the hotkeys fly off to god knows where.</p>
<p>Because <code>B</code> in the Russian layout is <code>И</code>. And Aseprite thinks “aha, the И key was pressed, let me check my hotkey table… nope, no hotkey for И”.</p>
<p>Even though physically you pressed the same key.</p>
<p>Lots of programs do this. Aseprite is just the most painful example - its whole workflow is built on hotkeys.</p>
<p>Everything else goes through WinAPI - meaning VK. And VK maps to the “QWERTY equivalent”. On some non-QWERTY layouts (French AZERTY, German QWERTZ) this equivalent differs from actual QWERTY. And hotkeys go to shit.</p>
<p>Decent folks use scan codes. They’re the minority.</p>
<hr>
<h1 id="linux-its-own-set-of-rakes">Linux: its own set of rakes<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#linux-its-own-set-of-rakes" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Linux also shat the bed, and arguably even harder.</p>
<p>For starters, there are three abstraction layers before you get a symbol.</p>
<p><strong>Hardware scan code.</strong> The keyboard (PS/2 or USB HID) sends a raw scan code. The kernel receives it.</p>
<p><strong>linux_keycode.</strong> The kernel digests the scan code into things like <code>KEY_Q</code>, <code>KEY_W</code>, <code>KEY_E</code>. This is no longer a physical number, it’s a “QWERTY position”. At this level you’ve already lost the pure scan code.</p>
<p>That is, the Linux kernel immediately makes an anglocentric assumption.</p>
<p><strong>XKB.</strong> X Keyboard Extension takes whatever the kernel shat out (<code>KEY_Q</code>, <code>KEY_W</code>…) and shits out its own positions: <code>&#x3C;AD01></code>, <code>&#x3C;AD02></code> … <code>&#x3C;AB01></code>, <code>&#x3C;AB02></code>.</p>
<p>And XKB maps these coordinates directly to symbols (Keysyms). Like <code>&#x3C;AD01></code> = <code>q</code>, and in the Russian layout = <code>Cyrillic_shorti</code> (й).</p>
<p>The Linux problem is that if a program listens to linux_keycode directly (and many do, especially old X11 programs and some games through SDL), you get the same problems as with VK on Windows.</p>
<p>But if a program listens to XKB Keysyms - it gets the final characters. And if it binds hotkeys to characters rather than scan codes - broken again.</p>
<hr>
<h1 id="what-to-do">What to do<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#what-to-do" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Programmers: bind input to scan codes. Always. A key’s physical position doesn’t change with the layout.</p>
<p>Game devs: take a look at Source(Quake(Doom)). It was all figured out 30+ years ago.</p>
<p>Non-QWERTY users: suffer. Or file bug reports tagged “keyboard layout”. Or build your own tools to work around this crap.</p>
<hr>
<p><em>Maybe, someday, operating systems will start supporting languages other than English.</em></p>
<p><em>But apparently not yet.</em></p> ]]></description>
    <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Sakuli60lp</title>
    <link>https://shiza.sccl.cc/ru/Peripheral/Keyboards/Sakuli60lp</link>
    <guid>https://shiza.sccl.cc/ru/Peripheral/Keyboards/Sakuli60lp</guid>
    <description><![CDATA[ <blockquote class="callout info" data-callout="info">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>В процессе разработки... </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<p>Этап: Проектирование.</p>
</div>
</div>
</blockquote>
<h3 id="кишки-и-их-себестоимость">Кишки и их себестоимость<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#кишки-и-их-себестоимость" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<ul>
<li>1х Донгл Nordic Nrf52840 с Oled экраном :bongocat: - 1500 руб</li>
<li>2x Контроллеры, китай поделие в форме nice!nano v2, тоже на nrf52840 - 1300 руб</li>
<li>58х Kailh 1350 hotswap сокеты - ~500 руб</li>
<li>58х Свитчи Kailh Deep sea silent low profile - 2000 руб</li>
<li>58х диоды 1N4148 - 100 руб</li>
<li>2х аккумуляторы 503450 1000mah - 200 руб (оптом)</li>
<li>2x тумблера - 10 рублей (оптом)</li>
<li>винтики-резьбы-провода - какие-то копейки, т.к. покупаются только сотнями-тысячами</li>
<li>коннекторы для акб - 2 руб (оптом)</li>
<li>пару сотен грамм PETG пластика - 200 руб?<br>
== ~&#x3C;6000 руб</li>
</ul>
<ul>
<li>много человеко-часов работы на разработку и сброку, разных инструментов и оборудования</li>
</ul>
<p>Поделие без платы - хардкорный handwired<br>
В целом в механиках не вижу смысла вооще изготавливать плату, это делают только при масштабном производстве, ибо так тупа быстрее и дешевле, но не значит лучше и качественнее. Без платы так же можно нахуячить любую форму клавы, менять высоту и наклон кнопок и т.д</p>
<p>Софт туты ZMK<br>
Предположительно заряда должно хватать до нескольких месяцев ежедневного работы-гейминга по 15ч в сутки. (калькулятор вообще под 10 месяцев гессит, но будем реалистами)</p> ]]></description>
    <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Scan Code &amp; Virtual Key: Англоцентричный заговор операционных систем</title>
    <link>https://shiza.sccl.cc/ru/it/os/Keyboard-layout-input-problem</link>
    <guid>https://shiza.sccl.cc/ru/it/os/Keyboard-layout-input-problem</guid>
    <description><![CDATA[ <p>Как умудрились насрать в такую базовую вещь, как ввод с клавиатуры?</p>
<p>Серьёзно. Клавиатура - это то, через что мы взаимодействуем с компом каждый день. Часами. Годами. И система ввода до сих пор завязана на предположении что все сидят на QWERTY.</p>
<p>Поменял раскладку на Colemak? Хоткеи в играх сломались. Переключился на Rulemak? Aseprite не понимает что происходит. Юзаешь не-QWERTY в какой-нибудь старой проге? Добро пожаловать в ад.</p>
<p>И это не баг. Это фича. Англоцентричная фича, которой 40 лет.</p>
<hr>
<h1 id="как-должно-работать">Как должно работать<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#как-должно-работать" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Есть железо. Есть кнопки. У каждой кнопки есть физическая позиция.</p>
<p>Scan code - это просто номер этой позиции. Нажал клавишу в левом верхнем углу - получил scan code. Не важно какая там буква нарисована. Не важно какой язык. Это просто “кнопка номер 16”.</p>
<p><strong>Правильный подход:</strong> программа (игра, редактор, что угодно) слушает scan codes. Хоткей Ctrl+Z привязан к физической позиции кнопки Z - не к символу <code>Z</code>, не к виртуальному коду <code>VK_Z</code>, а к scan code. Тогда при любой раскладке хоткей работает одинаково.</p>
<p><strong>Что пошло не так:</strong> где-то в конце 80-х индустрия решила что удобнее работать с символами и виртуальными кодами. Потому что “ну все же на QWERTY сидят, чо там”.</p>
<p>И с тех пор каждый слой абстракции, добавленный поверх scan code, создавал проблемы для всех кто не на QWERTY.</p>
<p><small>PS/2 клавиатуры кстати поддерживают три сета scan codes. XT (set 1), AT (set 2), и 3270 (set 3). Современные клавы шлют set 2, контроллер на материнке транслирует в set 1. Живити с этой информацией.</small><br></p>
<hr>
<h1 id="windows-три-слоя-боли">Windows: три слоя боли<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#windows-три-слоя-боли" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Ввод с клавиатуры в Windows проходит три уровня:</p>
<pre><code>scan_code -> virtual_key -> Symbol
</code></pre>
<p><strong>Scan code</strong> - единственная адекватная вещь. Присваивается драйвером клавиатуры, не меняется от раскладки. В современных дровах можно ремапить матрицу, но это редкость.</p>
<p><strong>Virtual key</strong> - бесполезное говно. Появилось ещё в Windows 3.1. Тогда проблема была такая: есть приложение написанное под QWERTY, а юзер во Франции сидит на AZERTY. Хоткеи не работают. Решение Microsoft: давайте создадим прослойку которая мапит физическую кнопку на “QWERTY-эквивалент”. Типа “какая буква была бы на этой кнопке если бы раскладка была QWERTY”.</p>
<p>Изначально идея была здравая. Но vk должен был умереть вместе с Win16 приложениями.</p>
<p>Он не умер.</p>
<p>Он в WinAPI до сих пор. <code>GetKeyState(VK_CONTROL)</code>, <code>WM_KEYDOWN</code> с VK-кодами. Весь нативный Windows-софт сидит на VK. И ты не можешь просто взять и игнорить VK, потому что весь WinAPI через него работает.</p>
<p><strong>Symbol</strong> - уже конкретные символы в соответствии с раскладкой. Тут всё ок - нажал кнопку с учётом Shift/AltGr, получил букву.</p>
<p>Проблема в том что между scan code и приложением два уровня абстракции. И каждое приложение выбирает свой.</p>
<hr>
<h1 id="игры-каждый-дрочит-как-хочет">Игры: каждый дрочит как хочет<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#игры-каждый-дрочит-как-хочет" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="directinput">DirectInput<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#directinput" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Ебучий устаревший DirectInput мапит по VK.</p>
<p>Это API из 90-х. Microsoft сами <a href="https://learn.microsoft.com/en-us/windows/win32/directinput/directinput-portal" class="external">признали его устаревшим<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> ещё в 2005. Рекомендуют XInput для геймпадов и Raw Input для клавиатур.</p>
<p>Но он до сих пор жив. Некоторые игры до сих пор на нём. Всё что на DirectInput - всё говно для не-QWERTY раскладок.</p>
<h2 id="unity">Unity<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#unity" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Сраный Unity придумал свой Input Manager. <code>Input.getKey</code> анально привязан к Symbol.</p>
<p>То есть хоткей привязан не к кнопке, а к букве которую эта кнопка вводит в ТЕКУЩЕЙ раскладке. Переключил раскладку - хоткеи улетели.</p>
<p>Вродь они обновили систему инпута на “New Input System”. Теперь можно хапать клавиши через scan codes типа <code>Keyboard.current.wKey</code>. Но серавно в каких-то ситуациях происходит хуйня с кастомными раскладками.</p>
<p>То есть они типа починили, но не до конца. Классика.</p>
<h2 id="unreal-engine">Unreal Engine<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#unreal-engine" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>В UE если разраб не въебнёт галочку “use scan code” - инпут будет через VK.</p>
<p>По дефолту VK. То есть каждая игра на Unreal потенциально сломана. Зависит от того, заморачивался ли разраб.</p>
<h2 id="source-engine">Source Engine<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#source-engine" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>А вот дядушка Габен в сурсе (TF2, CS2, Portal…) юзает scan codes для ввода движения.</p>
<p>И оно просто работает. На любой раскладке. Без перенастроек.</p>
<p><small>сколько не хейти valve - что-то да хорошее делать они умеют.</small><br></p>
<hr>
<h1 id="хорошие-примеры">Хорошие примеры<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#хорошие-примеры" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Кроме сурса есть нормальные ребята.</p>
<p><strong>Minecraft.</strong> Где-то до 1.9-1.12 (точно не помню) в майне был другой код инпута - всё привязывалось к VK.</p>
<p>То есть если у тебя раскладка не QWERTY (а в некоторых раскладках vk отличается, например во французской и немецкой) - тебе приходилось ребиндить. Каждый раз. При каждом обновлении.</p>
<p>Потом в районе 1.12+ переписали на scan codes. И всё стало пиздато работать. Потому что физическая кнопка W - это всегда физическая кнопка W, не важно какая там буква в твоей раскладке.</p>
<p><em>в миникрафт бедроке пошли нахуй, у нас DirectInput йоу</em></p>
<hr>
<h1 id="софт-aseprite-и-остальные">Софт: Aseprite и остальные<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#софт-aseprite-и-остальные" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>А ебучий Aseprite блять по Symbol хуярит.</p>
<p>Ты рисуешь пиксельарт. Переключился на русскую раскладку (или любую другую не-QWERTY). И хоткеи улетают куда-то хуй пойми куда.</p>
<p>Потому что <code>B</code> в русской раскладке это <code>И</code>. И Aseprite думает “ага, нажата кнопка И, смотрю в своей таблице хоткеев… а там нет хоткея на И”.</p>
<p>Хотя физически ты нажал ту же кнопку.</p>
<p>Так многие проги. Aseprite просто самый больной пример - в нём работа завязана на хоткеи.</p>
<p>В остальном весь софт ходит через WinAPI - соответственно VK. А VK мапится на “QWERTY-эквивалент”. На некоторых не-QWERTY раскладках (французская AZERTY, немецкая QWERTZ) этот эквивалент отличается от QWERTY. И хоткеи летят лесом.</p>
<p>Нормальные ребята юзают scan codes. Их меньшинство.</p>
<hr>
<h1 id="linux-свои-грабли">Linux: свои грабли<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#linux-свои-грабли" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>В Linux тоже насрали, и кабуда даже сильнее.</p>
<p>Начнём с того, что там три слоя абстракции до символа.</p>
<p><strong>Аппаратный scan code.</strong> Клавиатура (PS/2 или USB HID) шлёт сырой scan code. Ядро получает.</p>
<p><strong>linux_keycode.</strong> Ядро переваривает scan code в условные <code>KEY_Q</code>, <code>KEY_W</code>, <code>KEY_E</code>. Это уже не физический номер, а “QWERTY-позиция”. То есть на этом уровне ты уже потерял чистый scan code.</p>
<p>То есть Linux kernel сразу же делает англоцентричное предположение.</p>
<p><strong>XKB.</strong> X Keyboard Extension берёт что ядро высрало (<code>KEY_Q</code>, <code>KEY_W</code>…) и высерает свои позиции: <code>&#x3C;AD01></code>, <code>&#x3C;AD02></code> … <code>&#x3C;AB01></code>, <code>&#x3C;AB02></code>.</p>
<p>И уже эти координаты XKB мапит напрямую в символы (Keysyms). Типа <code>&#x3C;AD01></code> = <code>q</code>, а в русской раскладке = <code>Cyrillic_shorti</code> (й).</p>
<p>Проблема линукса в том, что если прога слушает linux_keycode напрямую (а многие так делают, особенно старые X11 проги и некоторые игры через SDL), ты получаешь те же проблемы что и с VK в Windows.</p>
<p>Но если прога слушает XKB Keysyms - она получает уже окончательные символы. И если она привязывает хоткеи к символам а не к scan codes - опять слом.</p>
<hr>
<h1 id="что-делать">Что делать<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#что-делать" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Программистам: биндите инпут на scan codes. Всегда. Физическая позиция кнопки не меняется от раскладки.</p>
<p>Разрабам игр: загляните в Source(Quake(Doom)). Всё за вас придумали 30+ лет назад.</p>
<p>Пользователям не-QWERTY раскладок: страдать. Или писать баг-репорты с тегом “keyboard layout”. Или делать свои инструменты для обхода этой хуйни.</p>
<hr>
<p><em>Может, когда-нибудь, операционные системы начнут поддерживать другие языки, помимо английского.</em></p>
<p><em>Но видимо пока рано.</em></p> ]]></description>
    <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Outemu Switches</title>
    <link>https://shiza.sccl.cc/en/Peripheral/Keyboards/Switches/Outemu</link>
    <guid>https://shiza.sccl.cc/en/Peripheral/Keyboards/Switches/Outemu</guid>
    <description><![CDATA[ <p>I had a chance to test 38 switches, so let’s go through each one:</p>
<p>Sound comparison can be seen in the video</p>
<iframe width="100%" height="400" src="https://www.youtube.com/embed/roa0XXvA1UU" title="38 Outemu Keyboard Switches Sound Test Comparison" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>
</iframe>
<h1 id="tierlist">Tierlist<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#tierlist" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>🔴 - Linear</p>
<p>🟡 - Tactile</p>
<p>🔵 - Clicky</p>





































<table><thead><tr><th align="left">Rank</th><th align="left">Switches</th></tr></thead><tbody><tr><td align="left"><strong>S</strong></td><td align="left">🟡 Silent Jade Yellow • 🔴 Cold Plum • 🔴 Jade Pink</td></tr><tr><td align="left"><strong>A</strong></td><td align="left">🟡 Cream Yellow • 🔴 Cream Pink • 🔴 Silent Peach V2 • 🔴 Pink Lotus • 🔴 Hanny • 🔴 Lanny</td></tr><tr><td align="left"><strong>B</strong></td><td align="left">🟡 Silent Lemon V2 • 🟡 Maple Leaf • 🔴 Milk Peach • 🔴 Bai Lan</td></tr><tr><td align="left"><strong>C</strong></td><td align="left">🟡 Red Panda • 🟡 Milk Tea • 🔴 Silent Peach V1 • 🔴 Silent White • 🔴 Silent Ocean</td></tr><tr><td align="left"><strong>D</strong></td><td align="left">🟡 Silent Lemon V1 • 🔴 Dustproof Silver • 🔴 Crystal Linear • 🟡 Outemu Silent Tom</td></tr><tr><td align="left"><strong>F</strong></td><td align="left">🔴 Silver • 🔴 Black • 🔴 Red • 🟡 Brown • 🟡 Orange • 🟡 Purple</td></tr><tr><td align="left"><strong>F-</strong></td><td align="left">🔵 Blue, Green, Gold, Ocean Clicky, Spring Breeze, Milk Blue, Jerry Clicky, Cream Blue, Jade Blue</td></tr></tbody></table>
<hr>
<h1 id="description-of-each">Description of each<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#description-of-each" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="linears">[LINEARS]<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#linears" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Outemu Silent White</strong> - The simplest quiet switch, a little sandy, but for the first generation it will do if you have an ultra low-budget build.</li>
<li><strong>Outemu Silver</strong> - Standard slag, fast, with short stroke.</li>
<li><strong>Outemu Black</strong> - Standard slag, weighted spring.</li>
<li><strong>Outemu Red</strong> - Standard slag, basic.</li>
<li><strong>Outemu Silent Peach V1</strong> - It was ok, but now there is something better. Quiet, soft.</li>
<li><strong>Outemu Silent Peach V2</strong> - Good ultra-budget quiet switch.</li>
<li><strong>Outemu Cold Plum</strong> - Winter collection. Full nylon body, short stroke, heavy spring. Thick thocky sound.</li>
<li><strong>Outemu Pink Lotus</strong> - Summer collection. Lightweight, short stroke, smooth.</li>
<li><strong>Outemu Milk Peach</strong> - Budget regular switch, nice sound, but Cream and Jade are better.</li>
<li><strong>Outemu Bai Lan</strong> - Average.</li>
<li><strong>Outemu Hanny</strong> - Long-pole stem. Loud clacky switch (lego movie sound).</li>
<li><strong>Outemu Lanny</strong> - Similar to Hanny, but a little more balanced and ordinary.</li>
<li><strong>Outemu Silent Ocean</strong> - Not a bad quiet switch.</li>
<li><strong>Outemu Crystal Linear</strong> - Completely made of polycarbonate. Dry and rattling.</li>
<li><strong>Outemu Dustproof Silver</strong> - Slightly better than regular silver, but still so-so.</li>
<li><strong>Outemu Cream Pink</strong> - Well lubricated, buttery action, deep sound (but Jade is better).</li>
<li><strong>Outemu Jade Pink</strong> - A cooler version of Cream Pink, with a faster spring.</li>
</ul>
<hr>
<h2 id="tactiles">[TACTILES]<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#tactiles" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Outemu Brown</strong> - Absolute crap. There is no tactility, it feels like a dirty sand ruler with a ringing spring.</li>
<li><strong>Outemu Orange</strong> - A little heavier than Brown, but the same dry trash with a ringing sound.</li>
<li><strong>Outemu Purple</strong> - Old heavy tactile. The bump is sharp and unpleasant, the spring rings harshly on return.</li>
<li><strong>Outemu Red Panda</strong> - Passable clone of Holy Panda. A rounded bump at the start, but without manual lubrication it’s a so-so idea.</li>
<li><strong>Outemu Maple Leaf</strong> - Not a bad ringing tactile. The bump is clear, feels fast, and has a clacky sound.</li>
<li><strong>Outemu Milk Tea</strong> - Budget average. Due to the milky casing, the sound is a little softer, but the tactility is weak.</li>
<li><strong>Outemu Silent Jade Yellow</strong> - Top silent tactile device. A clear bump at the start, not wadded at all, lubricated normally. Close to Gazzew Boba u4, but softer.</li>
<li><strong>Outemu Cream Yellow</strong> - Tactile analogue of Cream Pink. Good lubrication, soft dull sound, pleasant running. Also close to u4.</li>
<li><strong>Outemu Silent Tom</strong> - Old silent tactile. The hillock is indistinct and blurred, no longer relevant.</li>
<li><strong>Outemu Silent Grey</strong> - Ancient cotton shit. There is almost no bump, when pressed there is a complete feeling of rubber.</li>
<li><strong>Outemu Silent Lemon V1</strong> - Was a popular quiet switch, but is very sandy.</li>
<li><strong>Outemu Silent Lemon V2</strong> - Work on bugs V1. It has become much smoother, normal quiet tactile.</li>
</ul>
<hr>
<h2 id="clickies">[CLICKIES]<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#clickies" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Outemu Blue</strong>, <strong>Outemu Green</strong>, <strong>Outemu Gold</strong>, <strong>Outemu Ocean Clicky</strong>, <strong>Outemu Spring Breeze</strong>, <strong>Outemu Milk Blue</strong>, <strong>Outemu Jerry Clicky</strong>, <strong>Outemu Cream Blue</strong>, <strong>Outemu Jade Blue</strong> - Total shit without exception. Due to the cheap design of the Click Jacket, everything inside is loose and rattling. A nasty plastic crunch instead of a normal click.</li>
</ul> ]]></description>
    <pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Outemu Switches</title>
    <link>https://shiza.sccl.cc/ru/Peripheral/Keyboards/Switches/Outemu</link>
    <guid>https://shiza.sccl.cc/ru/Peripheral/Keyboards/Switches/Outemu</guid>
    <description><![CDATA[ <p>Мне довелось потестить 38 свитчей отуему, пройдёмся по каждому:</p>
<p>Звуковое сравнение можно посмотреть в видосике</p>
<iframe width="100%" height="400" src="https://www.youtube.com/embed/roa0XXvA1UU" title="38 Outemu Keyboard Switches Sound Test Comparison" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>
</iframe>
<h1 id="тирлист">Тирлист<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#тирлист" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>🔴 - Linear</p>
<p>🟡 - Tactile</p>
<p>🔵 - Clicky</p>





































<table><thead><tr><th align="left">Ранг</th><th align="left">Свитчи</th></tr></thead><tbody><tr><td align="left"><strong>S</strong></td><td align="left">🟡  Silent Jade Yellow • 🔴  Cold Plum • 🔴  Jade Pink</td></tr><tr><td align="left"><strong>A</strong></td><td align="left">🟡  Cream Yellow • 🔴  Cream Pink • 🔴  Silent Peach V2 • 🔴  Pink Lotus • 🔴  Hanny • 🔴  Lanny</td></tr><tr><td align="left"><strong>B</strong></td><td align="left">🟡  Silent Lemon V2 • 🟡  Maple Leaf • 🔴  Milk Peach • 🔴  Bai Lan</td></tr><tr><td align="left"><strong>C</strong></td><td align="left">🟡  Red Panda • 🟡  Milk Tea • 🔴  Silent Peach V1 • 🔴  Silent White • 🔴  Silent Ocean</td></tr><tr><td align="left"><strong>D</strong></td><td align="left">🟡  Silent Lemon V1 • 🔴  Dustproof Silver • 🔴  Crystal Linear • 🟡 Outemu Silent Tom</td></tr><tr><td align="left"><strong>F</strong></td><td align="left">🔴  Silver • 🔴  Black • 🔴  Red • 🟡  Brown • 🟡  Orange • 🟡  Purple</td></tr><tr><td align="left"><strong>F-</strong></td><td align="left">🔵 Blue, Green, Gold, Ocean Clicky, Spring Breeze, Milk Blue, Jerry Clicky, Cream Blue, Jade Blue</td></tr></tbody></table>
<hr>
<h1 id="описание-каждого">Описание каждого<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#описание-каждого" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="linears">[LINEARS]<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#linears" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Outemu Silent White</strong> - Простейший тихий свитч, немного песочит, но для первого поколения сойдёт, если ультра низкобюджетная сборка.</li>
<li><strong>Outemu Silver</strong> - Стандартный шлак, быстрый, с укороченным ходом.</li>
<li><strong>Outemu Black</strong> - Стандартный шлак, утяжелённая пружина.</li>
<li><strong>Outemu Red</strong> - Стандартный шлак, базовый.</li>
<li><strong>Outemu Silent Peach V1</strong> - Был норм, но сейчас есть лучше. Тихий, мягкий.</li>
<li><strong>Outemu Silent Peach V2</strong> - Хороший ультрабюджет тихий свитч.</li>
<li><strong>Outemu Cold Plum</strong> - Winter коллекция. Фулл нейлоновый корпус, укороченный ход, тяжелая пружина. Плотный thocky звук.</li>
<li><strong>Outemu Pink Lotus</strong> - Summer коллекция. Лёгкий, укороченный ход, плавный.</li>
<li><strong>Outemu Milk Peach</strong> - Бюджетный обычный свитч, приятный звук, но Cream и Jade лучше.</li>
<li><strong>Outemu Bai Lan</strong> - Середнячок.</li>
<li><strong>Outemu Hanny</strong> - Long-pole стем. Громкий clacky свитч (lego movie sound).</li>
<li><strong>Outemu Lanny</strong> - Похож на Hanny, но чуть сбалансированнее и обычнее.</li>
<li><strong>Outemu Silent Ocean</strong> - Неплохой тихий свитч.</li>
<li><strong>Outemu Crystal Linear</strong> - Полностью из поликарбоната. Сухой и дребезжит.</li>
<li><strong>Outemu Dustproof Silver</strong> - Чуть лучше обычного сильвера, но все равно так себе.</li>
<li><strong>Outemu Cream Pink</strong> - Хорошо смазан, маслянистый ход, глубокий звук (но Jade лучше).</li>
<li><strong>Outemu Jade Pink</strong> - Более крутая версия Cream Pink, с быстрой пружиной.</li>
</ul>
<hr>
<h2 id="tactiles">[TACTILES]<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#tactiles" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Outemu Brown</strong> - Абсолютное говно. Тактильности нет, ощущается как грязный песочный линейник со звенящей пружиной.</li>
<li><strong>Outemu Orange</strong> - Чуть тяжелее, чем Brown, но такой же сухой хлам со звоном.</li>
<li><strong>Outemu Purple</strong> - Старый тяжелый тактильник. Бугор резкий and неприятный, пружина жестко звенит на возврате.</li>
<li><strong>Outemu Red Panda</strong> - Проходной клон Holy Panda. Округлый бугор на старте, но без ручной смазки так себе затея.</li>
<li><strong>Outemu Maple Leaf</strong> - Неплохой звонкий тактильник. Бугор четкий, ощущается быстрым, clacky звучание.</li>
<li><strong>Outemu Milk Tea</strong> - Бюджетный средняк. Из-за молочного корпуса звук чуть мягче, но тактильность слабая.</li>
<li><strong>Outemu Silent Jade Yellow</strong> - Топовый тихий тактильник. Четкий бугор на старте, вообще не ватный, смазан нормально. Близок к Gazzew Boba u4, но мягче.</li>
<li><strong>Outemu Cream Yellow</strong> - Тактильный аналог Cream Pink. Хорошая смазка, мягкий глухой звук, приятный ход. Тоже близок к u4.</li>
<li><strong>Outemu Silent Tom</strong> - Старый тихий тактильник. Бугор невнятный и размытый, уже не актуален.</li>
<li><strong>Outemu Silent Grey</strong> - Древнее ватное говно. Бугра почти нет, при нажатии полное ощущение резины.</li>
<li><strong>Outemu Silent Lemon V1</strong> - Был популярным тихим свитчем, но сильно песочит.</li>
<li><strong>Outemu Silent Lemon V2</strong> - Работа над ошибками V1. Стал гораздо плавнее, нормальный тихий тактил.</li>
</ul>
<hr>
<h2 id="clickies">[CLICKIES]<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#clickies" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Outemu Blue</strong>, <strong>Outemu Green</strong>, <strong>Outemu Gold</strong>, <strong>Outemu Ocean Clicky</strong>, <strong>Outemu Spring Breeze</strong>, <strong>Outemu Milk Blue</strong>, <strong>Outemu Jerry Clicky</strong>, <strong>Outemu Cream Blue</strong>, <strong>Outemu Jade Blue</strong> - Тотальное говно без исключений. Из-за дешевой конструкции Click Jacket внутри все люфтит и дребезжит. Мерзкий пластиковый хруст вместо нормального клика.</li>
</ul> ]]></description>
    <pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Discord: a hole in security</title>
    <link>https://shiza.sccl.cc/en/it/Discord-Security-Problem</link>
    <guid>https://shiza.sccl.cc/en/it/Discord-Security-Problem</guid>
    <description><![CDATA[ <h1 id="discord-and-the-secret-technology-of-open-file-with-password">Discord and the secret technology of “open file with password”<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#discord-and-the-secret-technology-of-open-file-with-password" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>It seems surprising how many spammers are on Discord. Their sheer numbers have spawned the <code>1.jpg, 2.jpg, 3.jpg, 4.jpg @everyone @here</code> meme</p>
<p><img src="../../pictures/it/discord_spam.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>And then you dig into the topic and it turns out that absolutely any unauthorized file read access lets you steal someone’s session with a single token grep. No problem at all.</p>
<p>Not “you need to inject into the process”. Not “you need to bypass OS-level protection”. Just read a file.</p>
<hr>
<h1 id="what-is-a-token">What is a token?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#what-is-a-token" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Very roughly, in Discord:</p>
<pre><code>login + password + 2fa
        ↓
Discord issues a token (hash based on some data)
        ↓
the token becomes your session
</code></pre>
<p>Then the client sends this token to the server with every request:</p>
<blockquote>
<p>“it’s still me”</p>
</blockquote>
<p>Server:</p>
<blockquote>
<p>“ok, come in”</p>
</blockquote>
<p>Standard scheme. That’s how OAuth2, JWT, bearer tokens work. The model itself is fine - a token instead of a password, can be revoked server-side.</p>
<p>The problem isn’t the token. The problem is where it sits.</p>
<hr>
<h1 id="where-the-token-is-stored">Where the token is stored<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#where-the-token-is-stored" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Discord is built on <a href="https://www.electronjs.org/" class="external">Electron<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Electron is a framework for desktop apps using HTML/CSS/JS. Basically a browser without an address bar.</p>
<p>Discord stores the token in <code>Local Storage</code>. Technically it’s a LevelDB database sitting in the user’s home directory:</p>
<pre><code>~/.config/discord/Local Storage/leveldb/
</code></pre>
<p>Or on Windows:</p>
<pre><code>%APPDATA%/discord/Local Storage/leveldb/
</code></pre>
<p>And the token sits there in plaintext.</p>
<p>Not encrypted. Not in the OS keychain. Just a string in a database. Any program running as your user can read this file.</p>
<p>And now the funniest part: Electron, which Discord is built on, has had a built-in <code>safeStorage</code> API since 2019.</p>
<p>It can encrypt data using OS-level mechanisms:</p>
<ul>
<li><strong>macOS</strong> - Keychain Access. Protection from other users AND from other applications.</li>
<li><strong>Windows</strong> - DPAPI (Data Protection API). Encrypts with a key tied to the user account. Protection from other users.</li>
<li><strong>Linux</strong> - kwallet, gnome-libsecret, or freedesktop Secret Service. Depending on your DE.</li>
</ul>
<p>So the tool for proper secret storage is right there in Electron, out of the box. No need to install anything separately. No need to write your own crypto. Just call <code>safeStorage.encryptString(token)</code>.</p>
<p>Discord doesn’t do this.</p>
<p>For 6 years now.</p>
<hr>
<h1 id="what-happens-if-the-token-is-stolen">What happens if the token is stolen?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#what-happens-if-the-token-is-stolen" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Literally nothing interesting happens:</p>
<p>You take the token.</p>
<p>Feed it to another client.</p>
<p>T H A T ’ S  I T …</p>
<p>: You’re already inside the account.</p>
<p><img src="../../pictures/it/discord_token_login.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<ul>
<li>No login.</li>
<li>No password.</li>
<li>No 2FA.</li>
<li>No SMS.</li>
<li>No passkey.</li>
<li>No push notifications.</li>
<li>No confirmations.</li>
<li>No alerts about a new login.</li>
</ul>
<p><img src="../../pictures/it/discord_2fa.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>The new login isn’t even registered as new. The old session simply “moves” to a different machine.</p>
<p>The only thing you might notice is getting logged out of your device. And only if someone’s already logged in with that token, without leaving it for a rainy day.</p>
<hr>
<h1 id="is-it-easy-to-steal">Is it easy to steal?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#is-it-easy-to-steal" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Yes.</p>
<p><img src="../../pictures/it/discord_grep_token.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>On Windows it works exactly the same. Any .exe you download claiming to be “game cheats” or “free nitro” can read the file and send the token to the attacker’s server.</p>
<p>No exploits. No zero-day vulnerabilities. Just <code>open()</code> on a file that Discord itself placed in the home directory.</p>
<hr>
<h1 id="how-discord-could-have-done-it">How Discord COULD have done it<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#how-discord-could-have-done-it" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>There are a fuckton of options. All standard, all industry-wide:</p>
<ul>
<li><code>safeStorage</code> in Electron - available since 2019, used by other Electron apps (Signal Desktop, 1Password, Bitwarden)</li>
<li>Store secrets through OS keychain directly</li>
<li>Access confirmation for secrets (biometrics, master password)</li>
<li>Device fingerprint + token binding to hardware</li>
<li>A separate key generated on-device at first login</li>
<li>Security alerts on sudden environment changes (new IP, new OS, new hardware)</li>
<li>Device-based sessions with new device confirmation</li>
</ul>
<p>Yes, all of this can also be bypassed with full access to the machine.</p>
<p>But between:</p>
<blockquote>
<p>“read a file”</p>
</blockquote>
<p>and</p>
<blockquote>
<p>“inject into a process, bypass system dialogs, spoof the environment”</p>
</blockquote>
<p>there’s a massive gap.</p>
<p>Right now the attacker only needs the most primitive access - read permissions on files in the home directory. Meaning literally any running program.</p>
<p>With <code>safeStorage</code>, the attacker would need to either inject into the Discord process, or decrypt DPAPI/Keychain (which requires privilege escalation or stealing the master password).</p>
<p>It doesn’t make the attack impossible. But it raises the bar from “script kiddie with grep” to “targeted attack with specific tools”.</p>
<hr>
<h1 id="and-right-now">And right now?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#and-right-now" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Right now the cost of an attack looks roughly like this:</p>
<pre><code>download suspicious crap
        ↓
it reads a file
        ↓
your session is stolen: "1.jpg, 2.jpg, 3.jpg, 4.jpg @everyone @here"
</code></pre>
<p>And it doesn’t matter that you have 2FA. Doesn’t matter that your password is complex. The file is open to all programs running as your user.</p>
<hr>
<h1 id="if-discord-cant">If Discord can’t<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#if-discord-cant" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>An option - log into Discord from a dedicated user account.</p>
<p>So that only that user has read permissions on the session database. Barring internal exploits in the Discord app itself - the chance of anything getting stolen is minimal.</p>
<p>On Linux this is easy out of the box - the permission hierarchy is baked in since the system’s birth.</p>
<p>On Windows it’s also possible, but security isn’t as strong: permissions were bolted on as a hack only with the Vista release, and plenty of software still bypasses them.</p>
<p>But still better than leaving the file open to everyone, which Discord for some unfathomable reason couldn’t protect itself.</p>
<p><img src="../../pictures/it/discord_rights.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<blockquote class="callout note" data-callout="note">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>This protection layer is implemented in my <a href="../../en/it/os/sccl_nix" class="internal alias" data-slug="en/it/os/sccl_nix">NixOs config</a></p></div>
                  
                </div>
</blockquote>
<p><small>you can check your own discord right now: go to <code>~/.config/discord/Local Storage/leveldb/</code> and run <code>grep -r 'mfa\.[a-zA-Z0-9_-]\{20,\}' .</code> - you’ll probably find your token.</small><br></p> ]]></description>
    <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Discord: дыра в безопасности</title>
    <link>https://shiza.sccl.cc/ru/it/Discord-Security-Problem</link>
    <guid>https://shiza.sccl.cc/ru/it/Discord-Security-Problem</guid>
    <description><![CDATA[ <h1 id="discord-и-секретная-технология-открытый-файл-с-паролем">Discord и секретная технология “открытый файл с паролем”<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#discord-и-секретная-технология-открытый-файл-с-паролем" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Казалось бы удивительно, чо так много спамеров в дискорде. Они своим количеством уже породили мем <code>1.jpg, 2.jpg, 3.jpg, 4.jpg @everyone @here</code></p>
<p><img src="../../pictures/it/discord_spam.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>А потом копаешь тему и оказывается что абсолютно любой несанкционированный доступ к чтению файлов позволяет без проблем украсть твою сессию одним grep-ом токена.</p>
<p>Не “надо внедриться в процесс”. Не “надо обойти OS-level защиту”. Просто прочитать файл.</p>
<hr>
<h1 id="что-такое-токен">Что такое токен?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#что-такое-токен" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Очень грубо, в дискорде:</p>
<pre><code>login + password + 2fa
        ↓
Discord выдаёт токен (хэш на основе каких-то данных)
        ↓
токен становится твоей сессией
</code></pre>
<p>Дальше клиент кидает этот токен на сервер при каждом запросе:</p>
<blockquote>
<p>“это всё ещё я”</p>
</blockquote>
<p>Сервер:</p>
<blockquote>
<p>“ок, входи”</p>
</blockquote>
<p>Стандартная схема. Так работает OAuth2, JWT, bearer tokens. Сама по себе модель норм - токен вместо пароля, можно отозвать серверно.</p>
<p>Проблема не в токене. Проблема в том, где он лежит.</p>
<hr>
<h1 id="где-хранится-токен">Где хранится токен<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#где-хранится-токен" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Discord построен на <a href="https://www.electronjs.org/" class="external">Electron<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Electron - это фреймворк для десктопных приложений на HTML/CSS/JS. По сути это браузер без адресной строки.</p>
<p>Discord хранит токен в <code>Local Storage</code>. Технически это LevelDB база, которая лежит в домашней директории пользователя:</p>
<pre><code>~/.config/discord/Local Storage/leveldb/
</code></pre>
<p>Или на Windows:</p>
<pre><code>%APPDATA%/discord/Local Storage/leveldb/
</code></pre>
<p>И токен лежит там в plaintext.</p>
<p>Не зашифрованный. Не в OS keychain. Просто строка в базе данных. Любая программа, запущенная от твоего пользователя, может прочитать этот файл.</p>
<p>А теперь самое смешное: Electron, на котором построен Discord, с 2019 года имеет встроенный API <code>safeStorage</code>.</p>
<p>Он умеет шифровать данные через OS-level механизмы:</p>
<ul>
<li><strong>macOS</strong> - Keychain Access. Защита от других пользователей И от других приложений.</li>
<li><strong>Windows</strong> - DPAPI (Data Protection API). Шифрует ключом, привязанным к учётке пользователя. Защита от других пользователей.</li>
<li><strong>Linux</strong> - kwallet, gnome-libsecret, или freedesktop Secret Service. В зависимости от DE.</li>
</ul>
<p>То есть инструмент для нормального хранения секретов есть прямо в Electron из коробки. Его не надо ставить отдельно. Не надо писать свою криптографию. Просто вызвать <code>safeStorage.encryptString(token)</code>.</p>
<p>Discord этого не делает.</p>
<p>Уже 6 лет как.</p>
<hr>
<h1 id="а-если-токен-украсть">А если токен украсть?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#а-если-токен-украсть" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>То буквально ничего интересного не происходит:</p>
<p>Берёшь токен.</p>
<p>Подсовываешь его другому клиенту.</p>
<p>В С Ё…</p>
<p>: Ты уже внутри аккаунта.</p>
<p><img src="../../pictures/it/discord_token_login.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<ul>
<li>Без логина.</li>
<li>Без пароля.</li>
<li>Без 2FA.</li>
<li>Без SMS.</li>
<li>Без passkey.</li>
<li>Без push-уведомлений.</li>
<li>Без подтверждений.</li>
<li>Без алертов о новом входе.</li>
</ul>
<p><img src="../../pictures/it/discord_2fa.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Новый логин вообще не регистрируется как новый. Старая сессия просто “переезжает” на другую машину.</p>
<p>Всё что можешь заметить - так это что разлогинило с твоего устройства. И то при условии что с этого токена уже кто-то зашёл, не оставив на чёрный день.</p>
<hr>
<h1 id="а-чо-легко-украсть">А чо, легко украсть?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#а-чо-легко-украсть" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Да.</p>
<p><img src="../../pictures/it/discord_grep_token.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<p>На Windows работает ровно так же. Любой .exe качающий “читы для игры” или “бесплатный нитро” может прочитать файл и отправить токен на сервер злоумышленника.</p>
<p>Никаких эксплойтов. Никаких уязвимостей нулевого дня. Просто <code>open()</code> на файл, который Discord сам положил в домашнюю директорию.</p>
<hr>
<h1 id="как-discord-мог-бы-сделать">Как Discord МОГ бы сделать<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#как-discord-мог-бы-сделать" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Вариантов дохуя. Все стандартные, все индустриальные:</p>
<ul>
<li><code>safeStorage</code> в Electron - доступен с 2019, юзают другие Electron-приложения (Signal Desktop, 1Password, Bitwarden)</li>
<li>Хранение секретов через OS keychain напрямую</li>
<li>Подтверждение доступа к секрету (биометрия, master password)</li>
<li>Fingerprint устройства + привязка токена к железу</li>
<li>Отдельный ключ, генерируемый на устройстве при первом входе</li>
<li>Security alerts при резкой смене окружения (новый IP, новая OS, новое железо)</li>
<li>Device-based сессии с подтверждением нового устройства</li>
</ul>
<p>Да, всё это тоже можно обходить при наличии full access к машине.</p>
<p>Но между:</p>
<blockquote>
<p>“прочитать файл”</p>
</blockquote>
<p>и</p>
<blockquote>
<p>“внедриться в процесс, обойти системные диалоги, подделать окружение”</p>
</blockquote>
<p>лежит огромная пропасть.</p>
<p>Сейчас атакующему достаточно самого примитивного доступа - права на чтение файлов в домашней директории. То есть вообще любой запущенной программы.</p>
<p>С <code>safeStorage</code> атакующему пришлось бы либо внедряться в процесс Discord, либо расшифровывать DPAPI/Keychain (что требует повышения привилегий или кражи мастер-пароля).</p>
<p>Это не делает атаку невозможной. Но поднимает планку с “скрипткидди с grep-ом” до “таргетированной атаки со специфическими инструментами”.</p>
<hr>
<h1 id="а-сейчас">А сейчас?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#а-сейчас" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Сейчас цена атаки выглядит примерно так:</p>
<pre><code>скачал подозрительную фигню
        ↓
она прочитала файл
        ↓
твою сессию угнали: "1.jpg, 2.jpg, 3.jpg, 4.jpg @everyone @here"
</code></pre>
<p>И пофиг что у тебя 2FA. Пофиг что сложный пароль. Файл открыт для всех программ, запущенных от твоего пользователя.</p>
<hr>
<h1 id="если-discord-не-может">Если Discord не может<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#если-discord-не-может" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Как варик - логиниться в Discord со специально выделенного пользователя.</p>
<p>Чтоб права на чтение базы с сессией были только у него. При отсутствии внутренних эксплойтов в приложении Discord - шанс что у тебя что-то угонят минимальный.</p>
<p>На Linux это легко сделать из коробки - иерархия прав там нормальная с рождения системы.</p>
<p>На Windows в целом тоже можно, но безопасность не так велика: права появились как костыль лишь с релизом Vista, и до сих пор много софта работает в обход них.</p>
<p>Но всё лучше, чем оставлять файл открытым для всех, который Discord сам защитить по какой-то неведомой причине не смог.</p>
<p><img src="../../pictures/it/discord_rights.webp" width="auto" height="auto" alt="" loading="lazy"></p>
<blockquote class="callout note" data-callout="note">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>Такая защитная прослойка имплементирована в моём <a href="../../ru/it/os/sccl_nix" class="internal alias" data-slug="ru/it/os/sccl_nix">NixOs config</a></p></div>
                  
                </div>
</blockquote>
<p><small>можешь прям щас проверить свой discord: зайди в <code>~/.config/discord/Local Storage/leveldb/</code> и сделай <code>grep -r 'mfa\.[a-zA-Z0-9_-]\{20,\}' .</code> - скорее всего найдёшь свой токен.</small><br></p> ]]></description>
    <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>SSK-58tmr</title>
    <link>https://shiza.sccl.cc/en/Peripheral/Keyboards/SSK-58tmr</link>
    <guid>https://shiza.sccl.cc/en/Peripheral/Keyboards/SSK-58tmr</guid>
    <description><![CDATA[ <blockquote class="callout info" data-callout="info">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>In the process of thinking (development has not started) </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<p>Currently working on another keyboard: <a href="../../../en/Peripheral/Keyboards/Sakuli60lp" class="internal" data-slug="en/Peripheral/Keyboards/Sakuli60lp">Sakuli60lp</a></p>
</div>
</div>
</blockquote>
<p><em>A.K.A.</em></p>
<h1 id="long-way-to-dream">Long way to dream<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#long-way-to-dream" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="in-short">In short…<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#in-short" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>I have long had a desire to build a custom keyboard from scratch…<br>
This writing is essentially my shitpost <em>(Idk what the fuck:: I love writing letters)</em><br>
I’ll dump my thoughts on this project here, if you can call it that. (sort of like an overkill job, maybe it’s possible)</p>
<h2 id="whatamhow">Whatamhow<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#whatamhow" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>It is known that the topic of analog keyboards, mainly based on Hall-Effect sensors, has been popular in the keyboard market for the last few years.<br>
I don’t see any point in listing the goodies, they are obvious.<br>
And one of the minuses is the fucking energy consumption of HE keyboards compared to mechanical ones.<br>
Which greatly complicates the assembly of a wireless keyboard…</p>
<p>For many, it may seem that wireless is useless shit, useless to anyone.<br>
Until some time I thought so myself, sitting on a staggered 2 kg piece of aluminum with buttons.<br>
But then I found myself in the hands of a split keyboard without a single wire.<br>
: turned on 2 halves, shoved the USB dongle into the computer and you work<br>
holds a charge for 3 months with 15 hours of daily use with 1500mAh batteries in each half (hule mechanics)<br>
And it turned out to be so convenient that I can no longer imagine a keyboard with wires.</p>
<p>That’s why I look towards tmr, because he eats 8 times less than he<br>
Although there must be pitfalls with it in the form of an unstable Vcc, which can spoil their accuracy, which is crap in comparison with he (well, in fact, in fact, they are stupidly less noisy, they don’t really suck in terms of accuracy)</p>
<h2 id="sobsna-what-do-i-need-where-the-fuck-does-it-all-begin">Sobsna what do I need, where the fuck does it all begin.<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#sobsna-what-do-i-need-where-the-fuck-does-it-all-begin" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Right now it’s a stage of thinking, which will later flow into tests.<br>
According to budgets, I hope not to cost more than 25k rubles.<br>
<small>the price for delivering sensors to Russia is just fucking fucked up (60++ euros per bag, fucking trade unions and sanctions)</small><br></p>
<p>In terms of software, at first I thought this was the top: <a href="https://github.com/peppapighs/libhmk" class="external">peppapighs/libhmk<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, because this is the best open source software for analog keyboards that can be found so far. It’s a pity that no one has messed with the TMR section yet, but overall I don’t think there’s a big difference. (more on the difference later)</p>
<p>But unfortunately <a href="https://github.com/peppapighs/libhmk/issues/2" class="external">author stated<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - there will be no support for split<br>
And implementing such a theme on your own is fucking hell, which will probably require re-wiring all the software. Because there is no implementation of communication and synchronization of two controllers (such as uart/i2c/spi) Well, at least you can put some kind of wireless on top of tinyusb if you break your ass a little.</p>
<p>But in order not to tear my ass, I’ll rather look towards <a href="https://github.com/zmkfirmware/zmk" class="external">zmk<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> because This is one of the best solutions for keyboard firewood.<br>
And some idiot is already making a <a href="https://github.com/cr3eperall/zmk-feature-hall-effect" class="external">module<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> for it to support HE boards, with a rapid trigger, socd and other cool features.</p>
<h2 id="so-we-raised-the-topic-of-software">So we raised the topic of software…<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#so-we-raised-the-topic-of-software" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>But there is no mention of TRM anywhere.<br>
But no one has ever made keyboards on TMR in the open source.<br>
Moreover, no one has made Split Wireless TMR keyboards.</p>
<p>Everywhere current HE-HE-HE</p>
<p>This is one of the key problems of this project - there are no examples.<br>
But fortunately there is already some progress in the HE field.<br>
And there is hope that there will be no special problems with TMR.</p>
<h2 id="lets-move-on-to-the-difference-between-tmr-and-he">Let’s move on to the difference between TMR and HE.<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#lets-move-on-to-the-difference-between-tmr-and-he" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Pachama, I have hope that I can push TMR sensors under HE firewood without tearing my ass?</p>
<ul>
<li>TMR and HE have the same pinout (if we consider keyboard-friendly options in the SOT23-3 case)</li>
</ul>
<blockquote>
<p>GND, Vcc, Out</p>
</blockquote>
<ul>
<li>Can operate in the same voltage range</li>
</ul>
<blockquote>
<p>Keyboards usually dance at 3.3v - both technologies can work in this field</p>
</blockquote>
<ul>
<li>Similar transfer function</li>
</ul>
<blockquote>
<p>HE: VOUT = VQ + S*B<br>
TMR: VOUT = VCC*(VOFFSET + S*B)</p>
<p>, where<br>
VOUT - output voltage<br>
VCC - supply voltage<br>
VOFFSET - offset<br>
VQ - Voltage at B = 0<br>
S - sensitivity<br>
B - induction (don’t give a fuck, the position of the magnet, the switch is tightly clamped)</p>
<p>In short, it’s just bullshit, we just need to take Vcc into account, which may become a problem in the future.</p>
</blockquote>
<p>The theme itself will be a columar format, like on a keyboard (it has a little more - 64 buttons, I think this keyboard will still have 58-60 buttons), which few people will be interested in.<br>
But questions about HE/TMR are common, regardless of the layout, I hope at least someone will be interested in reading some of my belongings and research on this topic…</p>
<p><img src="../../../pictures/Peripheral/sccl_colemak.webp" width="auto" height="auto" alt="" loading="lazy"></p> ]]></description>
    <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>SSK-58tmr</title>
    <link>https://shiza.sccl.cc/ru/Peripheral/Keyboards/SSK-58tmr</link>
    <guid>https://shiza.sccl.cc/ru/Peripheral/Keyboards/SSK-58tmr</guid>
    <description><![CDATA[ <blockquote class="callout info" data-callout="info">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>В процессе обдумываний (разработка не началась) </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<p>В данный момент занимаюсь другой клавиатурой: <a href="../../../ru/Peripheral/Keyboards/Sakuli60lp" class="internal" data-slug="ru/Peripheral/Keyboards/Sakuli60lp">Sakuli60lp</a></p>
</div>
</div>
</blockquote>
<p><em>A.K.A.</em></p>
<h1 id="долгий-путь-к-мечте">Долгий путь к мечте<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#долгий-путь-к-мечте" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="если-кратко">Если кратко…<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#если-кратко" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>У меня уже давно есть желание собрать кастомную клавиатуру с нуля…<br>
Ета писанина по-сути своей является моим шитпостом <em>(хз нахуя::я люблю писать буковы)</em><br>
Буду сюда вываливать свои мысли по поводу сего проектица, если ето можно так назвать. (вроде работы овердохуя, наверна можно)</p>
<h2 id="чотамкак">Чотамкак<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#чотамкак" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Известно, что на рынке клавиатур последние несколько лет попсует тема с аналоговыми клавиатурами, в основном на базе Hall-Effect сенсоров<br>
Плюшки перечислять не вижу смысла, они очевидны.<br>
А из минусов - ебейшее энергопотребление HE клавиатур по сравнению с механикой.<br>
Что значительно усложняет сборку беспроводной клавы…</p>
<p>Для многих может показаться что беспровод - бесполезное говно, нахуй никому ненужное.<br>
Я до каких то пор сама так думала, сидя на стаггеред 2кг куске аллюминия с кнопками.<br>
Но потом в моих руках оказалась split клавиатура без единого провода.<br>
: включил 2 половинки, пихнул usb донгл в комп и работаешь<br>
держит заряд месяца 3 при 15 часовом ежедневном юзе с 1500мач аккумами в каждой половинке (механика хуле)<br>
И эт оказалось настолько удобно, что я больше не мыслю клавиатуру, в которой будут провода.</p>
<p>Потому я гляжу в сторону tmr, ибо хавает он раз так в 8 меньше, чем he<br>
Хотя с ним должно быть есть подводные камни в виде нестабильного Vcc, что может гадить их ебанутую в сравнении с he точность (ну вапще на самом деле они тупа меньше шумят, по точности не прям ебут)</p>
<h2 id="собсна-чо-мне-нада-с-чего-нахуй-всё-начинается">Собсна чо мне нада, с чего нахуй всё начинается.<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#собсна-чо-мне-нада-с-чего-нахуй-всё-начинается" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Щас пока этап кумеканья, который позже перетечет в тесты.<br>
По бюджетам я надеюсь не уйти дороже 25к руб.<br>
<small>цена доставки сенсоров в россию это пиздец просто нахуй (60++ евро за пакетик ебанные профсоюзы и санкции)</small><br></p>
<p>По софту - я сначал показалось, что это топ: <a href="https://github.com/peppapighs/libhmk" class="external">peppapighs/libhmk<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, ибо это лучший опенсурс софт для аналоговых клав, чо пока можно найти. Жаль на нём пока вроде никто не мутил TMR поделия, но в целом не думаю, что велика разница. (о разнице позже)</p>
<p>Но к сожалению якы <a href="https://github.com/peppapighs/libhmk/issues/2" class="external">заявил аффтор<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - поддержки split не буде<br>
А имплементить такую тему самостоятельно - ебанутый ад, который потребует, должно быть, перековырчивания всего софта. Т.к. там нет реализации связи и синхронизации двух контроллеров (типа uart/i2c/spi) Ну хотяб беспровод какой-то налопатить поверх tinyusb можно если немного порвать жопу.</p>
<p>Но дабы не рвать жопу, скорее буду глядеть в сторону <a href="https://github.com/zmkfirmware/zmk" class="external">zmk<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> т.к. это одино из лучших решений для дров клавы.<br>
И на него уже какой-то пиздузиаст пилит <a href="https://github.com/cr3eperall/zmk-feature-hall-effect" class="external">модуль<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> для поддержки HE бордов, с раппид триггером, socd и прочими крутыми фичами.</p>
<h2 id="вот-мы-подняли-тему-софта">Вот мы подняли тему софта…<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#вот-мы-подняли-тему-софта" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>А ведь нигде нет упоминаний TRM.<br>
А ведь никто и не делал в опенсурсе клавы на TMR.<br>
А ведь тем более никто не делал Split Wireless TMR клавы.</p>
<p>Везде тока HE-HE-HE</p>
<p>Это одна из ключевых проблем этого проекта - нет примеров.<br>
Но благо уже есть какие-то подвижки на HE поле.<br>
И есть надежда что с TMR не будет особых проблем.</p>
<h2 id="перейдём-собсна-к-разнице-tmr-и-he">Перейдём собсна к разнице TMR и HE.<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#перейдём-собсна-к-разнице-tmr-и-he" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Пачаму у меня есть надежда, что я смогу пихнуть TMR датчики, под HE дрова, при этом не порвав жопу?</p>
<ul>
<li>TMR и HE имеют одинаковую распиновку (если рассматривать удобные для клавы варианты в SOT23-3 корпусе)</li>
</ul>
<blockquote>
<p>GND, Vcc, Out</p>
</blockquote>
<ul>
<li>Могут работать в одном и том же диапазоне напряги</li>
</ul>
<blockquote>
<p>Клавы обычно пляшут от 3.3v - обе технологии могут работать на этом поле</p>
</blockquote>
<ul>
<li>Схожая передаточная функция</li>
</ul>
<blockquote>
<p>HE: VOUT = VQ + S*B<br>
TMR: VOUT = VCC*(VOFFSET + S*B)</p>
<p>, где<br>
VOUT — выходное напряжение<br>
VCC — напряжение питания<br>
VOFFSET — смещение<br>
VQ - Напряга при B = 0<br>
S — чувствительносты<br>
B — индукция (похуй, положение магнита, наскильно сильно зажат свитч)</p>
<p>Кароче одна хуйня, нам нада только учитывать Vcc, что может станет проблемой в будущем.</p>
</blockquote>
<p>Сама темка будет columar формата, типа как на кортинке (на ней чутьб больше - 64 кнопки, я думаю в этой клаве таки будет 58-60 кнопок), что мало кому интересно.<br>
Но вопросецы по поводу HE/TMR общие, независимо от лейаута, надеюсь хоть комута будет интересно почитать какие-та мои пожитки и исследования в этой теме…</p>
<p><img src="../../../pictures/Peripheral/sccl_colemak.webp" width="auto" height="auto" alt="" loading="lazy"></p> ]]></description>
    <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>sccl_nix: Declarative heaven</title>
    <link>https://shiza.sccl.cc/en/it/os/sccl_nix</link>
    <guid>https://shiza.sccl.cc/en/it/os/sccl_nix</guid>
    <description><![CDATA[ <h1 id="if-this-shitpost-like-article-looks-like-schizoposting">If this shitpost-like article looks like schizoposting<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#if-this-shitpost-like-article-looks-like-schizoposting" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Proper docs in English: <a href="https://github.com/scclie/sccl_nix" class="external">github/scclie/sccl_nix/README.md<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a></p>
<p><small>am just dumping thoughts here</small><br></p>
<hr>
<h1 id="why-the-fuck-why"><del>Why the fuck</del> Why?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#why-the-fuck-why" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>In the past, after 6 years of Arch-only use - I didn’t get it either.</p>
<p>Like, here’s yet another trendy distro, something like Fedora, Manjaro and other cuties.</p>
<p>Then I felt like installing it…</p>
<p>After that, apparently, my world flipped upside down.</p>
<p>A bunch of problems that plagued regular distros - solved instantly. I didn’t even realize they were problems before - after years of using standard distros you reach a consensus, you just get used to how you’re supposed to work in them for things to be chill.</p>
<p>But one kind of chill and another kind of chill - those are two completely different kinds of chill.</p>
<p>The funniest part - problems you don’t even think of as problems. Configs scatter across <code>/etc</code>, packages get installed and forgotten, systemd units are enabled by hand and lost. Six months later the system is a black box. You have no idea how it got to this state.</p>
<p>Fucking git for <code>/etc</code>? fr</p>
<p>NixOS offers the opposite: you describe the end result. All packages, configs, users, services - one build, one state.</p>
<p>Want a second machine? Same config, different host. Want to roll back? Slam the previous generation. Every build lives in <code>/nix/store</code>, nothing gets scattered across <code>/usr</code> and <code>/etc</code> randomly.</p>
<p>A separate joy with shells. You often need “disposable programs” - install, use, forget. And it’s sitting in the system ;k</p>
<p>Nix offers <code>nix shell</code> - throw in any programs, use them, forget - they get cleaned up on garbage collection. Dependencies are only pulled into your shell from <code>/nix/store</code>.</p>
<p>You can just <code>nix run nixpkgs#imagemagick -- convert ...</code> and it downloads, runs, and disappears on the next garbage collector run. Zero traces.</p>
<p><small>manually, if you wanna clean up - you slam <code>nix-collect-garbage -d</code>.</small><br></p>
<hr>
<h1 id="where-nix-even-came-from">Where Nix even came from<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#where-nix-even-came-from" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>In 2006, a cool dude named Eelco Dolstra defended his PhD at Utrecht University. Thesis topic: “<a href="https://edolstra.github.io/pubs/phd-thesis.pdf" class="external">The Purely Functional Software Deployment Model<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>”</p>
<p>The idea was radical: what if a package is a pure function?</p>
<p>Input: source + dependencies + compile flags. Output: binary.</p>
<p>Same input = same output. Always. On any machine. None of that “it builds on my machine”.</p>
<p>For this, <code>/nix/store</code> is arranged with genius simplicity. Each package lives in its own directory, and the path includes a hash of all inputs:</p>
<pre><code>/nix/store/5mq2jcn36ldd97xrcnf3jg8irnfp5xqr-fish-4.0.1/
</code></pre>
<p>The hash depends on sources, dependencies, compile flags, compiler version. EVERYTHING.</p>
<p>Any change - different hash - different path. Two packages don’t conflict because they’re in different directories. Atomic updates. None of that “I installed the new version and broke the old one”.</p>
<p>It’s a completely different paradigm compared to other package managers.</p>
<p>The nixpkgs ecosystem is now the <a href="https://repology.org/repositories/graphs" class="external">largest package repository in the world<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. All of it built by one Hydra farm and cached in a binary cache.</p>
<p><small>honestly, the numbers are a bit inflated - a lot of packages are split into tons of tiny ones. In terms of actual software, nixpkgs is smaller than e.g. AUR.</small><br></p>
<hr>
<h1 id="imperative-vs-declarative">Imperative vs declarative<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#imperative-vs-declarative" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>As was dumped out in some very old thought back at the dawn of Nix - package managers can be compared to programming languages.</p>
<p>There’s an article <a href="https://en.wikisource.org/wiki/Functional_Package_Management_with_Guix" class="external">“Functional Package Management with Guix”<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> where this idea is well explored, but it sounded earlier too.<br>
<small>too lazy to dig up where else, but it was.</small><br></p>
<p><strong>Imperative</strong> package managers (apt, yum, pacman) - like procedural programming. You tell the system WHAT TO DO: <code>apt install nginx</code>, then <code>systemctl enable nginx</code>, then manually the config in <code>/etc/nginx/nginx.conf</code>. A sequence of commands mutates state.</p>
<p><strong>Declarative</strong> ones (Nix, Guix) - like functional. You describe WHAT RESULT you want:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="nix" data-theme="github-light github-dark"><code data-language="nix" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">services</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">nginx</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  virtualHosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"example.com"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    root</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "/var/www/example.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">;</span></span></code></pre></figure>
<p>Not “install nginx and configure it”. But “this is what nginx should be in the system”.</p>
<p>The difference is fundamental. In the imperative world you lose history - the system remembers the current state but not how it got there. In declarative - each build is a function of the config. Want to replicate on another machine? Same config, same result.</p>
<p>and what’s next? Some higher-level approach, more abstract? (or a mega-abstract llm-based chat interface??????? - ai problem resolver :D )</p>
<hr>
<h1 id="ecosystem">Ecosystem<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#ecosystem" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>NixOS by itself only solves part of the problem. The system level. But there’s a ton more you want to declarativize.</p>
<h2 id="flakes">flakes<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#flakes" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>An experimental feature since 2021. Still experimental.</p>
<p>But in practice everyone’s been on flakes for ages. They solve two main problems of old Nix: dependency versioning (via <code>flake.lock</code>) and a unified project format.</p>
<p>Before flakes there was channels - global system state. Switch the channel - it changes for everything. Zero isolation.</p>
<p>With flakes, each project has its own <code>flake.lock</code> that pins specific versions of ALL dependencies. Like <code>package-lock.json</code> but for the entire system.</p>
<p>The flake auto-discovers hosts from directories - drop <code>hosts/&#x3C;hostname>/</code>, it picks it up as <code>nixosConfigurations.&#x3C;hostname></code>. No <code>if hostname ==</code> hacks at the discovery level.</p>
<h2 id="home-manager">home-manager<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#home-manager" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><a href="https://github.com/nix-community/home-manager" class="external">10k stars on GitHub<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Lets you declaratively manage your user environment - dotfiles, user packages, services.</p>
<p>Previously dotfiles were managed with GNU stow or bare git repos. home-manager lets you do the same thing but Nix-style: with options, dependencies, immutability.</p>
<p>And the main thing - integrates as a NixOS module. One <code>nixos-rebuild switch</code> rebuilds both the system and the user environment.</p>
<h2 id="disko">disko<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#disko" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><a href="https://github.com/nix-community/disko" class="external">NixOS is a distro where everything is described as code. Except one thing: disk partitioning during installation is done manually. disko fixes this sad omission.<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a></p>
<p>You write the disk config once instead of messing with <code>gparted</code> every time.<br>
If hosts have identical disks - you can replicate freely.</p>
<h2 id="sops-nix">sops-nix<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#sops-nix" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Secrets (SSH keys, passwords, GPG) are encrypted with an age key and live right in the repo. At build time they’re decrypted into the right places.</p>
<p>Previously secrets were either kept outside the repo (and lost), or in encrypted git repos (inconvenient). sops-nix makes it transparent: secrets as part of the config, but encrypted.</p>
<hr>
<h1 id="the-scaling-problem">The scaling problem<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-scaling-problem" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>There’s one catch with NixOS. You start with one host, everything’s beautiful. Then you add a second. Then a third.</p>
<p>And the config turns into a dump of exceptions and such. (just like any codebase)</p>
<p>There are other approaches:</p>
<ul>
<li><a href="https://github.com/hercules-ci/flake-parts" class="external">flake-parts<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - a modular system for flakes, lets you split config into reusable modules. Good for large projects but adds its own abstraction layer.</li>
<li><a href="https://github.com/snowfallorg/lib" class="external">snowfall-lib<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - a library for organizing Nix configs with auto-discovery. Similar to what I do, but with its own philosophy.</li>
<li>Tons of people just live with <code>if hostname</code> and don’t sweat it. Also an option if you have 1-2 hosts.</li>
</ul>
<p>And that’s where <a href="https://codeberg.org/scclie/sccl_nix/" class="external">sccl_nix<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> was born - my configuration that tries not to turn into a dump. (turned into one a couple times, but I try really hard)<br>
I wanted an architecture where adding a new host means creating a folder and declaring <code>sccl.*</code> options. Without a single <code>if</code>.</p>
<hr>
<h1 id="sccl_nix-architecture">sccl_nix architecture<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#sccl_nix-architecture" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Dotfiles are split into three entities.</p>
<p><strong>hosts</strong> - hardware and system stuff. Disks (disko), GPU drivers, network, system packages. Anything that can’t be synced between different machines. Auto-discovered by the flake from <code>hosts/</code>.</p>
<p><strong>profiles</strong> - a user with their zoo of programs, shell and user-level junk. One host can have any number of profiles. Imported into the host config.</p>
<p><strong>shared</strong> - base for everyone. Fish, niri, alacritty, waybar, basic CLI tools. Configure once - import into all profiles.</p>
<p>Currently two hosts: <strong>sacculos</strong> (desktop, AMD) and <strong>aero15laptop</strong> (laptop, NVIDIA). Two profiles: <strong>paper</strong> (main, with the whole zoo) and <strong>bootstrap</strong> (minimal, just vim, nmtui and ssh for USB installs).<br>
<small>as an example of real usage.</small><br></p>
<p>Diagram:</p>
<pre><button class="expand-button" aria-label="Expand mermaid diagram" data-view-component><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z"></path></svg></button><code class="mermaid" data-clipboard="&#x22;flowchart TD\n    F[\&#x22;❄️ flake.nix<br/>auto-discovery: hosts + shells\&#x22;]\n\n    NM[\&#x22;⚙️ nixos/modules/core.nix<br/>sccl.* options: conditional config\&#x22;]\n\n    H1[\&#x22;🖥️ hosts/sacculos<br/>desktop + amd gpu\&#x22;]\n    H2[\&#x22;💻 hosts/aero15laptop<br/>laptop + nvidia gpu\&#x22;]\n\n    P1[\&#x22;👤 profiles/paper<br/>main: steam, blender...\&#x22;]\n    PS[\&#x22;📦 profiles/shared<br/>base: fish, niri, waybar...\&#x22;]\n\n    PB[\&#x22;🪶 profiles/bootstrap<br/>minimal: nmtui, vim, ssh\&#x22;]\n\n    S[\&#x22;🔐 secrets/ sops-nix encrypted\&#x22;]\n    SH[\&#x22;🐚 shells/ auto-discovered devShells\&#x22;]\n\n    F --> H1\n    F --> H2\n    F --> SH\n\n    H1 --> NM\n    H2 --> NM\n\n    H1 --> P1\n    H2 --> P1\n\n    H1 --> PB\n    H2 --> PB\n\n    P1 --> PS\n\n    classDef flakeStyle stroke:#517599,stroke-width:2px,color:currentColor\n    classDef hostStyle stroke:#5e81ac,stroke-width:2px,color:currentColor\n    classDef profileStyle stroke:#8fbcbb,stroke-width:2px,color:currentColor\n    classDef sharedStyle stroke:#d08770,stroke-width:2px,color:currentColor\n    classDef moduleStyle stroke:#bf616a,stroke-width:2px,color:currentColor\n    classDef secretStyle stroke:#bf616a,stroke-width:2px,color:currentColor\n    classDef shellStyle stroke:#d08770,stroke-width:2px,color:currentColor\n\n    class F flakeStyle\n    class H1,H2 hostStyle\n    class P1,PB profileStyle\n    class PS sharedStyle\n    class NM moduleStyle\n    class S secretStyle\n    class SH shellStyle&#x22;">flowchart TD
    F["❄️ flake.nix&#x3C;br/>auto-discovery: hosts + shells"]

    NM["⚙️ nixos/modules/core.nix&#x3C;br/>sccl.* options: conditional config"]

    H1["🖥️ hosts/sacculos&#x3C;br/>desktop + amd gpu"]
    H2["💻 hosts/aero15laptop&#x3C;br/>laptop + nvidia gpu"]

    P1["👤 profiles/paper&#x3C;br/>main: steam, blender..."]
    PS["📦 profiles/shared&#x3C;br/>base: fish, niri, waybar..."]

    PB["🪶 profiles/bootstrap&#x3C;br/>minimal: nmtui, vim, ssh"]

    S["🔐 secrets/ sops-nix encrypted"]
    SH["🐚 shells/ auto-discovered devShells"]

    F --> H1
    F --> H2
    F --> SH

    H1 --> NM
    H2 --> NM

    H1 --> P1
    H2 --> P1

    H1 --> PB
    H2 --> PB

    P1 --> PS

    classDef flakeStyle stroke:#517599,stroke-width:2px,color:currentColor
    classDef hostStyle stroke:#5e81ac,stroke-width:2px,color:currentColor
    classDef profileStyle stroke:#8fbcbb,stroke-width:2px,color:currentColor
    classDef sharedStyle stroke:#d08770,stroke-width:2px,color:currentColor
    classDef moduleStyle stroke:#bf616a,stroke-width:2px,color:currentColor
    classDef secretStyle stroke:#bf616a,stroke-width:2px,color:currentColor
    classDef shellStyle stroke:#d08770,stroke-width:2px,color:currentColor

    class F flakeStyle
    class H1,H2 hostStyle
    class P1,PB profileStyle
    class PS sharedStyle
    class NM moduleStyle
    class S secretStyle
    class SH shellStyle
</code><div id="mermaid-container" role="dialog"><div id="mermaid-space"><div class="mermaid-content"></div></div></div></pre>
<hr>
<h1 id="sccl">sccl.*<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#sccl" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>The main feature that keeps the config from sliding into <code>if hostname ==</code> hell.</p>
<p>In <code>core.nix</code> ALL options are defined, and they all default to <code>false</code>. Each module gates itself via <code>lib.mkIf config.sccl.&#x3C;feature>.enable</code>.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="nix" data-theme="github-light github-dark"><code data-language="nix" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{ config</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">,</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lib</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">,</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ... </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}:</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  config</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> lib</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">mkIf</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> config</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">sccl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">zapret</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">enable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    services</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">zapret</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { ... };</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre></figure>
<p>None of that <code>if hostName == "laptop" then false</code>. The module simply doesn’t turn on.</p>
<p>Full option list <em>(at the time of writing, idk if I’ll update it)</em>:</p>
<pre><code>sccl.ui.enable           # Nord theme + greetd
sccl.audio.enable        # pipewire
sccl.bluetooth.enable    # bluetooth + blueman
sccl.net.enable          # networkmanager + proxy + firewall
sccl.automount.enable    # udisks2 USB automount
sccl.secrets.enable      # sops-nix encrypted secrets
sccl.zapret.enable       # DPI bypass
sccl.flclashx.enable     # FlClashX proxy GUI
sccl.playground.enable   # Docker + dev tools
sccl.chaotic.enable      # chaotic-nyx repo
sccl.nix-ld.enable       # nix-ld for non-nixpkgs binaries
sccl.bootstrap           # bool: minimal install mode
</code></pre>
<p>A host config literally looks like this:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="nix" data-theme="github-light github-dark"><code data-language="nix" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">sccl</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  ui</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  audio</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  net</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  automount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># desktop only</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  zapret</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;       </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># desktop only</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  playground</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;   </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># desktop only</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  nix-ld</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">;</span></span></code></pre></figure>
<p>Adding a new host - copy the folder, tweak <code>configuration.nix</code>, generate <code>hardware-configuration.nix</code>, tweak <code>disko.nix</code>. The flake picks it up automatically.</p>
<p>The bootstrap profile solves the problem of installing on weak hardware. Deploy bootstrap (~1-2 GB), boot up, flip <code>sccl.bootstrap = false</code>, rebuild - and you have a full system. No need to wait for 100500 packages to compile from a USB stick that barely has space.</p>
<p>You can use nixos-anywhere for this, but bootstrap stays as a backup option.</p>
<hr>
<h1 id="keyboard-and-secrets">Keyboard and secrets<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#keyboard-and-secrets" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="layout">Layout<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#layout" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The keyboard layout is parametrized through flake arguments (<code>specialArgs</code>).</p>
<p>sacculos has an ergo-split on Vial firmware - <code>us,rulemak_vial</code>. aero15laptop is <code>colemak_dh,rulemak</code>. One <code>mkHost</code> in <code>flake.nix</code>, and all layers (system keyboard, XKB, WM) get the right layout.</p>
<p>At the WM level (niri) the layout is also passed as a parameter: Colemak-CAWS on the desktop, Colemak-DH-Wide on the laptop. This is nice when you have different keyboards with different firmware and different layouts on different machines.</p>
<p>No copypaste, no hacks.</p>
<h2 id="secrets">Secrets<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#secrets" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>SSH keys, GPG - everything is encrypted via <a href="https://github.com/Mic92/sops-nix" class="external">sops-nix<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> with an age key and lives right in the repo at <code>secrets/common.yaml</code>.</p>
<p>On rebuild with <code>sccl.secrets.enable = true</code>, keys decrypt into <code>~/.ssh/</code>, GPG imports automatically.</p>
<p>The only thing to keep separately - the private age key. It lives in my password manager, not in the repo.</p>
<hr>
<h1 id="the-catch">The catch?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-catch" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>I’ve got a few.</p>
<p><strong>Nix is a different language.</strong> Not just config syntax, but a full functional language. Lazy, dynamically typed, with its own weird standard library. The learning curve is a sheer wall.</p>
<p><strong>Documentation.</strong> It exists, but… You learn a lot by trial and error and reading other people’s configs. <a href="https://ianthehenry.com/posts/how-to-learn-nix/" class="external">How to Learn Nix<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> by Ian Henry is literally a 50+ post diary of a guy learning Nix.</p>
<p><strong>Build time.</strong> The first build can take a while, especially if little is cached. But Hydra caches almost everything, so subsequent builds are fast.</p>
<p><strong>Flakes experimental since 2021.</strong> Still. But everyone uses them. The status is more of a formality at this point.</p>
<p><strong>Overkill for simple things.</strong> If you have one server with nginx - you don’t need NixOS. But if you have 3+ machines, custom kernel patches, your own software built from git - it’s worth it.</p>
<p>Overall: yeah, with enough skill you can still make a mess. But the sccl_nix architecture at least tries to prevent it. <em>I believe in it (trying to)</em></p>
<p><small>so far so good. desktop and laptop live on the same config with the same profile. I always get the same working environment and don’t even notice whether I’m on the laptop or PC <em>(my posture notices)</em> - everything is always configured identically.</small><br></p> ]]></description>
    <pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>sccl_nix: Декларативный рай</title>
    <link>https://shiza.sccl.cc/ru/it/os/sccl_nix</link>
    <guid>https://shiza.sccl.cc/ru/it/os/sccl_nix</guid>
    <description><![CDATA[ <h1 id="если-эта-shitpost-like-статья-выглядит-как-шиза">Если эта shitpost-like статья выглядит как шиза<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#если-эта-shitpost-like-статья-выглядит-как-шиза" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Нормальная дока на ангельском: <a href="https://github.com/scclie/sccl_nix" class="external">github/scclie/sccl_nix/README.md<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a></p>
<p><small>тут я просто вываливаю мысли</small><br></p>
<hr>
<h1 id="ная-зачем"><del>На*&#x26;я</del> Зачем?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#ная-зачем" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Вот мне раньше, после 6 лет юза онли арча - тоже не было понятно.</p>
<p>Мол наплодили ещё какой-то попсовый дистр, аля федоры, манжары и прочих качи.</p>
<p>А потом мне захотелось его установить…</p>
<p>После этого, видимо, мир перевернулся.</p>
<p>Куча проблем, что возникали на обычных дистрах - решились моментами. Мне даже ранее не казалось что это проблемы - за годы использования стандартных дистров ловишь консенсус, просто привыкаешь к тому как над в них работать, чтоб всё было заебись.</p>
<p>Но то заебись и это заебись - это совершенно два разных заебись.</p>
<p>Самое смешное - проблемы, о которых даже не думаешь как о проблемах. Конфиги расползаются по <code>/etc</code>, пакеты ставятся и забываются, systemd-юниты включаются ручками и теряются. Через полгода система - чёрный ящик. Ты понятия не имеешь, как она дошла до жизни такой.</p>
<p>Хуярить ебучий git для <code>/etc</code>? fr</p>
<p>NixOS предлагает обратное: ты описываешь конечный результат. Все пакеты, конфиги, юзеры, сервисы - один билд, одно состояние.</p>
<p>Хочешь вторую машину? Те же конфиги, другой хост. Хочешь откатиться? Въёбываешь предыдущую генерацию. Каждый билд лежит в <code>/nix/store</code>, ничего не раскидывается по <code>/usr</code> и <code>/etc</code> как попало.</p>
<p>Отдельный кайф с шеллами. Часто надо поставить “одноразовые проги” - поставил, юзанул, забыл. А оно лежит в системе ;k</p>
<p>Никс предлагает <code>nix shell</code> - накидал любых прог, поюзал, забыл - само удалится при сборке мусора. Зависимости тянутся только в твой шелл из <code>/nix/store</code>.</p>
<p>Можно просто <code>nix run nixpkgs#imagemagick -- convert ...</code> и оно скачается, исполнится, и при следующем garbage collector исчезнет. Никаких следов.</p>
<p><small>ручками, если хочш почистить - <code>nix-collect-garbage -d</code> втуливаешь.</small><br></p>
<hr>
<h1 id="откуда-вообще-nix">Откуда вообще Nix<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#откуда-вообще-nix" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>В 2006 году крутой мужик по имени Eelco Dolstra защитил PhD в Utrecht University. Тема диссертации: “<a href="https://edolstra.github.io/pubs/phd-thesis.pdf" class="external">The Purely Functional Software Deployment Model<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>”</p>
<p>Идея была радикальной: а что если пакет - это чистая функция?</p>
<p>Вход: исходники + зависимости + флаги компиляции. Выход: бинарник.</p>
<p>Одинаковый вход = одинаковый выход. Всегда. На любой машине. Без “у меня собирается а у тебя нет”.</p>
<p>Для этого <code>/nix/store</code> устроен гениально просто. Каждый пакет лежит в своей директории, и путь включает хеш всех входных данных:</p>
<pre><code>/nix/store/5mq2jcn36ldd97xrcnf3jg8irnfp5xqr-fish-4.0.1/
</code></pre>
<p>Хеш зависит от исходников, зависимостей, флагов компиляции, версии компилятора. ВСЕГО.</p>
<p>Любое изменение - другой хеш - другой путь. Два пакета не конфликтуют потому что лежат в разных директориях. Атомарные обновления. Никакого “я поставил новую версию и поломал старую”.</p>
<p>Эт совершенно другая парадигма, по сравнению с другими пакетниками.</p>
<p>Экосистема nixpkgs сейчас - <a href="https://repology.org/repositories/graphs" class="external">крупнейший в мире репозиторий пакетов<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. И всё это собирается одной фермой Hydra и кешируется в бинарный кеш.</p>
<p><small>на самом деле немного обман цифр - очень много пакетов разбиты на куча маленьких. Если смотреть по реальному софту - nixpkgs меньше чем тот же aur.</small><br></p>
<hr>
<h1 id="императивное-vs-декларативное">Императивное vs декларативное<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#императивное-vs-декларативное" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Как вываливали в какой-то очень старой мысли ещё на заре придумывания Nix - пакетники можно сравнить с языками программирования.</p>
<p>Есть <a href="https://en.wikisource.org/wiki/Functional_Package_Management_with_Guix" class="external">статья “Functional Package Management with Guix”<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> где эта мысль хорошо раскрыта, но идея звучала и раньше.<br>
<small>мне лень искать, где ещё, но было.</small><br></p>
<p><strong>Императивные</strong> пакетники (apt, yum, pacman) - это как процедурное программирование. Ты говоришь системе ЧТО ДЕЛАТЬ: <code>apt install nginx</code>, потом <code>systemctl enable nginx</code>, потом ручками конфиг в <code>/etc/nginx/nginx.conf</code>. Последовательность команд меняет состояние.</p>
<p><strong>Декларативные</strong> (Nix, Guix) - это как функциональное. Ты описываешь КАКОЙ РЕЗУЛЬТАТ нужен:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="nix" data-theme="github-light github-dark"><code data-language="nix" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">services</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">nginx</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  virtualHosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"example.com"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    root</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> "/var/www/example.com"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">;</span></span></code></pre></figure>
<p>Не “поставь nginx и настрой”. А “вот такой nginx должен быть в системе”.</p>
<p>Разница фундаментальная. В императивном мире ты теряешь историю - система помнит текущее состояние, но не помнит как она в него пришла. В декларативном - каждый билд это функция от конфига. Хочешь повторить на другой машине - тот же конфиг, тот же результат.</p>
<p>а дальше чо? Какой-то более высокоуровневый подход, более абстрактный? (иль мега абстрактный llm-based chat interface??????? - ai problem resolver :D )</p>
<hr>
<h1 id="экосистема">Экосистема<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#экосистема" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Сам по себе NixOS решает только часть проблемы. Системный уровень. А есть ещё куча всего, что хочется в декларативщину.</p>
<h2 id="flakes">flakes<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#flakes" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Экспериментальная фича с 2021 года. До сих пор экспериментальная.</p>
<p>Но по факту все давно сидят на флейках. Они решают две главные проблемы старого Nix: версионирование зависимостей (через <code>flake.lock</code>) и единый формат проекта.</p>
<p>До флейков был channels - глобальное состояние системы. Поменял канал - поменялось для всего. Никакой изоляции.</p>
<p>С флейками у каждого проекта свой <code>flake.lock</code>, который фиксирует конкретные версии ВСЕХ зависимостей. Как <code>package-lock.json</code> но для всей системы.</p>
<p>Флейк авто-дискаверит хосты из папок - кидаешь <code>hosts/&#x3C;hostname>/</code>, он сам подхватывает в <code>nixosConfigurations.&#x3C;hostname></code>. Никаких <code>if hostname ==</code> костылей на уровне дискаверинга.</p>
<h2 id="home-manager">home-manager<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#home-manager" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><a href="https://github.com/nix-community/home-manager" class="external">10k звёзд на гитхабе<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Позволяет декларативно управлять пользовательским окружением - дотфайлы, пользовательские пакеты, сервисы.</p>
<p>Раньше дотфайлы делали через GNU stow или bare git repo. home-manager позволяет всё то же самое, но в Nix-стиле: с опциями, с зависимостями, с иммутабельностью.</p>
<p>И главное - интегрируется с NixOS модулем. Один <code>nixos-rebuild switch</code> пересобирает и систему и пользовательское окружение.</p>
<h2 id="disko">disko<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#disko" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><a href="https://github.com/nix-community/disko" class="external">NixOS - это дистр где всё описывается как код. Кроме одного: разметка дисков при установке делается ручками. disko исправляет это печальное упущение.<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a></p>
<p>Достаточно один раз прописать конфиг разметки дисков, а не дрочить каждый раз <code>gparted</code><br>
Если хосты с одинаковыми дисками - можно спокойно реплиецировать.</p>
<h2 id="sops-nix">sops-nix<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#sops-nix" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Секреты (SSH-ключи, пароли, GPG) шифруются age-ключом и лежат прямо в репе. При билде расшифровываются в нужные места.</p>
<p>Раньше секреты хранили либо вне репы (и теряли), либо в зашифрованных git-репо (неудобно). sops-nix делает это прозрачно: секреты как часть конфига, но зашифрованы.</p>
<hr>
<h1 id="проблема-масштабирования">Проблема масштабирования<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#проблема-масштабирования" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>С NixOS есть одна засада. Ты начинаешь с одного хоста, всё красиво. Потом добавляешь второй. Потом третий.</p>
<p>И конфиг превращается в свалку из кучи исключений и т.п. (всё как в любом коде)</p>
<p>Есть и другие подходы:</p>
<ul>
<li><a href="https://github.com/hercules-ci/flake-parts" class="external">flake-parts<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - модульная система для флейков, позволяет разбивать конфиг на переиспользуемые модули. Хорош для больших проектов, но добавляет свой слой абстракции.</li>
<li><a href="https://github.com/snowfallorg/lib" class="external">snowfall-lib<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - библиотека для организации Nix-конфигов с авто-дискавери. Похожа на то что делаю я, но со своей философией.</li>
<li>Куча народу просто живёт с <code>if hostname</code> и не парится. Тоже варик, если хостов 1-2.</li>
</ul>
<p>Собственно, из этого и родился <a href="https://codeberg.org/scclie/sccl_nix/" class="external">sccl_nix.<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> - моя конфигурация, которая пытается не превратиться в помойку. (пару раз превращалась, но я очень стараюсь)<br>
Мне хотелось архитектуру, где добавление нового хоста - это создание папки и декларирование <code>sccl.*</code> опций. Без единого <code>if</code>.</p>
<hr>
<h1 id="архитектура-sccl_nix">Архитектура sccl_nix<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#архитектура-sccl_nix" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Дотфайлы разделены на три сущности.</p>
<p><strong>hosts</strong> - железо и системщина. Диски (disko), дрова GPU, сеть, системные пакеты. Всё что нельзя синкать между разными машинами. Авто-дискаверятся флейком из <code>hosts/</code>.</p>
<p><strong>profiles</strong> - пользователь со своим зоопарком прог, шеллом и user-level хламом. На одном хосте может быть сколько угодно профилей. Импортятся в конфиг хоста.</p>
<p><strong>shared</strong> - база для всех. Рыба, niri, alacritty, waybar, базовые CLI тулзы. Один раз настроил - импортнул во все профили.</p>
<p>Сейчас два хоста: <strong>sacculos</strong> (десктоп, AMD) и <strong>aero15laptop</strong> (ноут, NVIDIA). Два профиля: <strong>paper</strong> (основной, со всем зоопарком) и <strong>bootstrap</strong> (минимальный, только vim, nmtui и ssh для установки с флешки).<br>
<small>как пример реального юза.</small><br></p>
<p>Схема:</p>
<pre><button class="expand-button" aria-label="Expand mermaid diagram" data-view-component><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 1.06L2.56 7h10.88l-2.22-2.22a.75.75 0 011.06-1.06l3.5 3.5a.75.75 0 010 1.06l-3.5 3.5a.75.75 0 11-1.06-1.06l2.22-2.22H2.56l2.22 2.22a.75.75 0 11-1.06 1.06l-3.5-3.5a.75.75 0 010-1.06l3.5-3.5z"></path></svg></button><code class="mermaid" data-clipboard="&#x22;flowchart TD\n    F[\&#x22;❄️ flake.nix<br/>auto-discovery: hosts + shells\&#x22;]\n\n    NM[\&#x22;⚙️ nixos/modules/core.nix<br/>sccl.* options: conditional config\&#x22;]\n\n    H1[\&#x22;🖥️ hosts/sacculos<br/>desktop + amd gpu\&#x22;]\n    H2[\&#x22;💻 hosts/aero15laptop<br/>laptop + nvidia gpu\&#x22;]\n\n    P1[\&#x22;👤 profiles/paper<br/>main: steam, blender...\&#x22;]\n    PS[\&#x22;📦 profiles/shared<br/>base: fish, niri, waybar...\&#x22;]\n\n    PB[\&#x22;🪶 profiles/bootstrap<br/>minimal: nmtui, vim, ssh\&#x22;]\n\n    S[\&#x22;🔐 secrets/ sops-nix encrypted\&#x22;]\n    SH[\&#x22;🐚 shells/ auto-discovered devShells\&#x22;]\n\n    F --> H1\n    F --> H2\n    F --> SH\n\n    H1 --> NM\n    H2 --> NM\n\n    H1 --> P1\n    H2 --> P1\n\n    H1 --> PB\n    H2 --> PB\n\n    P1 --> PS\n\n    classDef flakeStyle stroke:#517599,stroke-width:2px,color:currentColor\n    classDef hostStyle stroke:#5e81ac,stroke-width:2px,color:currentColor\n    classDef profileStyle stroke:#8fbcbb,stroke-width:2px,color:currentColor\n    classDef sharedStyle stroke:#d08770,stroke-width:2px,color:currentColor\n    classDef moduleStyle stroke:#bf616a,stroke-width:2px,color:currentColor\n    classDef secretStyle stroke:#bf616a,stroke-width:2px,color:currentColor\n    classDef shellStyle stroke:#d08770,stroke-width:2px,color:currentColor\n\n    class F flakeStyle\n    class H1,H2 hostStyle\n    class P1,PB profileStyle\n    class PS sharedStyle\n    class NM moduleStyle\n    class S secretStyle\n    class SH shellStyle&#x22;">flowchart TD
    F["❄️ flake.nix&#x3C;br/>auto-discovery: hosts + shells"]

    NM["⚙️ nixos/modules/core.nix&#x3C;br/>sccl.* options: conditional config"]

    H1["🖥️ hosts/sacculos&#x3C;br/>desktop + amd gpu"]
    H2["💻 hosts/aero15laptop&#x3C;br/>laptop + nvidia gpu"]

    P1["👤 profiles/paper&#x3C;br/>main: steam, blender..."]
    PS["📦 profiles/shared&#x3C;br/>base: fish, niri, waybar..."]

    PB["🪶 profiles/bootstrap&#x3C;br/>minimal: nmtui, vim, ssh"]

    S["🔐 secrets/ sops-nix encrypted"]
    SH["🐚 shells/ auto-discovered devShells"]

    F --> H1
    F --> H2
    F --> SH

    H1 --> NM
    H2 --> NM

    H1 --> P1
    H2 --> P1

    H1 --> PB
    H2 --> PB

    P1 --> PS

    classDef flakeStyle stroke:#517599,stroke-width:2px,color:currentColor
    classDef hostStyle stroke:#5e81ac,stroke-width:2px,color:currentColor
    classDef profileStyle stroke:#8fbcbb,stroke-width:2px,color:currentColor
    classDef sharedStyle stroke:#d08770,stroke-width:2px,color:currentColor
    classDef moduleStyle stroke:#bf616a,stroke-width:2px,color:currentColor
    classDef secretStyle stroke:#bf616a,stroke-width:2px,color:currentColor
    classDef shellStyle stroke:#d08770,stroke-width:2px,color:currentColor

    class F flakeStyle
    class H1,H2 hostStyle
    class P1,PB profileStyle
    class PS sharedStyle
    class NM moduleStyle
    class S secretStyle
    class SH shellStyle
</code><div id="mermaid-container" role="dialog"><div id="mermaid-space"><div class="mermaid-content"></div></div></div></pre>
<hr>
<h1 id="sccl">sccl.*<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#sccl" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Главная фича, которая не даёт конфигу скатиться в свалку <code>if hostname ==</code>.</p>
<p>В <code>core.nix</code> определены ВСЕ опции, и все они по дефолту <code>false</code>. Каждый модуль гейтит сам себя через <code>lib.mkIf config.sccl.&#x3C;feature>.enable</code>.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="nix" data-theme="github-light github-dark"><code data-language="nix" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{ config</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">,</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> lib</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">,</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ... </span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}:</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  config</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> lib</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">mkIf</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70"> config</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">sccl</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">zapret</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">.</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">enable</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    services</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">zapret</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> { ... };</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  };</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre></figure>
<p>Никаких <code>if hostName == "laptop" then false</code>. Модуль просто не включается.</p>
<p>Полный список опций <em>(на момент написания статьи, хз буду ли обновлять)</em>:</p>
<pre><code>sccl.ui.enable           # Nord-тема + greetd
sccl.audio.enable        # pipewire
sccl.bluetooth.enable    # bluetooth + blueman
sccl.net.enable          # networkmanager + прокси + firewall
sccl.automount.enable    # udisks2 автоподключение флешек
sccl.secrets.enable      # sops-nix шифрованные секреты
sccl.zapret.enable       # обход DPI
sccl.flclashx.enable     # FlClashX GUI для прокси
sccl.playground.enable   # Docker + dev tools
sccl.chaotic.enable      # chaotic-nyx репо
sccl.nix-ld.enable       # nix-ld для бинарников не из nixpkgs
sccl.bootstrap           # bool: минимальный режим установки
</code></pre>
<p>Конфиг хоста выглядит буквально так:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="nix" data-theme="github-light github-dark"><code data-language="nix" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">sccl</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  ui</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  audio</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  net</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  automount</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;    </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># только на десктопе</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  zapret</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;       </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># только на десктопе</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  playground</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;   </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D"># только на десктопе</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">  nix-ld</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">enable</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span><span style="--shiki-light:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">;</span></span></code></pre></figure>
<p>Добавление нового хоста - скопировал папку, поправил <code>configuration.nix</code>, сгенерил <code>hardware-configuration.nix</code>, поправил <code>disko.nix</code>. Флейк сам его подхватит.</p>
<p>Bootstrap-профиль решает проблему установки на слабом железе. Накатил bootstrap (~1-2 GB), загрузился, переключил <code>sccl.bootstrap = false</code>, перебилдил - и вот полная система. Не надо ждать пока на ноуте соберётся 100500 пакетов с флешки где ещё и места нет.</p>
<p>Для этого можно юзать nixos-anywhere, но bootstrap остаётся как бекап-вариант.</p>
<hr>
<h1 id="клава-и-секреты">Клава и секреты<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#клава-и-секреты" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<h2 id="раскладка">Раскладка<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#раскладка" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Раскладка параметризована через аргументы флейка (<code>specialArgs</code>).</p>
<p>У sacculos эрго-сплит на Vial прошивке - <code>us,rulemak_vial</code>. У aero15laptop - <code>colemak_dh,rulemak</code>. Один <code>mkHost</code> в <code>flake.nix</code>, и все слои (системная клава, XKB, WM) получают правильную раскладку.</p>
<p>На уровне WM (niri) раскладка тоже передаётся параметром: на десктопе Colemak-CAWS, на ноуте Colemak-DH-Wide. Это удобно когда у тебя разные клавиатуры с разной прошивкой и разными раскладками на разных машинах.</p>
<p>Без копипасты и костылей.</p>
<h2 id="секреты">Секреты<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#секреты" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>SSH-ключи, GPG - всё зашифровано через <a href="https://github.com/Mic92/sops-nix" class="external">sops-nix<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> с age-ключом и лежит прямо в репе в <code>secrets/common.yaml</code>.</p>
<p>При ребилде с <code>sccl.secrets.enable = true</code> ключи сами расшифровываются в <code>~/.ssh/</code>, GPG импортится автоматом.</p>
<p>Всё что над хранить отдельно - приватный age-ключ. Он у меня в менеджере паролей, не в репе.</p>
<hr>
<h1 id="подвох">Подвох?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#подвох" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<p>Их есть у меня.</p>
<p><strong>Nix - это другой язык.</strong> Не просто синтаксис конфигов, а полноценный функциональный язык. Ленивый, с динамической типизацией, со своей странной стандартной библиотекой. Кривая обучения - отвесная стена.</p>
<p><strong>Документация.</strong> Она есть, но… Многое узнаёшь методом тыка и чтения чужих конфигов. <a href="https://ianthehenry.com/posts/how-to-learn-nix/" class="external">How to Learn Nix<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> от Ian Henry - это буквально дневник на 50+ постов о том как чувак учил Nix.</p>
<p><strong>Время билда.</strong> Первый билд может идти долго, особенно если мало что закешировано. НоHydra кеширует почти всё, т.ч. последующие билды быстрые.</p>
<p><strong>Flakes experimental с 2021.</strong> До сих пор. Но все используют. Статус скорее формальность на этом этапе.</p>
<p><strong>Оверкилл для простых вещей.</strong> Если у тебя один сервер с nginx - тебе не нужен NixOS. Но если у тебя 3+ машины, кастомные патчи ядра, свой софт собранный из git - оно того стоит.</p>
<p>В целом: да, при большом скилле и тут можно насрать. Но архитектура sccl_nix хотя бы пытается этого не допустить. <em>я в это верю (пытаюсь)</em></p>
<p><small>пока полёт нормальный. десктоп и ноут живут на одном конфиге с одинаковым профилем. Я получаю всегда одинаковую рабочую среду и даже не замечаю сижу ли я за ноутом, иль за пк <em>(моя осанка замечает)</em> - всё всегда настроено одинакого.</small><br></p> ]]></description>
    <pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate>
  </item><item>
    <title>Shaking. Visuomotor Feedback Loop</title>
    <link>https://shiza.sccl.cc/en/Games/Aim/Shaking</link>
    <guid>https://shiza.sccl.cc/en/Games/Aim/Shaking</guid>
    <description><![CDATA[ <p>The majority of tracking novices experience shakiness. During aiming, they perform movements in jerks. Each mouse flick is a separate action, followed by result analysis and another flick. The crosshair moves crookedly - overreaction occurs, along with multiple unnecessary movements, which further complicates subsequent analysis.</p>
<h2 id="visuomotor-feedback-loop">Visuomotor Feedback Loop<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#visuomotor-feedback-loop" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>This entire phenomenon can be described by a single term - Visuomotor Feedback Loop. Essentially, this is an information processing cycle that the nervous system runs at maximum possible speed:</p>
<p>Loop stages:</p>
<ol>
<li>
<p>Visual analysis. The eye captures the current position of the target and its trajectory. Assessment of distance, movement vector, movement speed.</p>
</li>
<li>
<p>Information processing. The brain processes visual data and calculates the necessary motor command. Micro-calculation occurs: distance, route prediction, turn angle calculation, etc.</p>
</li>
<li>
<p>Command execution. Muscles receive the signal and perform the movement. The mouse moves to the desired position.</p>
</li>
<li>
<p>Repeat. The loop closes and starts again.</p>
</li>
</ol>
<p>Reactive tracking works precisely on the basis of this loop. This is a sequence of micro-corrections that occur at such speed that they appear as a single smooth action. Even pro players perform the same micro-corrections, but their interpolation (smoothing between cycles) reaches a level where the movement looks absolutely smooth - each micro-correction adds to the initial vector rather than being a new movement vector. Plus, an experienced system incorporates slight trajectory prediction, pre-calculating the target’s next position.</p>
<h2 id="novice-vs-professional">Novice vs Professional<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#novice-vs-professional" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Novice:</p>
<ul>
<li>Sees target → processes information slowly → movement calculation (often occurs incorrectly, with artifacts and unnecessary movements) → hand performs action → result analysis → waiting → next flick</li>
</ul>
<p>Noticeable gaps appear between loop cycles. Each flick is perceived as a separate, discrete operation rather than part of a continuous flow.</p>
<p>Professional:</p>
<ul>
<li>Sees target → fast accurate information processing → micro-correction, even if the initial movement was inaccurate → instant result analysis → next correction</li>
</ul>
<p>The professional’s loop spins <em>faster</em>. Unlike the novice, an experienced player is capable of qualitatively correcting their previous movements - for example, while previously moving in a straight line, they can add precise correction within the same movement vector such that it won’t look like a flick or shake. These micro-adjustments layer upon each other, creating the appearance of smooth, continuous tracking.</p>
<h2 id="skill-development-mechanism">Skill Development Mechanism<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#skill-development-mechanism" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Many hours of training in aim trainers allow for optimization of the speed and effectiveness of the visuomotor feedback loop. The system learns to:</p>
<ul>
<li>
<p>Analyze visual flow faster</p>
</li>
<li>
<p>Analyze qualitatively - form more precise motor commands without unnecessary movements</p>
</li>
</ul>
<p>Result: micro-corrections that previously were visible as distinct jerks begin to merge into a single smooth movement.</p>
<h2 id="conclusion">Conclusion<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#conclusion" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Shakiness and jerks (flickshots with noticeable intervals) are signs of insufficient speed and accuracy of information processing. The system simply doesn’t manage to perform the necessary number of micro-corrections and has to make macro-movements.</p>
<p>Smooth tracking is the result of high speed and loop efficiency. Micro-corrections occur so frequently that they become practically invisible.</p>
<p>Tracking skill development is acceleration of the visuomotor feedback loop and optimization of its efficiency. Over time: better prediction, better interpolation, better synchronization, better analysis quality, artifacts disappear, etc.</p> ]]></description>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Тряска. Зрительно-моторная петля</title>
    <link>https://shiza.sccl.cc/ru/Games/Aim/Shaking</link>
    <guid>https://shiza.sccl.cc/ru/Games/Aim/Shaking</guid>
    <description><![CDATA[ <h2 id="проблема-микро-флик-флик-флик">Проблема микро-флик-флик-флик<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#проблема-микро-флик-флик-флик" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>У бол-ва новичков в трекинге присутсвует тряска. Пре аиминге они совершают движения рывками. Каждый флик мышкой - это отдельное действие, после которого следует анализ результата и очередной флик Прицел перемещается криво - происодит оверреакт, и множетсво лишних движений, что ещё больше усложняет дальнейший анализ.</p>
<h2 id="зрительно-моторная-петля">Зрительно-моторная петля<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#зрительно-моторная-петля" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Все это явление можно описать одним термином - Зрительно-моторная петля (visuomotor feedback loop). По сути, это цикл обработки информации, который нервная система прогоняет на максимально возможной скорости:</p>
<p>Этапы петли:</p>
<ol>
<li>
<p>Визуальный анализ. Глаз фиксирует текущее положение цели и ее траекторию. Оценка дистанции, вектора движения, скорости перемещения.</p>
</li>
<li>
<p>Обработка информации. Мозг обрабатывает визуальные данные и рассчитывает необходимую моторную команду. Происходит микро-калькуляция: дистанция, предугадание маршрута, расчет угла поворота и т.п.</p>
</li>
<li>
<p>Исполнение команды. Мышцы получают сигнал и совершают движение. Мышь перемещается в нужную позицию.</p>
</li>
<li>
<p>Повтор. Петля замыкается и начинается заново.</p>
</li>
</ol>
<p>Реактивный трекинг работает именно на основе этой петли. Этопоследовательность микро-коррекций, которые происходят с такой скоростью, что кажутся единым плавным действием. Даже про-игроки совершают те же микро-коррекции, только их интерполяция (сглаживание между циклами) достигает уровня, при котором движение выглядит абсолютно гладким, каждая микро коррекция плюсуется к инишиал вектору, а не является новым вектором движения. Плюс опытная система закладывает небольшое предугадание траектории, допредсказывая следующую позицию цели.</p>
<h2 id="новичок-vs-профессионал">Новичок vs Профессионал<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#новичок-vs-профессионал" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Новичок:</p>
<ul>
<li>Видит таргет → долго обрабатывает информацию → расчет движения (часто происходит некорректно, с артефактами, лишними движениями) → рука совершает действие → анализ результата → ожидание → следующий флик</li>
</ul>
<p>Между циклами петли возникают ощутимые пробелы. Каждый флик воспринимается как отдельная, дискретная операция, а не как часть непрерывного потока.</p>
<p>Профессионал:</p>
<ul>
<li>Видит таргет → быстрая точная обработка информации → микро-коррекция, даже если первоначальное движение было неточным → мгновенный анализ результата → следующая коррекция</li>
</ul>
<p>Петля у профессионала крутится <em>быстрее</em>. В отличии от новичка - опытный игрок способен качественно корректировать прошлые свои движения - например двигаясь ранее по прямой - он способен в рамках того же вектора движения добавлять точную коррекцию, что это не будет выглядить как флик или тряска. Эти микро-регулировки накладываются друг на друга, создавая видимость гладкого, непрерывного трекинга.</p>
<h2 id="механизм-развития-навыка">Механизм развития навыка<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#механизм-развития-навыка" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Многочасовая тренировка в аим-тренерах позволяет оптимизировать скорости и эффективность зрительно-моторной петли. Система обучается:</p>
<ul>
<li>
<p>Анализировать визуальный поток быстрее</p>
</li>
<li>
<p>Качественно анализировать - формировать более точные моторные команды, без лишних движений</p>
</li>
</ul>
<p>Результат: микро-коррекции, которые раньше были видны как отчетливые рывки, начинают сливаться в единое гладкое движение.</p>
<h2 id="итог">Итог<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#итог" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Тряска и рывки (flickshot с заметными интервалами) - это признак недостаточной скорости и точности обработки информации. Система просто не успевает совершить необходимое количество микро-коррекций, и приходится делать макро-движения.</p>
<p>Плавный трекинг - это результат высокой скорости и эффективности петли. Микро-коррекции происходят столь часто, что становятся практически незаметны.</p>
<p>Развитие навыка трекинга - это ускорение зрительно-моторной петли и оптимизация ее эффективности. Со временем лучше прогнозирование, лучше интерполяция, лучше синхронизация, лучше качество анализа, пропадают артефакты и т.п.</p> ]]></description>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>How to Make a Good Mousepad Review</title>
    <link>https://shiza.sccl.cc/en/Peripheral/Mousepads/How-to-make-a-good-mousepad-review</link>
    <guid>https://shiza.sccl.cc/en/Peripheral/Mousepads/How-to-make-a-good-mousepad-review</guid>
    <description><![CDATA[ <h1 id="tips-for-accurately-explaining-mousepad-characteristics">Tips for accurately explaining mousepad characteristics:<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#tips-for-accurately-explaining-mousepad-characteristics" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<hr>
<h2 id="1-surface-texture">1. Surface Texture<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#1-surface-texture" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Abrasive</strong> / <strong>Medium Abrasive</strong> / <strong>Smooth</strong></li>
<li>Sandpaper-like or not</li>
<li>How it interacts with skin and clothing sleeves<br>
<img src="../../../pictures/Peripheral/k83_texture.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="2-friction-properties">2. Friction Properties<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#2-friction-properties" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Dynamic Friction (Glide):</strong></p>
<ul>
<li>Resistance when the mouse is already in motion</li>
<li>The lower it is, the easier the mouse glides after the initial movement<br>
<a href="../../../pictures/Peripheral/cisapadv2.mov" class="internal" data-slug="pictures/Peripheral/cisapadv2.mov"><img src="../../../pictures/Peripheral/cisapadv2.png" width="100" height="auto" alt="cisapadv2" loading="lazy"></a></li>
</ul>
<p><strong>Static Friction (Stopping Power):</strong></p>
<ul>
<li>Resistance at the moment you start moving the mouse</li>
<li>The higher it is, the easier it is to stop the mouse precisely, but the harder it is to initiate movement<br>
<img src="../../../pictures/Peripheral/mousepad_friciton_test.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="3-glide-characteristics">3. Glide Characteristics<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#3-glide-characteristics" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Mudpad Effect</strong>: High static + low dynamic friction == the mouse feels stuck at the start but glides smoothly afterward</li>
<li><strong>Consistent Glide</strong>: Minimal difference between static and dynamic friction<br>
<img src="../../../pictures/Peripheral/old_mousepad.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="4-speed-rating-conventional">4. Speed Rating (Conventional)<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#4-speed-rating-conventional" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Control</strong>: High static and dynamic resistance, often a “mudpad,” but not always; the meta is often defined by static friction.</li>
<li><strong>Mid (Balanced)</strong>: Moderate static and dynamic resistance, suitable for everything.</li>
<li><strong>Speed</strong>: Low resistance, heavy on tracking.<br>
<em>You can play any game on any pad with enough skill and dedication; it’s primarily a question of average comfort.</em></li>
</ul>
<h2 id="5-xy-axis-consistency">5. X/Y Axis Consistency<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#5-xy-axis-consistency" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li>Is there a difference between left-right and up-down movement?</li>
<li>Ideally, the X/Y ratio should be 1:1.</li>
</ul>
<h2 id="6-durability--maintenance">6. Durability &#x26; Maintenance<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#6-durability--maintenance" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Moisture Resistance:</strong></p>
<ul>
<li>A water absorption test usually indicates how quickly the pad will get dirty in general.<br>
<img src="../../../pictures/Peripheral/mat_water_resist.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<p><strong>How to clean?</strong></p>
<ul>
<li>Some pads are picky about the choice of cleaning products.<br>
<img src="../../../pictures/Peripheral/ins.gif" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="7-base-construction">7. Base Construction<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#7-base-construction" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Firmness Level:</strong></p>
<ul>
<li><strong>X-Soft</strong>: Significant squish, can be pressed down almost to the desk.</li>
<li><strong>Soft</strong>: Noticeable, springy give.</li>
<li><strong>Mid</strong>: Firm support, almost no compression.<br>
<img src="../../../pictures/Peripheral/xsoft.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="8-surface-composition">8. Surface Composition<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#8-surface-composition" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li>Weave type (Tight, Jacquard, Hybrid, Cordura, Textured)</li>
<li>Material quality and stitching quality on the edges</li>
<li>Base material and how well it grips the desk<br>
<img src="../../../pictures/Peripheral/raiden.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="9-comparative-analysis">9. Comparative Analysis<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#9-comparative-analysis" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li>Comparison with other popular models (Artisan, LGG, MGG, X-Raypad)</li>
<li>Similarities in:
<ul>
<li>Speed and glide characteristics</li>
<li>Texture and tactile feel</li>
<li>Overall user experience</li>
</ul>
</li>
<li>Example: “Similar to the Artisan Raiden, but a bit slower” or “Feels like the G-SR-SE, but not as muddy”</li>
</ul>
<blockquote class="callout example" data-callout="example">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>Additional Tips: </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<ol>
<li>Test with different mouse skates for a complete picture.</li>
</ol>
<ul>
<li>It might be the case that:</li>
<li>Skates A are faster on Pad X and slower on Pad Y.</li>
<li>Skates B are faster on Pad Y and slower on Pad X.</li>
</ul>
<ol start="2">
<li>How the glide changes over time. Some pads <em>break in</em> after a week and feel different.</li>
<li>What it’s best suited for (Arena shooters or tactical/angle-holding games. Better for Tracking, Switching, Click-timing, or Universal).</li>
</ol>
</div>
</div>
</blockquote>
<blockquote class="callout tip" data-callout="tip">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>Good Example </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<p><a href="https://docs.google.com/spreadsheets/d/159aQ_JsUOg-KDLwOGdM8WyOEgfaDkmsb6Vlxnkw23-c/htmlview#gid=907849023" class="external">9Sanya’s Spreadsheet<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a><br>
<img src="../../../pictures/Peripheral/cisa_ss.png" width="auto" height="auto" alt="" loading="lazy"></p>
</div>
</div>
</blockquote> ]]></description>
    <pubDate>Thu, 25 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Как сделать хороший обзор на коврик для мыши</title>
    <link>https://shiza.sccl.cc/ru/Peripheral/Mousepads/How-to-make-a-good-mousepad-review</link>
    <guid>https://shiza.sccl.cc/ru/Peripheral/Mousepads/How-to-make-a-good-mousepad-review</guid>
    <description><![CDATA[ <blockquote class="callout info" data-callout="info">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>This page on English: <a href="../../../en/Peripheral/Mousepads/How-to-make-a-good-mousepad-review" class="internal alias" data-slug="en/Peripheral/Mousepads/How-to-make-a-good-mousepad-review">EN How to make a good mousepad review</a></p></div>
                  
                </div>
</blockquote>
<h1 id="типсы-на-грамотное-объяснение-характеристик-коврика-для-мыши">Типсы на грамотное объяснение характеристик коврика для мыши:<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#типсы-на-грамотное-объяснение-характеристик-коврика-для-мыши" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h1>
<hr>
<h2 id="1-текстура-поверхности">1. Текстура поверхности<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#1-текстура-поверхности" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Абразивная</strong> / <strong>Средняя абразивная</strong> / <strong>Гладкая</strong></li>
<li>Наждак/не наждак</li>
<li>Дружит ли с кожей и рукавом<br>
<img src="../../../pictures/Peripheral/k83_texture.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="2-силы-трения">2. Силы трения<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#2-силы-трения" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Динамическое трение (скольжение):</strong></p>
<ul>
<li>Сопротивление, когда мышь уже в движении</li>
<li>Чем ниже — тем проще она несется после старта<br>
<a href="../../../pictures/Peripheral/cisapadv2.mov" class="internal" data-slug="pictures/Peripheral/cisapadv2.mov"><img src="../../../pictures/Peripheral/cisapadv2.png" width="100" height="auto" alt="cisapadv2" loading="lazy"></a></li>
</ul>
<p><strong>Статическое трение (остановка):</strong></p>
<ul>
<li>Сопротивление в момент, начала движения мышью</li>
<li>Чем выше — тем проще стоппить мышь, но сложнее стартовать<br>
<img src="../../../pictures/Peripheral/mousepad_friciton_test.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="3-характер-скольжения">3. Характер скольжения<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#3-характер-скольжения" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Эффект болота</strong>: Высокое статическое + низкое динамическое трение == мышь вязнет на старте, но потом неплохо скользит</li>
<li><strong>Ровное скольжение</strong>: Минимальная разница между статикой и динамикой<br>
<img src="../../../pictures/Peripheral/old_mousepad.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="4-скоростной-рейтинг-условный">4. Скоростной рейтинг (условный)<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#4-скоростной-рейтинг-условный" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li><strong>Контрольный (Control)</strong>: Высокое статик и динамик сопротивление, зачастую болото, но не всегда, мета статики</li>
<li><strong>Средний (Mid)</strong>: Умеренное статик и динамик сопротивление, подходит для всего</li>
<li><strong>Быстрый (Speed)</strong>: Низкое сопротивление, трекинг хеви<br>
<em>на любом ковре при желеании и достаточной скиллухе можно играть что угодно, вопрос удобства в среднем</em></li>
</ul>
<h2 id="5-равенство-по-осям-xy">5. Равенство по осям X/Y<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#5-равенство-по-осям-xy" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li>Есть ли разница между движением влево-вправо и вверх-вниз</li>
<li>В идеале соотношение X/Y</li>
</ul>
<h2 id="6-живучесть">6. Живучесть<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#6-живучесть" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Сопротивление влаге:</strong></p>
<ul>
<li>Тест на впитываемость влаги обычно отражает насколько быстро ковёр в целом будет загрязняться<br>
<img src="../../../pictures/Peripheral/mat_water_resist.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<p><strong>Чем мыть?</strong></p>
<ul>
<li>Некоторые ковры придирчивы к выбору средств для мытья<br>
<img src="../../../pictures/Peripheral/ins.gif" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="7-подложка-база">7. Подложка (база)<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#7-подложка-база" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><strong>Жесткость:</strong></p>
<ul>
<li><strong>XSoft</strong>: Сильно проминается, можно вдавить чуть ли не до стола</li>
<li><strong>Soft</strong>: Чувствуется упругая податливость</li>
<li><strong>Mid</strong>: Твердая поддержка, почти не прогибается<br>
<img src="../../../pictures/Peripheral/xsoft.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="8-состав-поверхности">8. Состав поверхности<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#8-состав-поверхности" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li>Тип плетения (плотное, жаккард, гибридное, кордура, рельефное)</li>
<li>Качество материала и прошивки краёв</li>
<li>Материал основы и насколько хорошо она держится за стол<br>
<img src="../../../pictures/Peripheral/raiden.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ul>
<h2 id="9-сравнительный-анализ">9. Сравнительный анализ<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#9-сравнительный-анализ" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<ul>
<li>Сравнениее с другими популярными моделями (Artisan, lgg, mgg, x-raypad)</li>
<li>Сходства по:
<ul>
<li>Скорости и характеру скольжения</li>
<li>Тактильным ощущениям от текстуры</li>
<li>Общему впечатлению от использования</li>
</ul>
</li>
<li>Пример: “Похож на Artisan Raiden, но чуть меленее” или “Ощущения как от G-SR-SE, но не такой болотистый”</li>
</ul>
<blockquote class="callout example" data-callout="example">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>Доп Типсы: </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<ol>
<li>Тест с разными глайдами на мышке для полной картины</li>
</ol>
<ul>
<li>может быть такое что</li>
<li>глайды A быстрее на коврике Х и медленее на коврике Y</li>
<li>глайды B быстрее на коврике Y и медленее на коврике X</li>
</ul>
<ol start="2">
<li>Характер глайда со временем, некоторые ковры через неделю <em>приглаживаются</em> и ощущаются иначе</li>
<li>Для чего он подходит больше всего (для ареновых шутеров или угловых. Лучше для трекинга, свитчинга, клинтинга или универсал)</li>
</ol>
</div>
</div>
</blockquote>
<blockquote class="callout tip" data-callout="tip">
<div class="callout-title">
                  <div class="callout-icon"></div>
                  <div class="callout-title-inner"><p>Хороший пример </p></div>
                  
                </div>
<div class="callout-content">
<div class="callout-content-inner">
<p><a href="https://docs.google.com/spreadsheets/d/159aQ_JsUOg-KDLwOGdM8WyOEgfaDkmsb6Vlxnkw23-c/htmlview#gid=907849023" class="external">Таблица 9Sanya<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> <img src="../../../pictures/Peripheral/cisa_ss.png" width="auto" height="auto" alt="" loading="lazy"></p>
</div>
</div>
</blockquote> ]]></description>
    <pubDate>Thu, 25 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>The Casualization of Aim - Why &quot;Pure&quot; Aim Skill Doesn&#039;t Decide Competitive Games</title>
    <link>https://shiza.sccl.cc/en/Games/Aim/Casualization-of-aim</link>
    <guid>https://shiza.sccl.cc/en/Games/Aim/Casualization-of-aim</guid>
    <description><![CDATA[ <p><img src="../../../pictures/aim/ground_tracking.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>In the world of cyber-athletes, hardcore gamers, and tryhards, the cult of aiming has been elevated to an absolute. Hours on training maps, endless duels, grinding in aim trainers, and the pursuit of the perfect sensitivity, mouse, mousepad, skates, posture, resolution, FOV, etc., etc. – these are the rituals of the modern neo-pilgrim, who believes that reaction speed and mouse precision will lead them to the top-1. But in reality, a hardcore analysis of most games <strong>“casualizes”</strong> aim as a key skill. In most modern competitive multiplayer games, “pure” aim is deliberately placed in a subordinate position by the developers. Its potential is harshly limited by systems designed to <strong>level the playing field</strong> between players of different skill levels.</p>
<h2 id="the-great-equalizer-rng-movement-and-uncontrollable-factors"><strong>The Great Equalizer: RNG, Movement, and Uncontrollable Factors</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-great-equalizer-rng-movement-and-uncontrollable-factors" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The main enemy of raw aiming skill is randomness and limitations. Developers figured out long ago that a game where victory belongs entirely to the one with faster reactions and better mouse control is doomed to a niche audience <em>(the Quake series serves as an example)</em>. New players would leave, their asses sore, and matchmaking wouldn’t be able to handle the giant skill gap between the top and the bottom.</p>
<p>Hence, the crutch systems come into play:</p>
<ul>
<li>
<p><strong>Random Bullet Spread (RNG Spread):</strong> The most obvious example. Even in the most popular tactical shooter <em>Counter-Strike</em>, which is considered the esports benchmark <em>(well, at least it’s the most popular)</em>, bullets from any weapon while standing don’t fly strictly to one point, but within a certain circle of spread. In <em>Valorant</em>, this spread is even more pronounced. There’s no way to guaranteed control every bullet - you can only <strong>increase the probability</strong> of a hit. That’s a huge difference. Even a memorized spray pattern isn’t an exact science; it’s a ritual dance around random deviation.<br>
<img src="../../../pictures/aim/rng_spread.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>Movement Speed Control:</strong> A key but often overlooked aspect. In many games (<strong>CS2</strong>, <strong>Valorant</strong>, <strong>R6 Siege</strong>) the base movement speed of characters is relatively <strong>low and predictable</strong>. This is a powerful equalizing tool. <strong>Against a slow target, both a noob and a pro will aim almost equally effectively.</strong> The difference in their skill will be minimal. A tracking genius cannot reveal their full potential simply because the target doesn’t challenge them. The entire “spread” between bad and good aim shrinks. The difference becomes noticeable when a fast, unpredictable target appears. Here, a noob will simply get lost and won’t be able to hit it, while a cracked player, albeit with worse results than against a slow target, will still be able to track it. Thus, <strong>slow movement is a hidden mechanism that prevents a strong aimer from unleashing their full potential</strong> against a weak opponent.<br>
<img src="../../../pictures/aim/apex_glide.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>Moving Accuracy:</strong> A mechanic almost entirely removed from tactical shooters. In the same <em>CS</em>, firing while moving is synonymous with “miss.” This is a conscious decision that shifts the focus from shooting mechanics to positioning and tactics (counter-strafing, stop-shots).<br>
<img src="../../../pictures/aim/strafe_rng.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>Bloom, damage falloff, complex ballistics</strong> - all these are layers of RNG that add a factor of luck to the equation. Sometimes a wild shot will hit, while a perfectly aimed one will miss.<br>
<img src="../../../pictures/aim/ballistics.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
</ul>
<p>The core idea is to <strong>lower the skill ceiling for aim</strong> (to neutralize the aim advantage). The difference between a good and a god-like aimer is negated by the fact that <strong>even a god can’t guarantee a hit</strong> due to RNG, and against a slow target, their superiority won’t be as noticeable. A novice always has a chance for a random hit or a “lucky” headshot.</p>
<h2 id="high-ttk-vs-low-ttk-the-aim-dependency-paradox"><strong>High TTK vs Low TTK: The Aim-Dependency Paradox</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#high-ttk-vs-low-ttk-the-aim-dependency-paradox" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Here lies the main paradox that many fail to grasp.</p>
<ul>
<li>
<p><strong>Low TTK (Time To Kill):</strong> Characteristic of tactical shooters (CS2, Valorant, R6 Siege). A kill happens in 1-2 hits. It would seem that jewel-like aim is needed here to land headshots consistently. But in reality, <strong>aim is less important</strong> than it seems. The decisive factor is often not the pure shot, but: <strong>Positioning</strong> (you can catch an enemy by surprise, get an easy kill without even starting a duel), <strong>Angle knowledge</strong> (pre-fires, off-angles, etc.), <strong>Luck</strong> (a random bullet flew into the face. A noob just jerked their hand, fired while running/jumping and one-tapped a much stronger player).<br>
<img src="../../../pictures/aim/valorant_headshot.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>High TTK:</strong> A classic for arena shooters (<em>Quake</em>, <em>Apex Legends</em>, <em>Overwatch 2</em>, <em>The Finals</em>). Here, to kill an enemy, you need to continuously track them and hit them <strong>many times in a row</strong>. It’s simply impossible to track by accident for dozens of seconds.<br>
<img src="../../../pictures/aim/quake_lg.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
</ul>
<p>And this is where <strong>raw aim takes the front seat</strong>. A lucky shot won’t decide anything here. You need to <strong>track the target long and accurately</strong>. In high-TTK games, the gap in aiming skill between players is much more visible. A strong player will consistently track and kill a novice, who simply won’t have the precision and mouse control to deal enough damage. Tactics and positioning alone won’t solve the problem here – you absolutely need to hit your shots (though you can try to compensate with movement, which again expands the aim-advantage spread based on the points above).</p>
<h2 id="escape-from-aim-tactics-movement-and-abstract-abilities"><strong>Escape from Aim: Tactics, Movement, and Abstract Abilities</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#escape-from-aim-tactics-movement-and-abstract-abilities" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Since pure aim is a difficult skill, requiring thousands of hours to hone, developers shift the focus to other, more “democratic” mechanics:</p>
<ol>
<li><strong>Tactics and Communication.</strong> You can be terrible with a mouse but still outsmart your opponent by taking unexpected positions, making calls for team play, and using utility.<br>
<img src="../../../pictures/aim/dust_tactics.png" width="auto" height="auto" alt="" loading="lazy"></li>
<li><strong>Movement.</strong> In tactical shooters, it works as a <strong>limiter</strong> (slow and predictable). In arena shooters, it works as a <strong>divider</strong> (fast and complex), allowing a skilled player to dodge and demonstrate superior tracking.<br>
<img src="../../../pictures/aim/Thumbnail_Best_Movement_Guide.png" width="auto" height="auto" alt="" loading="lazy"></li>
<li><strong>Abilities.</strong> The most powerful tool for de-leveling aim. Why aim perfectly when you can spam your opponent with grenades, turrets, smokes, slow-fields, and flashbangs? These mechanics require not precision, but good decision-making and timing.<br>
<img src="../../../pictures/aim/overwatch_zarub.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ol>
<hr>
<h2 id="conclusion"><strong>Conclusion</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#conclusion" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Aim undoubtedly remains an important skill. But most competitive games are designed to <strong>“casualize”</strong> it, to make it <strong>one of many factors for victory, but not the only one</strong>, and certainly not the primary (decisive) one. Slow movement, RNG spread, and an abundance of abilities are part of a design philosophy aimed at keeping the game accessible to millions, not just a select few with perfectly honed motor skills.</p> ]]></description>
    <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Muscle Memory, Part of the Aiming Skill Set</title>
    <link>https://shiza.sccl.cc/en/Games/Aim/Muscle-memory</link>
    <guid>https://shiza.sccl.cc/en/Games/Aim/Muscle-memory</guid>
    <description><![CDATA[ <p>A myth lives in the mind of the average competitive gamer: that muscle memory is the holy grail of aim. The idea is that you just need to grind thousands of hours on one sensitivity, so that your hand, without any input from the brain, performs perfect movements. To memorize, like a virtuoso violinist scales, every centimeter of the mousepad, every micro-movement of the mouse. This idea is just as harmful and dangerous a coping mechanism as the belief that “aim is everything” (see <a href="../../../en/Games/Aim/Casualization-of-aim" class="internal alias" data-slug="en/Games/Aim/Casualization-of-aim">EN Casualization of aim</a>).</p>
<p><img src="../../../pictures/aim/muscle_memory.png" width="auto" height="auto" alt="" loading="lazy"></p>
<h2 id="not-memorization-but-adaptation"><strong>Not Memorization, but Adaptation</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#not-memorization-but-adaptation" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The main misconception is the idea of muscle memory as a rigid recording in the neurons: “a 3.7 cm turn to the right == a headshot.” In reality, it’s not about recording specific movements, but about <strong>forming the ability to effectively adapt</strong> to different conditions, to conduct analysis.<br>
When it comes to aim, the brain and nervous system learn not to remember mouse positions, but to <strong>solve motor tasks</strong>: “The target is moving like this, I need to apply a force of this specific strength and duration.”</p>
<h2 id="sens-schizophrenia-the-perfect-sens-is-an-illusion"><strong>Sens-Schizophrenia: The Perfect Sens is an Illusion</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#sens-schizophrenia-the-perfect-sens-is-an-illusion" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Acolytes of the cult believe that by finding their <em>perfect</em> sensitivity (let’s say, 52 cm/360), they will reach nirvana. But the world is unstable. What happens if:</p>
<ul>
<li>You change your mousepad/skates to slightly slower/faster ones</li>
<li>A speck of dust gets on the skates</li>
<li>The weather brings higher humidity – the mousepad becomes a bit slower</li>
<li>You’ve worn a slow spot on the mousepad, where movement in the center is slower than on the edges</li>
</ul>
<p><img src="../../../pictures/aim/change_skates_gg.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Any of these micro-changes <strong>completely invalidates</strong> the idea of memorized movements. With your <em>ideal</em> 52 cm/360 sensitivity, you de facto need to apply different amounts of force to perform the same movements expecting the same result. And if you rely purely on muscle memory, you will miss (You perfectly applied the force for a 10cm movement to the left, but the mouse traveled only 9.5cm or a full 10.5cm). Because your brain expected one level of resistance but got another.</p>
<h2 id="aim-is-basketball-an-analogy"><strong>Aim is Basketball: An Analogy</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#aim-is-basketball-an-analogy" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>A basketball player throwing the ball into the hoop doesn’t calculate millimeters or reproduce a shot memorized down to the nanosecond. They assess the distance, the defenders’ movements, their own momentum, and <strong>adapt the shot on the fly</strong>. They rely on a general feel for their body, the ball, and the hoop’s position. They can score from different positions, with different approaches, using one or two hands, with or without wind, even with balls of different weight and bounce. Because they have developed <strong>general coordination</strong>, not memorized one specific shot.</p>
<p>It’s the same with a cracked aimer. They don’t remember how many centimeters to move the mouse for a headshot from Mirage’s fireboxes into pit, they don’t know what hand movement is needed to turn 20°. They see the target, their brain instantly assesses the distance from the crosshair to the target and the target’s movement speed, and their arm, wrist, and fingers <strong>cooperate</strong> to solve this task. The composition of this “cooperation” will change depending on the sensitivity.</p>
<ul>
<li><strong>On a low sensitivity (50 cm/360)</strong> the main work is done by the forearm and shoulder for large turns, while the wrist and fingers make micro-corrections.</li>
<li><strong>On a high sensitivity (20 cm/360)</strong> the wrist and fingers are actively engaged, while the movement from the shoulder and forearm is reduced.</li>
</ul>
<p><strong>The perfect aimer isn’t the one who memorized one sensitivity, but the one who developed neuromotor control that allows them to effectively use any sensitivity to solve the task.</strong> Depending on the task – the sensitivity can be different – for arena shooters a faster sens might be more convenient, while for tactical ones – a slower one.</p>
<p><img src="../../../pictures/aim/sens_chart.png" width="auto" height="auto" alt="" loading="lazy"></p>
<h2 id="the-visuomotor-feedback-loop"><strong>The Visuomotor Feedback Loop</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-visuomotor-feedback-loop" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>When you grind an aim trainer for hours, you’re not training your arm muscles. You’re training the <strong>speed and efficiency of the feedback loop</strong>:</p>
<ol>
<li><strong>Eye:</strong> Sees the target and its movement.</li>
<li><strong>Brain:</strong> Processes the visual information and calculates the necessary motor command.</li>
<li><strong>Hand:</strong> Executes the command.</li>
<li><strong>Repeat the cycle:</strong> analysis of the previous result.</li>
</ol>
<p>The essence of training is to shorten the time it takes to complete this loop (reduce analysis time) and increase the accuracy of the calculation in step №2. Muscle memory here is not memory of the movement, but <strong>memory of how different motor commands correlate with the result on the screen</strong>. It’s a skill of <strong>calibration</strong>, not reproduction.</p>
<hr>
<h2 id="conclusion"><strong>Conclusion</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#conclusion" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>An obsession with muscle memory and the search for the “one true” sensitivity is a dead end. It creates a fragile system that collapses at the slightest change in conditions.</p>
<p><strong>The true skill of aiming is not muscle memory, but visuomotor coordination.</strong> It’s the ability of your brain and body to quickly and accurately adapt to any conditions: a new sensitivity, a different gameplay style, a changed situation. It’s about plasticity, not rigidity. You need to develop not the <em>memory of the hand</em>, but the connection between the eye, the brain, and the hand. And then you’ll have that cracked aim.</p>
<p>With regular aim training – changing sensitivity stops being noticeable – you can easily jump between 24-34-38-44-55-66-77-88 <em>(etc., etc.)</em> cm/360 without feeling much difference – the speed of adaptation to new conditions will take mere seconds.</p> ]]></description>
    <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Мышечная память, часть навыка прицеливания</title>
    <link>https://shiza.sccl.cc/ru/Games/Aim/Muscle-memory</link>
    <guid>https://shiza.sccl.cc/ru/Games/Aim/Muscle-memory</guid>
    <description><![CDATA[ <p>В голове массового компетитив-игрока живет миф о мышечной памяти как о священном Граале аима. Мол, нужно просто намотать тысячи часов на одной сенсе, чтобы рука сама, без участия мозга, совершала идеальные движения. Заучить, как скрипач-виртуоз гаммы, каждый сантиметр ковра, каждый микродвижек мышкой. Эта идея - такой же вредный и опасный копинг, как и вера в то, что “аим решает всё” (см. <a href="../../../ru/Games/Aim/Casualization-of-aim" class="internal alias" data-slug="ru/Games/Aim/Casualization-of-aim">RU Casualization of aim</a>).</p>
<p><img src="../../../pictures/aim/muscle_memory.png" width="auto" height="auto" alt="" loading="lazy"></p>
<h2 id="не-запоминание-а-адаптация"><strong>Не запоминание, а адаптация</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#не-запоминание-а-адаптация" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Главное заблуждение - представление о мышечной памяти как о жесткой записи в нейронах: «поворот на 3.7 см вправо == хедшот». На самом деле, это не запись конкретных движений, а <strong>формирование способности эффективно адаптироваться</strong> к разным условиям, проводить анализ.<br>
В случае с аимом мозг и нервная система учатся не запоминать положения мыши, а <strong>решать моторные задачи</strong>: “Цель движется так-то, нужно применить усилие такой-то силы и длительности”.</p>
<h2 id="сенса-шизофрения-идеальная-сенса--это-иллюзия"><strong>Сенса-шизофрения: идеальная сенса — это иллюзия</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#сенса-шизофрения-идеальная-сенса--это-иллюзия" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Адепты культа верят, что найдя свою <em>идеальную</em> сенсу (условно, 52 см/360), они достигнут нирваны. Но мир нестабилен. Что будет, если:</p>
<ul>
<li>Ты сменил коврик/глайды на чуть более медленный/быстрый</li>
<li>На глайды попала пылинка</li>
<li>Погода прокнула повыешнную влажность - ковёр стал чуть медленее</li>
<li>Ты натёр слоу-спот на ковре, движение в центре медленее чем по краям</li>
</ul>
<p><img src="../../../pictures/aim/change_skates_gg.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Любое из этих микро-изменений <strong>полностью инвалидирует</strong> идею о заученных движениях. С твоей <em>идеальной</em> сенсой 52 см/360 де-факто необходимо прилагать разные усилия для совершения одинаковых движений в ожидании одного результата. И если ты уповаешь на чистую мышечную память, ты промажешь (Ты идеально приложил усилия на движение 10см влево, но мышь прокатилась лишь 9.5см или целых 10.5см). Потому что твой мозг ожидал одного сопротивления, а получил другое.</p>
<h2 id="аим---баскетбол-аналогия"><strong>Аим - баскетбол: Аналогия</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#аим---баскетбол-аналогия" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Баскетболист, бросающий мяч в кольцо, не вычисляет миллиметры и не воспроизводит заученный до наносекунды бросок. Он оценивает дистанцию, движение защитников, свое ускорение и <strong>адаптирует бросок на лету</strong>. Он полагается на общее чувство тела, мяча и положение кольца. Он может забить с разных позиций, с разного разбега, одной или двумя руками, хоть с ветром, хоть с разным весом и упругостью мяча. Потому что он развил <strong>общую координацию</strong>, а не заучил один конкретный бросок.</p>
<p>Точно так же и жоский аимер. Он не помнит, на сколько сантиметров нужно сдвинуть мышь для хедшота на Мираже с фаирбоксов в яму, он не знает какое движение рукой ему нужно сделать чтоб повернуться на 20°. Он видит цель, его мозг мгновенно оценивает расстояние прицела до цели и скорость передвижения цели, и его рука, кисть, пальцы <strong>кооперируются</strong>, чтобы решить эту задачу. Состав этой “кооперации” будет меняться в зависимости от сенсы.</p>
<p><img src="../../../pictures/aim/plecho.png" width="auto" height="auto" alt="" loading="lazy"></p>
<ul>
<li><strong>На низкой сенсе (50 см/360)</strong> основную работу выполняет предплечье и плечо для крупных поворотов, а кисть и пальцы вносят микро-коррекции.</li>
<li><strong>На высокой сенсе (20 см/360)</strong> в дело активно вступают кисть и пальцы, движение плечом и предплечьем сокращается.</li>
</ul>
<p><strong>Идеальный аимер — это не тот, кто заучил одну сенсу, а тот, кто развил нейромоторный контроль, позволяющий ему эффективно использовать любую сенсу для решения задачи.</strong> В зависимости от задачи - сенса может быть разной - для ареновых шутеров более удобна будет быстрая сенса, а для тактических - медленная.</p>
<p><img src="../../../pictures/aim/sens_chart.png" width="auto" height="auto" alt="" loading="lazy"></p>
<h2 id="зрительно-моторная-петля"><strong>Зрительно-моторная петля</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#зрительно-моторная-петля" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Когда ты часами долбишь в аим-тренере, ты тренируешь не мышцы руки. Ты тренируешь <strong>скорость и эффективность петли обратной связи</strong>:</p>
<ol>
<li><strong>Глаз:</strong> Видит цель и ее движение.</li>
<li><strong>Мозг:</strong> Обрабатывает визуальную информацию и вычисляет необходимую моторную команду.</li>
<li><strong>Рука:</strong> Выполняет команду.</li>
<li><strong>Повтор круга:</strong> анализ результатов прошлого</li>
</ol>
<p>Суть тренировок - сократить время прохождения этой петли (уменьшить время на анализ) и повысить точность расчета в пункте №2. Мышечная память здесь - это не память о движении, а <strong>память о том, как разные моторные команды соотносятся с результатом на экране</strong>. Это навык <strong>калибровки</strong>, а не воспроизведения.</p>
<hr>
<h2 id="вывод"><strong>Вывод</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#вывод" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Зацикленность на мышечной памяти и поиске “той самой” сенсы - это путь в тупик. Он создает хрупкую систему, которая рушится от малейшего изменения условий.</p>
<p><strong>Настоящий навык аима — это не мышечная память, а моторно-зрительная координация</strong> Это способность твоего мозга и тела быстро и точно адаптироваться к любым условиям: к новой сенсе, другому геймплею, изменившейся обстановке. Это пластичность, а не жесткость. Разивать нужно не <em>память руки</em>, а связь между глазом, мозгом и рукой. И тогда будет ебочка.</p>
<p>При регуляных тренировкая аима - смена сенсы перестаёт ощущаться - ты можешь спокойно скакать между 24-34-38-44-55-66-77-88 <em>(и т.д. и т.п.)</em> см/360 и при этом не особо ощущая разницу - скорость адаптации к новым условия будет занимать считанные секунды.</p> ]]></description>
    <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Main</title>
    <link>https://shiza.sccl.cc/en/</link>
    <guid>https://shiza.sccl.cc/en/</guid>
    <description><![CDATA[ <p>Me writing my absolute brainrot here!!!!</p>
<p><video src="../pictures/blobby-blobby-dancer.mp4" autoplay loop muted playsinline></video><br>
<small><em>am rly like this blobby dancer. Its the best <a href="https://www.nvidia.com/en-us/geforce/community/demos/" class="external">nvidia tech demo<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> ive ever seen.</em></small><br></p> ]]></description>
    <pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Explorer</title>
    <link>https://shiza.sccl.cc/explorer</link>
    <guid>https://shiza.sccl.cc/explorer</guid>
    <description><![CDATA[  ]]></description>
    <pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Main</title>
    <link>https://shiza.sccl.cc/</link>
    <guid>https://shiza.sccl.cc/</guid>
    <description><![CDATA[ <p>Me writing my absolute brainrot here!!!!</p>
<p><video src="./pictures/blobby-blobby-dancer.mp4" autoplay loop muted playsinline></video><br>
<small><em>am rly like this blobby dancer. Its the best <a href="https://www.nvidia.com/en-us/geforce/community/demos/" class="external">nvidia tech demo<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> ive ever seen.</em></small><br></p> ]]></description>
    <pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Recents</title>
    <link>https://shiza.sccl.cc/recents</link>
    <guid>https://shiza.sccl.cc/recents</guid>
    <description><![CDATA[  ]]></description>
    <pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Оказуаливание аима - Почему &quot;чистый&quot; скилл прицеливания не решает в соревновательных играх</title>
    <link>https://shiza.sccl.cc/ru/Games/Aim/Casualization-of-aim</link>
    <guid>https://shiza.sccl.cc/ru/Games/Aim/Casualization-of-aim</guid>
    <description><![CDATA[ <p><img src="../../../pictures/aim/ground_tracking.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>В мире киберкотлет, кибершпротов и хардкорного гейминга культ аиминга возведен в абсолют. Часы на тренировочных картах, бесконечные дуэли, дроч в аим тренерах и погоня за идеальной сенсой, мышкой, ковром, глайдами, посадкой, разрешением, fov<em>ом</em> и т.д. и т.п. - всё это ритуалы современного неопилигрима, верящего, что скорость реакции и точность мыши доведут его до топ-1. Но по факту жоский анализ бол-ва игор <strong>оказуаливает</strong> аим как ключевой навык. В большинстве современных мультиплеерных компетитив игр “чистый” аим намеренно поставлен разработчиками в подчиненное положение. Его потенцевал жеско ограничен системами, призванными <strong>уровнять шансы</strong> между игроками разного уровня мастерства.</p>
<h2 id="великий-уравнитель-рандом-мувмент-и-неконтролируемые-факторы"><strong>Великий уравнитель: Рандом, мувмент и неконтролируемые факторы</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#великий-уравнитель-рандом-мувмент-и-неконтролируемые-факторы" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Главный враг raw-скилла аиминга - это случайность и ограничения. Разработчики давно воткнули, что игра, где победа безраздельно принадлежит тому, у кого быстрее реакция и лучше mouse-контроль, обречена на узкую аудиторию <em>(серия quake в качестве примера)</em>. Новые игроки будут уходить, с болью в попе, а матчмейкинг не справится с гигантским разрывом в скилле между вершиной и низом.</p>
<p>Поэтому в ход идут системы-костыли:</p>
<ul>
<li>
<p><strong>Random Bullet Spread (RNG Spread):</strong> Самый очевидный пример. Даже в самом популярном тактической шутере <em>Counter-Strike</em>, который считается эталоном киберспорта <em>(ну как минимум он самый популярный)</em>, пули из любого оружия в стоячем положении летят не строго в одну точку, а в пределах некого круга разброса. В <em>Valorant</em> этот разброс еще более выражен. Нет возможности гарантированно контролировать каждую пулю - можно лишь <strong>увеличить вероятность</strong> попадания. Это огромная разница - Даже заученный спрей-паттерн - это не точная наука, а танцы с бубном вокруг случайного отклонения <em>(пжзарегай)</em>.<br>
<img src="../../../pictures/aim/rng_spread.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>Контроль скорости передвижения:</strong> Ключевой, но часто упускаемый из виду аспект. Во многих играх (<strong>CS2</strong>, <strong>Valorant</strong>, <strong>R6 Siege</strong>) базовая скорость передвижения персонажей относительно <strong>низкая и предсказуемая</strong>. Это мощнейший инструмент уравнивания. <strong>Против медленной цели и новичок, и про будут целиться практически одинаково эффективно.</strong> Разница в их скилле будет минимальна. Гений трекинга не сможет раскрыть свой потанцевал, просто потому, что цель не бросает ему вызов. Вся “вилка” между плохим и хорошим аимом сжимается. Разница же ощущаестся когда появляется быстрая, непредсказуемая цель. Здесь нуб просто потеряется и не сможет по ней попасть, в то время как жоский-игрок, хоть и с худшим результатом, чем против медленной цели, всё же сможет её оттрекать. Таким образом, <strong>медленный мувмент - это скрытый механизм, не дающий сильному аимеру раскрыть свой полный потанцевал</strong> против слабого оппонента.<br>
<img src="../../../pictures/aim/apex_glide.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>Стрельба в движении:</strong> Механизм, практически полностью убранный из тактических шутеров. В том же <em>CS</em> стрельба на ходу - это синоним слова “промах”. Это сознательное решение, которое переводит акцент с пострелушек на позиционирование и тактику (стоп-шоты).<br>
<img src="../../../pictures/aim/strafe_rng.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>Система промахов (Bloom), уменьшение урона на дистанции, сложная баллистика</strong> — все это слои RNG, которые добавляют в уравнение фактор удачи. Иногда кривой выстрел попадет, а идеально наведенный - нет.<br>
<img src="../../../pictures/aim/ballistics.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
</ul>
<p>Суть идеи - <strong>снизить ceiling (потолок) мастерства аима.</strong> (невелировать aim-advantage) Разница между хорошим и богоподобным аимером нивелируется тем, что <strong>даже бог не может гарантировать попадание</strong> из-за рандома, а против медленной цели его превосходство будет не так заметно. Новичок всегда имеет шанс на случайное попадание или “счастливый” хедшот.</p>
<h2 id="высокий-ttk-vs-низкий-ttk-парадокс-аим-зависимости"><strong>Высокий TTK vs Низкий TTK: Парадокс аим-зависимости</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#высокий-ttk-vs-низкий-ttk-парадокс-аим-зависимости" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Здесь кроется главный парадокс, который многие не осознают.</p>
<ul>
<li>
<p><strong>Низкий TTK (Time To Kill):</strong> Характерен для тактических шутанов (CS2, Valorant, R6 Siege). Убийство происходит за 1-2 попадания. Казалось бы, тут нужен ювелирный аим, чтоб давать по шапке с пермой. Но на деле <strong>аим менее важен</strong>, чем кажется. Решающим фактором часто становится не чистое попадание, а: <strong>Позиционирование</strong> (можно застать врага врасплох, дать одну по затылку, сделать килл даже не начиная дуэли), <strong>Знание углов</strong> (префы, оффэнглы и т.п.), <strong>Удача</strong> (Случайная пуля залетела в лицо. Новичок просто дёрнул рукой, стрельну на бегу/в прыжке и шотнул куда более сильного игрока)<br>
<img src="../../../pictures/aim/valorant_headshot.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
<li>
<p><strong>Высокий TTK:</strong> Классика для ареновых шутеров (<em>Quake</em>, <em>Apex Legends</em>, <em>Overwatch 2</em>, <em>The Finals</em>). Здесь чтобы убить врага, нужно непрерывно трекать его и попадать в него <strong>много раз подряд</strong>. Случайно затрекать в течении десятка секунд просто не получится.<br>
<img src="../../../pictures/aim/quake_lg.png" width="auto" height="auto" alt="" loading="lazy"></p>
</li>
</ul>
<p>И вот здесь <strong>raw-аим выходит на первое место</strong>. Удачным выстрелом тут ничего не решить. Нужно именно <strong>долго и точно вести цель</strong>. В играх с высоким TTK разрыв в мастерстве аима между игроками виден значительно больше. Сильный игрок будет стабильно оттрекивать и убивать новичка, у которого просто не хватит точности и контроля над мышью, чтобы нанести достаточно дамага. Тактикой и позицией тут проблему не решить - нужно именно попадать, но можно пытаться вывозить на мувменте, но тут опять же расширяется вилка aim-advantage исходя из пунктов выше.</p>
<h2 id="бегство-от-аима-тактика-мувмент-и-абстрактные-способности"><strong>Бегство от аима: Тактика, мувмент и абстрактные способности</strong><a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#бегство-от-аима-тактика-мувмент-и-абстрактные-способности" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Поскольку чистый аим - навык сложный, требующий тысячи часов на оттачивание, разработчики уводят фокус в другие, более “демократичные” механики:</p>
<ol>
<li><strong>Тактика и коммуникация.</strong> Можно очень плохо двигать мышкой, но постоянно удивлять соперника, занимать неожиданные позиции, давать коллы для коммандой работы, юзать абилки.<br>
<img src="../../../pictures/aim/dust_tactics.png" width="auto" height="auto" alt="" loading="lazy"></li>
<li><strong>Мувмент.</strong> В так. шутерах он работает как <strong>ограничитель</strong> (медленный и предсказуемый). В ареновых - как <strong>разделитель</strong> (быстрый и сложный), позволяющий сильному игроку доджить и демонстрировать превосходство в трекинге.<br>
<img src="../../../pictures/aim/Thumbnail_Best_Movement_Guide.png" width="auto" height="auto" alt="" loading="lazy"></li>
<li><strong>Способности (Abilities).</strong> Мощнейший инструмент для делевелизации аима. Зачем идеально целиться, если можно закидать соперника гранатами, турелями, смоками, всякими слоу-спотами и флешками. Эти механики требуют не точности, а правильного принятия решений и тайминга.<br>
<img src="../../../pictures/aim/overwatch_zarub.png" width="auto" height="auto" alt="" loading="lazy"></li>
</ol>
<hr>
<h2 id="вывод">Вывод<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#вывод" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Аим, безусловно, остается важным навыком. Но бол-во компетитив игр задезайнены так, чтоб <strong>оказуалить</strong> его, сделать <strong>одним из многих факторов победы</strong>, <strong>но не единственным</strong> и уж точно не основным (решающим). Медленный мувмент, RNG разброс и обилие абилок - это дизайнерская философия, направленная на то, чтоб игра оставалась доступной для миллионов, а не для горстки избранных с идеально отточенной моторикой.</p> ]]></description>
    <pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Main</title>
    <link>https://shiza.sccl.cc/ru/</link>
    <guid>https://shiza.sccl.cc/ru/</guid>
    <description><![CDATA[ <p>Me writing my absolute brainrot here!!!!</p>
<p><video src="../pictures/blobby-blobby-dancer.mp4" autoplay loop muted playsinline></video><br>
<small><em>am rly like this blobby dancer. Its the best <a href="https://www.nvidia.com/en-us/geforce/community/demos/" class="external">nvidia tech demo<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> ive ever seen.</em></small><br></p> ]]></description>
    <pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Search</title>
    <link>https://shiza.sccl.cc/search</link>
    <guid>https://shiza.sccl.cc/search</guid>
    <description><![CDATA[ <input type="text" id="search-input" class="search-page-input" placeholder="Search..." autofocus>
<div id="search-results"></div> ]]></description>
    <pubDate>Fri, 19 Sep 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Rulemak CAWS</title>
    <link>https://shiza.sccl.cc/en/Peripheral/Keyboards/Rulemak-CAWS</link>
    <guid>https://shiza.sccl.cc/en/Peripheral/Keyboards/Rulemak-CAWS</guid>
    <description><![CDATA[ <p>The Colemak CAWS project itself was created by the Colemak community to unite popular mods: <code>Curl</code>, <code>Angle</code>, <code>Wide</code>, <code>Symbols</code>, with the main work on CAWS done by <a href="https://dreymar.colemak.org/" class="external">DreymaR<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. This Russian variant is my adaptation for Cyrillic.</p>
<h2 id="core-concept">Core Concept<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#core-concept" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The Russian layout is built as a phonetic analogue of the English one:</p>
<ul>
<li>A > А</li>
<li>R > Р</li>
<li>S > С</li>
<li>T > Т</li>
<li>G > Г</li>
</ul>
<p>and so on, where possible.</p>
<p>This approach means you don’t have to learn a new layout from scratch - you can start typing confidently right away, knowing only the base layout: many words and combinations are typed with similar finger movements regardless of which layout you’re using.</p>
<h2 id="why-it-works">Why It Works<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#why-it-works" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Letter and sound frequencies in Russian and English are largely similar. Therefore, phonetically mapping the English layout to Russian provides a reasonably good distribution of workload without requiring separate deep optimization for Russian texts. (<a href="https://ru.wikipedia.org/wiki/%D0%A7%D0%B0%D1%81%D1%82%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C" class="external">Russian<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://en.wikipedia.org/wiki/Letter_frequency" class="external">English<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>)</p>
<h2 id="heatmap">Heatmap<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#heatmap" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Top 1000 words for Russian and English<br>
<img src="../../../pictures/Peripheral/rulemak_caws_thermal.png" width="auto" height="auto" alt="" loading="lazy"><br>
<img src="../../../pictures/Peripheral/colemak_caws_thermal.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>You can poke around and compare on my visualizer:</p>
<p><a href="https://heatkeys.sccl.cc" class="external">heatkeys.sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> <strong>(you can poke it right here!)</strong></p>
<iframe src="https://heatkeys.sccl.cc" style="width: 100%; height: 770px; border: 1px solid var(--secondary); border-radius: 8px;" loading="lazy"></iframe>
<h2 id="whats-here">What’s Here?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#whats-here" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>One of the features of Colemak CAWS is the central block of keys with symbols:</p>
<p><code>- [ \ / ]</code></p>
<p>Because of this, the layout feels almost like a split keyboard: there’s additional space between the hands, and some rarely used symbols end up under the index fingers.</p>
<p>In the Russian version, this space turned out to be perfect for letters:</p>
<p><code>Щ</code>, <code>Ш</code>, <code>Э</code>.</p>
<p>They’re not used that often, but they’re significantly more comfortable to press than in traditional layouts, where such letters are usually relegated to the right pinky.</p>
<p>Not all phonetic correspondences are obvious. For example:</p>
<ul>
<li><code>V → В</code></li>
<li><code>W → Ж</code></li>
</ul>
<p>This mapping might look strange, but it’s actually historically accurate.</p>
<p>The remaining letters had to be shoved wherever they’d fit:</p>
<ul>
<li><code>Ъ</code> ended up under the pinky, much as I’d prefer otherwise, since it’s used extremely rarely.</li>
<li><code>Ю</code> took the place of <code>\</code>. This letter isn’t too frequent either, so this turned out to be the least painful placement option.</li>
<li><code>Ё</code> - some idiot in the Russian layout stuck it in the ass-end of the world - in this variant it’s on <code>Е</code>, but on the <em>AltGr</em> layer.</li>
</ul>
<p>The layout doesn’t try to be perfect for Russian. The goal was to make the Russian part as similar as possible to the English one, so you don’t have to keep two different layouts in your head, while preserving the ergonomics and convenience of Colemak.</p>
<h2 id="compatibility">Compatibility<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#compatibility" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The layout was made for ANSI keyboards.</p>
<p>On ISO it might work slightly differently.</p>
<p>On Split I use the regular phonetic DH.</p>
<h2 id="installation-for-windows">Installation for Windows<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#installation-for-windows" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>The layout was built using <a href="https://www.microsoft.com/en-us/download/details.aspx?id=102134" class="external">Microsoft Keyboard Layout Creator<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> (<code>.kdl</code> file for editing is in the archive)</p>
<p>It’s better to drop the DLL files manually and register them in the registry by hand too.</p>
<p>But you can run the installer and pray the system doesn’t croak.</p>
<p><strong>Download archive:</strong><br>
<a href="../../../files/rumkcaws.7z" class="internal" data-slug="files/rumkcaws.7z">rumkcaws.7z</a></p>
<h2 id="linux">Linux<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#linux" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><small>No version for Linux, I use split and DH there anyway - the layout itself is basically for a laptop.</small><br><br>
U can create an xkb config using <a href="../../../en/it/xkb.sccl" class="internal" data-slug="en/it/xkb.sccl">xkb.sccl.md</a></p> ]]></description>
    <pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Rulemak CAWS</title>
    <link>https://shiza.sccl.cc/ru/Peripheral/Keyboards/Rulemak-CAWS</link>
    <guid>https://shiza.sccl.cc/ru/Peripheral/Keyboards/Rulemak-CAWS</guid>
    <description><![CDATA[ <p>Сам проект Colemak CAWS был создан сообществом вокруг Colemak для объеденения популярных модов <code>Curl</code>, <code>Angle</code>, <code>Wide</code>, <code>Symbols</code>, а основная работа над CAWS выполнена <a href="https://dreymar.colemak.org/" class="external">DreymaR<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>. Данный русский вариант является моей адаптацией для кириллицы.</p>
<h2 id="основная-идея">Основная идея<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#основная-идея" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Русская раскладка построена как фонетический аналог английской:</p>
<ul>
<li>A > А</li>
<li>R > Р</li>
<li>S > С</li>
<li>T > Т</li>
<li>G > Г</li>
</ul>
<p>и так далее, где это возможно.</p>
<p>Такой подход позволяет не учить новую раскладку - на ней можно печатать сразу уверенно зная лишь основную: многие слова и сочетания набираются похожими движениями пальцев независимо от выбранной раскладки.</p>
<h2 id="почему-это-работает">Почему это работает<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#почему-это-работает" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Частотность букв и звуков в русском и английском языках во многом схожа. Потому фонетическое отображение английской раскладки на русский язык даёт достаточно хорошее распределение нагрузки без отдельной глубокой оптимизации под русские тексты. (<a href="https://ru.wikipedia.org/wiki/%D0%A7%D0%B0%D1%81%D1%82%D0%BE%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C" class="external">Русский<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a>, <a href="https://en.wikipedia.org/wiki/Letter_frequency" class="external">Англиский<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> )</p>
<h2 id="heatmap">Heatmap<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#heatmap" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Топ-1000 слов для русского и англиского<br>
<img src="../../../pictures/Peripheral/rulemak_caws_thermal.png" width="auto" height="auto" alt="" loading="lazy"><br>
<img src="../../../pictures/Peripheral/colemak_caws_thermal.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Почекать, сравнить можно на моём визуализаторе:</p>
<p><a href="https://heatkeys.sccl.cc" class="external">heatkeys.sccl.cc<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> <strong>(можно потыкать прям тут!)</strong></p>
<iframe src="https://heatkeys.sccl.cc" style="width: 100%; height: 770px; border: 1px solid var(--secondary); border-radius: 8px;" loading="lazy"></iframe>
<h2 id="а-чо-тут">А чо тут?<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#а-чо-тут" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Одной из особенностей Colemak CAWS является центральный блок клавиш со знаками:</p>
<p><code>- [ \ / ]</code></p>
<p>Из-за него раскладка ощущается почти как split-клавиатура: между руками появляется дополнительное пространство, а часть редко используемых символов оказывается под указательными пальцами.</p>
<p>В русской версии это место отлично подошло для букв:</p>
<p><code>Щ</code>, <code>Ш</code>, <code>Э</code>.</p>
<p>Они используются не так часто, но нажимаются значительно удобнее, чем в традиционных раскладках, где подобные буквы обычно отправляют на правый мизинец.</p>
<p>Не все фонетические соответствия очевидны. Например:</p>
<ul>
<li><code>V → В</code></li>
<li><code>W → Ж</code></li>
</ul>
<p>Такое сопоставление может выглядеть странно, но оно вполне исторически достоверное.</p>
<p>Остальные буквы пришлось пихнуть куда впихнётся:</p>
<ul>
<li><code>Ъ</code> отправился под мизинец, как бы того мне хотелось, поскольку используется крайне редко.</li>
<li><code>Ю</code> заняла место <code>\</code>. Буква тоже не слишком частотная, поэтому это оказался наиболее безболезненный вариант её размещения.</li>
<li><code>Ё</code> какой то идиот в русской пихнул её в жопу мира - в данном варианте она на <code>Е</code>, но на слое <em>AltGr</em></li>
</ul>
<p>Раскладка не пытается быть идеальной для русского языка. Задача была сделать русскую часть максимально похожей на английскую, чтобы не приходилось держать в голове две разные раскладки, но сохранить эргономику и удобство от Colemak.</p>
<h2 id="совместимость">Совместимость<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#совместимость" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Раскладка делалась под ANSI-клавиатуры.</p>
<p>На ISO она может работать немного не так.</p>
<p>На Split я использую обычный фонетический DH.</p>
<h2 id="установка-для-windows">Установка для Windows<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#установка-для-windows" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Раскладка собрана через <a href="https://www.microsoft.com/en-us/download/details.aspx?id=102134" class="external">microsoft keyboard layout creator<svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em" viewBox="0 0 512 512"><path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"></path></svg></a> (<code>.kdl</code> файл для редактирования в архиве)</p>
<p>Лучше закидывать dll ручками и прописывать их в реестре тоже ручками.</p>
<p>Но можно тыкнуть установщик и молиться, что система не помрёт.</p>
<p><strong>Скачать архив:</strong><br>
<a href="../../../files/rumkcaws.7z" class="internal" data-slug="files/rumkcaws.7z">rumkcaws.7z</a></p>
<h2 id="linux">Linux<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#linux" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><small>А для лини нет, я там использую split и dh, сама раскладка, по сути, для ноута.</small><br><br>
Можно накидать xkb конфиг с помощью <a href="../../../ru/it/xkb.sccl" class="internal" data-slug="ru/it/xkb.sccl">xkb.sccl.md</a></p> ]]></description>
    <pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>The Problem with Aim Guides</title>
    <link>https://shiza.sccl.cc/en/Games/Aim/Coaching-problem</link>
    <guid>https://shiza.sccl.cc/en/Games/Aim/Coaching-problem</guid>
    <description><![CDATA[ <h2 id="coaching">Coaching<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#coaching" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>A novice’s request to “pls coach me” often puts an experienced player in a difficult position. They possess a skill that has been honed to the point of automatism, to a pure <em>feeling</em>. The first problem they encounter is the <strong>inability to convey this feeling</strong>. The answer “just hit your shots,” while truthful, is absolutely useless for someone who hasn’t yet experienced or understood this “just.”</p>
<p>To help, the experienced player takes a logical step-<strong>deconstruction</strong>. They break down the holistic skill into its component parts.<br>
Each of these components is a high-level <strong>abstraction</strong>, created to simplify the transmission of a complex ability. In theory, this should help the novice transition from a state of not understanding to their first real experience.</p>
<h2 id="overanalysis">Overanalysis<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#overanalysis" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>On one hand, a beginner might be able to extract something useful for themselves. However, it can also happen that instead of a holistic sensation of “tracking the target with the crosshair,” they start agonizing over a dozen isolated parameters, like: “Now I need to track precisely along the trajectory, controlling the speed, and now the target has sharply changed direction-I need a flick, but without overflicking, to jump onto the target smoothly yet quickly.”</p>
<p><img src="../../../pictures/aim/Pasted-image-20251001163625.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>A simple action becomes burdened by the need to <strong>simultaneously control all these concepts created “for simplification.”</strong></p>
<h2 id="the-inadequacy-of-abstractions">The Inadequacy of Abstractions<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-inadequacy-of-abstractions" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Over time, it becomes clear that any created model is incomplete. It fails to convey the pure feeling, that very <em>kinesthetics</em> of aim-the fusion of eye, hand, and mouse into a single action. The model itself begins to demand simplification.</p>
<p>And then a new level of explanation emerges, an even higher-level abstraction, such as:</p>
<ul>
<li>“Just play more”</li>
<li>“Just track the target and hit it”</li>
<li>“Don’t shake, but you can tense your arms as much as you want”</li>
<li>“Lead the target with your gaze, and your hand will follow automatically”</li>
</ul>
<p><img src="../../../pictures/aim/Pasted-image-20251001170344.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p><strong>This is the purest truth.</strong> This is exactly how all experienced players operate-they can perform at a sufficiently high level on autopilot. But for a novice, who is still struggling with basic concepts, this advice sounds like mockery.</p>
<h2 id="the-ultimate-solution">The Ultimate Solution<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#the-ultimate-solution" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>In the end, the circle closes. All the complex explanations and multi-layered abstractions lead to the only working solution that has always existed: <strong>“Watch how others do it and try to replicate it until you succeed.”</strong></p>
<p><img src="../../../pictures/aim/Pasted-image-20251001170718.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>This is not merely an evasion. It is an acknowledgment that some skills cannot be conveyed through words and diagrams. They can only be <strong>absorbed through observation and built up through thousands of repetitions</strong>. Practice, in the end, dissolves all abstractions, returning the player to the very starting point of the question-to that intuitive “track the target smoothly with your crosshair and strive to hit more.” But now, this “just” is backed by real, rather than theoretical, experience.</p> ]]></description>
    <pubDate>Fri, 10 Jan 2025 00:00:00 GMT</pubDate>
  </item><item>
    <title>Проблема гайдов по аиму</title>
    <link>https://shiza.sccl.cc/ru/Games/Aim/Coaching-problem</link>
    <guid>https://shiza.sccl.cc/ru/Games/Aim/Coaching-problem</guid>
    <description><![CDATA[ <h2 id="коучинг">Коучинг<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#коучинг" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Просьба новичка “закоучи пжпж” ставит опытного игрока в тупик. Он обладает навыком, доведенным до автоматизма, до чувства. И первая проблема, с которой он сталкивается - <strong>невозможность передать чувство</strong>. Ответ “просто попадай” хоть и правдив, но абсолютно бесполезен для того, кто этого “просто” еще не ощущает и не может понять.</p>
<p>Чтобы помочь, опытный игрок совершает логичный шаг - <strong>деконструкцию</strong>. Он разбирает целостный навык на составляющие компоненты.<br>
Каждый из этих компонентов является высокоуровневой <strong>абстракцией</strong>, созданной для упрощения передачи сложного умения. Что в теории должно помочь новичку перейти от непонимания к первому опыту.</p>
<h2 id="черезмерный-анализ">Черезмерный анализ<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#черезмерный-анализ" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>С одной стороны новичок сможет чота для себя выдернуть полезное, но может быть и такое, что вместо целостного ощущения “вести прицелом по таргету” - он начинает мучительно думать о десятке изолированных параметров, аля: “Сейчас нужно трекать ровно по траектории, контролируя скорость, а теперь цель резко сменила вектор движения - нужен флик, но без оверфлика, чтоб плавно, но быстро прыгнуть на таргет”.</p>
<p><img src="../../../pictures/aim/Pasted-image-20251001163625.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Простое действие утяжеляется необходимостью <strong>одновременно контролировать все эти созданные “для упрощения” концепции</strong>.</p>
<h2 id="неполноценность-абстракций">Неполноценность абстракций<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#неполноценность-абстракций" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Со временем становится ясно, что любая созданная модель неполна. Она не передает чистое чувство, ту самую <em>кинестетику</em> аима, слияние глаза, руки и мыши в одном действии. Сама модель начинает требовать упрощения.</p>
<p>И тогда возникает новый уровень объяснений, еще более высокоуровневая абстракция аля:</p>
<ul>
<li>“Just play more”</li>
<li>“Просто веди по таргету и попадай”</li>
<li>“Не трясись, но руки напрягать можешь сколько угодно”</li>
<li>“Взглядом веди таргет, а рука сама повторит”</li>
</ul>
<p><img src="../../../pictures/aim/Pasted-image-20251001170344.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p><strong>Это и есть чистейшая правда.</strong> Именно так и делают все опытные игроки - они на автопилоте могут сыграть на достаточно высоком уровне. Но для новичка, который все еще борется с базовыми концепциями, этот совет звучит как издевательство.</p>
<h2 id="ультимативное-решение">Ультимативное решение<a role="anchor" aria-hidden tabindex="-1" data-no-popover href="#ультимативное-решение" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>В итоге круг замыкается. Все сложные объяснения и многослойные абстракции приводят к единственному работающему решению, которое существовало всегда: <strong>“Смотри, как делают другие, и пытайся повторить, пока не получится”</strong>.</p>
<p><img src="../../../pictures/aim/Pasted-image-20251001170718.png" width="auto" height="auto" alt="" loading="lazy"></p>
<p>Это не просто уход от ответа. Это признание, что некоторые навыки невозможно передать словами и схемами. Их можно только <strong>перенять через наблюдение и наработать через тысячи повторений</strong>. Практика в конечном счете растворяют все абстракции, возвращая игрока к тому, с чего начинался вопрос, - к тому самому интуитивному “веди прицелом ровно по таргету и стремись попадать больше”. Но теперь это “просто” подкреплено реальным, а не теоретическим опытом.</p> ]]></description>
    <pubDate>Fri, 10 Jan 2025 00:00:00 GMT</pubDate>
  </item>
    </channel>
  </rss>