release: 2026-03-30-233129
1
docs/CNAME
Normal file
|
|
@ -0,0 +1 @@
|
|||
oddsquat.org
|
||||
113
docs/about/index.html
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
about | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="General info about this website and the author">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li>about</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
|
||||
<div style='margin-bottom: 1.5rem;'>
|
||||
<details>
|
||||
<summary style='cursor: pointer; display: inline;'>
|
||||
Hello,
|
||||
my name is <strong>He4eT</strong>.
|
||||
</summary>
|
||||
<p style='margin-left: 0.5rem;'>
|
||||
It’s a Latin transliteration and
|
||||
is read as “Nechet” [ˈnʲet͡ɕet].<br>
|
||||
The name comes from the Russian word “нечет”,
|
||||
a short form of “нечётный”,
|
||||
a mathematical term meaning “odd”.
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
I'm a front-end developer and a big fan of open-source,
|
||||
customization, and minimalist software.<br>
|
||||
Member of the
|
||||
<a href='https://t.me/barbadbar' target='_blank'>BadBar</a> crew,
|
||||
part of the
|
||||
<a href='https://decentrala.org' target='_blank'>Decentrala</a>
|
||||
and <a href='https://xecut.me' target='_blank'>Xecut</a> communities.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Oddsquat</strong> is my own private fanzine
|
||||
about experiments, code, and other cyberpunk stuff.<br>
|
||||
<a href='/rss.xml'>RSS feed</a> available.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<section>
|
||||
<ul>
|
||||
<li>Email: <a href='mailto:He4eT@oddsquat.org' target='_blank'>He4eT@oddsquat.org</a></li>
|
||||
<li>Telegram: <a href='https://t.me/He4eT' target='_blank'>@He4eT</a></li>
|
||||
<li>XMPP: <a href='xmpp:He4eT@dmz.rs' target='_blank'>He4eT@dmz.rs</a></li>
|
||||
<li>Fediverse:
|
||||
<ul>
|
||||
<li><a href='https://techhub.social/@He4eT' target='_blank'>@He4eT@techhub.social</a></li>
|
||||
<li><a href='https://c.im/@He4eT' target='_blank'>@He4eT@c.im</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Code: <a href='https://github.com/He4eT' target='_blank'>github.com/He4eT</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
464
docs/css/fonts.css
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/fira_code-0.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/fira_code-1.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/fira_code-2.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/fira_code-3.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/fira_code-4.woff2') format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/fira_code-5.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-6.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-7.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-8.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-9.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-10.woff2') format('woff2');
|
||||
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-11.woff2') format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-12.woff2') format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F10C, U+1F110-1F16C, U+1F170-1F190, U+1F19B-1F1AC, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F579, U+1F57B-1F594, U+1F597-1F5A3, U+1F5A5-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CB, U+1F6CD-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8B1, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA74, U+1FA78-1FA7A, U+1FA80-1FA86, U+1FA90-1FAA8, U+1FAB0-1FAB6, U+1FAC0-1FAC2, U+1FAD0-1FAD6, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-13.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-14.woff2') format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-15.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-6.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-7.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-8.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-9.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-10.woff2') format('woff2');
|
||||
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-11.woff2') format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-12.woff2') format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F10C, U+1F110-1F16C, U+1F170-1F190, U+1F19B-1F1AC, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F579, U+1F57B-1F594, U+1F597-1F5A3, U+1F5A5-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CB, U+1F6CD-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8B1, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA74, U+1FA78-1FA7A, U+1FA80-1FA86, U+1FA90-1FAA8, U+1FAB0-1FAB6, U+1FAC0-1FAC2, U+1FAD0-1FAD6, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-13.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-14.woff2') format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-15.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-16.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-17.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-18.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-19.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-20.woff2') format('woff2');
|
||||
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-21.woff2') format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-22.woff2') format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F10C, U+1F110-1F16C, U+1F170-1F190, U+1F19B-1F1AC, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F579, U+1F57B-1F594, U+1F597-1F5A3, U+1F5A5-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CB, U+1F6CD-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8B1, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA74, U+1FA78-1FA7A, U+1FA80-1FA86, U+1FA90-1FAA8, U+1FAB0-1FAB6, U+1FAC0-1FAC2, U+1FAD0-1FAD6, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-23.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-24.woff2') format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-25.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-16.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-17.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-18.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-19.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-20.woff2') format('woff2');
|
||||
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-21.woff2') format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-22.woff2') format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F10C, U+1F110-1F16C, U+1F170-1F190, U+1F19B-1F1AC, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F579, U+1F57B-1F594, U+1F597-1F5A3, U+1F5A5-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CB, U+1F6CD-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8B1, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA74, U+1FA78-1FA7A, U+1FA80-1FA86, U+1FA90-1FAA8, U+1FAB0-1FAB6, U+1FAC0-1FAC2, U+1FAD0-1FAD6, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-23.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-24.woff2') format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url('/fonts/open_sans-25.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans Condensed';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/open_sans_condensed-26.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans Condensed';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/open_sans_condensed-27.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans Condensed';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/open_sans_condensed-28.woff2') format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans Condensed';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/open_sans_condensed-29.woff2') format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans Condensed';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/open_sans_condensed-30.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans Condensed';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/open_sans_condensed-31.woff2') format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans Condensed';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/open_sans_condensed-32.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
24
docs/css/index.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 2rem;
|
||||
margin-left: 6%;
|
||||
}
|
||||
|
||||
li::before {
|
||||
content: '/ ';
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
font-size: 1.5rem;
|
||||
margin-left: 0;
|
||||
margin-bottom: 10%;
|
||||
}
|
||||
|
||||
li:first-child::before {
|
||||
content: '';
|
||||
}
|
||||
163
docs/css/main.css
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
*::selection {
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
*:focus-visible {
|
||||
outline: 4px solid hsl(0, 0%, 0%, 0.7);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
/* Headers */
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
scroll-margin-top: 0.5em;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
|
||||
a {
|
||||
border-bottom: 2px solid hsl(0, 0%, 0%, 0.2);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
/* Lists */
|
||||
|
||||
ul {
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
|
||||
@media (max-width: 500px) {
|
||||
table {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code and quotes*/
|
||||
|
||||
pre, blockquote {
|
||||
border-left: 4px solid hsl(0, 0%, 0%, 0.7);
|
||||
margin-left: 0;
|
||||
padding-left: 1.0rem;
|
||||
padding-right: 1.0rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: hsl(0, 0%, 0%, 0.04);
|
||||
overflow-x: auto;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
background-color: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'Fira Code', monospace;
|
||||
background-color: hsl(0, 0%, 0%, 0.04);
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
|
||||
figure.image img {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
figure.image.bordered img {
|
||||
border: 4px solid hsl(0, 0%, 0%, 0.7);
|
||||
}
|
||||
|
||||
figure.image figcaption {
|
||||
border-left: 4px solid hsl(0, 0%, 0%, 0.2);
|
||||
padding-left: 1.0rem;
|
||||
padding-right: 1.0rem;
|
||||
margin-top: 1.0rem;
|
||||
}
|
||||
|
||||
/* Entries */
|
||||
|
||||
.entry:last-child,
|
||||
.entry {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.entry > header,
|
||||
.entry > .description {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Stripes */
|
||||
|
||||
.stripesContainer {
|
||||
box-sizing: content-box;
|
||||
max-width: 45rem;
|
||||
padding: 5% 5% 0;
|
||||
}
|
||||
|
||||
.stripes {
|
||||
height: 8vh;
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
currentColor 33.33%,
|
||||
transparent 33.33%,
|
||||
transparent 50%,
|
||||
currentColor 50%,
|
||||
currentColor 83.33%,
|
||||
transparent 83.33%,
|
||||
transparent 100%);
|
||||
background-size: 30.00px 30.00px;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
html, body {
|
||||
scroll-behavior: smooth;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
body > header,
|
||||
body > footer,
|
||||
body > main {
|
||||
box-sizing: content-box;
|
||||
max-width: 45rem;
|
||||
padding: 3% 5%;
|
||||
}
|
||||
|
||||
body > header ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 1.5rem;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body > header li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
body > header li::after {
|
||||
content: ' / ';
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
body > header li:last-child::after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
body > footer {
|
||||
color: hsl(0, 0%, 0%, 0.6);
|
||||
}
|
||||
1
docs/css/typography.css
Normal file
BIN
docs/favicon.ico
Normal file
|
After Width: | Height: | Size: 123 B |
BIN
docs/fonts/fira_code-0.woff2
Normal file
BIN
docs/fonts/fira_code-1.woff2
Normal file
BIN
docs/fonts/fira_code-2.woff2
Normal file
BIN
docs/fonts/fira_code-3.woff2
Normal file
BIN
docs/fonts/fira_code-4.woff2
Normal file
BIN
docs/fonts/fira_code-5.woff2
Normal file
BIN
docs/fonts/open_sans-10.woff2
Normal file
BIN
docs/fonts/open_sans-11.woff2
Normal file
BIN
docs/fonts/open_sans-12.woff2
Normal file
BIN
docs/fonts/open_sans-13.woff2
Normal file
BIN
docs/fonts/open_sans-14.woff2
Normal file
BIN
docs/fonts/open_sans-15.woff2
Normal file
BIN
docs/fonts/open_sans-16.woff2
Normal file
BIN
docs/fonts/open_sans-17.woff2
Normal file
BIN
docs/fonts/open_sans-18.woff2
Normal file
BIN
docs/fonts/open_sans-19.woff2
Normal file
BIN
docs/fonts/open_sans-20.woff2
Normal file
BIN
docs/fonts/open_sans-21.woff2
Normal file
BIN
docs/fonts/open_sans-22.woff2
Normal file
BIN
docs/fonts/open_sans-23.woff2
Normal file
BIN
docs/fonts/open_sans-24.woff2
Normal file
BIN
docs/fonts/open_sans-25.woff2
Normal file
BIN
docs/fonts/open_sans-6.woff2
Normal file
BIN
docs/fonts/open_sans-7.woff2
Normal file
BIN
docs/fonts/open_sans-8.woff2
Normal file
BIN
docs/fonts/open_sans-9.woff2
Normal file
BIN
docs/fonts/open_sans_condensed-26.woff2
Normal file
BIN
docs/fonts/open_sans_condensed-27.woff2
Normal file
BIN
docs/fonts/open_sans_condensed-28.woff2
Normal file
BIN
docs/fonts/open_sans_condensed-29.woff2
Normal file
BIN
docs/fonts/open_sans_condensed-30.woff2
Normal file
BIN
docs/fonts/open_sans_condensed-31.woff2
Normal file
BIN
docs/fonts/open_sans_condensed-32.woff2
Normal file
7
docs/icon.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg
|
||||
version="1.1"
|
||||
width="128" height="128"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<rect width="100%" height="100%" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 143 B |
BIN
docs/images/posts/ugly_keyboards/keyboard_angel.jpg
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_cantor-mx.jpg
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_dactyl-manuform.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
1990
docs/images/posts/ugly_keyboards/keyboard_default.svg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_ergodox-ez.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_glove80.jpg
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_helix.jpg
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_katana60.jpg
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_keychron-q15.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_kinesis-advantage2.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 66 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_sofle.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/images/posts/ugly_keyboards/keyboard_svalboard.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
docs/images/posts/ugly_keyboards/typewriter.jpg
Normal file
|
After Width: | Height: | Size: 211 KiB |
BIN
docs/images/posts/ugly_keyboards/typewriter_ergo.jpg
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
docs/images/posts/ugly_keyboards/typing_zones_default.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/posts/ugly_keyboards/typing_zones_default_hands.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/images/posts/ugly_keyboards/typing_zones_ergo_hands.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/images/qr/oddsquat.org.png
Normal file
|
After Width: | Height: | Size: 408 B |
59
docs/index.html
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
oddsquat
|
||||
</title>
|
||||
<meta name="description" content="My own private fanzine">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/css/index.css">
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<main>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
<li><a href="/posts/">posts</a></li>
|
||||
<li><a href="/lost+found/">lost+found</a></li>
|
||||
<li><a href="/projects/">projects</a></li>
|
||||
<li><a href="/about/">about</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
590
docs/lost+found/index.html
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
lost+found | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="Unsorted pile of links I sometimes urgently need in the middle of a conversation.">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li>lost+found</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="lost-found">lost+found</h1>
|
||||
<p>Unsorted pile of links I sometimes urgently need
|
||||
in the middle of a conversation.</p>
|
||||
<blockquote>
|
||||
<p>Some file systems contain a special directory,
|
||||
called <strong>lost+found</strong> under Unix, where a file system check
|
||||
places lost and potentially corrupted files when the correct location
|
||||
cannot be determined,
|
||||
and so <strong>requires manual intervention by the user</strong>.</p>
|
||||
</blockquote>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://cari.institute/aesthetics' target='_blank'>
|
||||
Index of Aesthetics
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
An impressive library of visual styles
|
||||
that are usually hard to name.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://humanprogress.org/trends/' target='_blank'>
|
||||
Human Progress
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A collection of positive human statistics
|
||||
and optimistic charts.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://novehiclesinthepark.com/' target='_blank'>
|
||||
No Vehicles in the Park
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A short game that illustrates how one rule can mean
|
||||
different things to different people.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://deaddrops.com/' target='_blank'>
|
||||
Dead Drops
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A global network of anonymous,
|
||||
cemented USB drives for offline file sharing.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://freesewing.eu/' target='_blank'>
|
||||
FreeSewing
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Like GitHub, but for clothes.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://endoflife.date/' target='_blank'>
|
||||
EndOfLife.date
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
End-of-life (EOL) and support information is often
|
||||
hard to track, or very badly presented.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://www.atlasobscura.com/' target='_blank'>
|
||||
Atlas Obscura
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A comprehensive database of the world’s most
|
||||
wondrous places and foods.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://learnxinyminutes.com/' target='_blank'>
|
||||
Learn X in Y minutes
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A fast-paced intro to a new programming
|
||||
language or tool.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://en.wikipedia.org/wiki/Ithkuil' target='_blank'>
|
||||
Ithkuil
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
An experimental constructed language designed to express
|
||||
more profound levels of human cognition briefly and clearly.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='http://xahlee.info/kbd/keyboarding.html' target='_blank'>
|
||||
Xah Keyboard Guide
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Keyboards, input devices, shortcuts, typing habits,
|
||||
and the ergonomics behind it all.<br>
|
||||
See the <a href='http://www.xahlee.info/kbd/diy_keyboards_index.html' target='_blank'>
|
||||
list of Do-It-Yourself Keyboards</a>.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://ourworldindata.org/' target='_blank'>
|
||||
Our World In Data
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
An overview of global data and long-term trends.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://www.lesswrong.com/posts/tscc3e5eujrsEeFN4/well-kept-gardens-die-by-pacifism' target='_blank'>
|
||||
Well-Kept Gardens Die By Pacifism
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Good online communities die primarily by refusing to defend themselves.<br>
|
||||
Also <a href='https://lesswrong.ru/w/%D0%9F%D0%B0%D1%86%D0%B8%D1%84%D0%B8%D0%B7%D0%BC_%D0%B3%D1%83%D0%B1%D0%B8%D1%82_%D1%83%D1%85%D0%BE%D0%B6%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5_%D1%81%D0%B0%D0%B4%D1%8B' target='_blank'>
|
||||
available in Russian</a>.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://ciechanow.ski/archives/' target='_blank'>
|
||||
Articles by Bartosz Ciechanowski
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Interactive articles about physics, math, and engineering.
|
||||
It's probably the best website on the entire internet.<br>
|
||||
My favorite post is the one about
|
||||
<a href='https://ciechanow.ski/bicycle/' target='_blank'>bicycles</a>.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://suricrasia.online/iceberg/' target='_blank'>
|
||||
The Cursed Computer Iceberg Meme
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
An endless hall of shame and weirdness of computers.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://ncase.me/trust/' target='_blank'>
|
||||
The Evolution of Trust
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A briliant interactive guide to the game theory
|
||||
of why and how we trust each other.<br>
|
||||
The rest of this site is also pretty good.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://iknowwhatyoudownload.com/' target='_blank'>
|
||||
I Know What You Download
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Torrenting can leave traces.<br>
|
||||
Check torrent downloads and distributions
|
||||
for your own or your neighbor's IP address.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://encore.dev/blog/retries' target='_blank'>
|
||||
Retries
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Requests over the network can fail.<br>
|
||||
An interactive study of common retry methods for developers.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://hadihariri.com/2014/06/24/no-tabs-in-intellij-idea/' target='_blank'>
|
||||
No Tabs in IntelliJ IDEA
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
“There are many ways to navigate a project
|
||||
inside IntelliJ IDEA (or any other editor)
|
||||
that doesn’t necessarily include having to look
|
||||
through a list of tabs and figure out
|
||||
where you have to click next.”<br>
|
||||
Post from the VP of Program Management at JetBrains.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://sfwriter.com/wordstar.htm' target='_blank'>
|
||||
An Interface Designed for Touch Typists
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
If you think VIM is insane,
|
||||
you should definitely read about WordStar,
|
||||
which is still quite popular among many fiction writers.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://news.ycombinator.com/item?id=42492508' target='_blank'>
|
||||
Ask HN: Programmers who don't use autocomplete/LSP, how do you do it?
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Different opinions on writing code
|
||||
without using <del>overcomplicated</del> advanced tools.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://sneak.berlin/20201112/your-computer-isnt-yours/' target='_blank'>
|
||||
Your Computer Isn't Yours
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
An investigation into one of the many reasons why
|
||||
no one should use products from Apple.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://www.yr.no/en/' target='_blank'>
|
||||
Yr from NRK and Meteorologisk Institutt
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A weather forecast site with a no-nonsense interface.<br>
|
||||
Direct links:
|
||||
<a href='https://www.yr.no/en/forecast/graph/2-792680/Serbia/Central%20Serbia/Belgrade/Belgrade' target='_blank'>
|
||||
Belgrade</a>,
|
||||
<a href='https://www.yr.no/en/forecast/graph/2-745044/Republic%20of%20T%C3%BCrkiye/Istanbul/Istanbul' target='_blank'>
|
||||
Istanbul</a> and
|
||||
<a href='https://www.yr.no/en/forecast/graph/2-1486209/Russia/Sverdlovsk%20Oblast/Yekaterinburg' target='_blank'>
|
||||
Yekaterinburg</a>.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://hoodmaps.com/' target='_blank'>
|
||||
Hoodmaps
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A crowdsourced map to navigate cities
|
||||
using marked areas and user-generated tags.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://shademap.app' target='_blank'>
|
||||
ShadeMap
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
This service calculates shadows from mountains, buildings, and trees
|
||||
for any date and time, and displays them on a map.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://www.numbeo.com/cost-of-living/comparison.jsp' target='_blank'>
|
||||
Cost of Living Comparison
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A comparison tool lets you compare
|
||||
the affordability of two cities side-by-side.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://www.cia.gov/the-world-factbook/' target='_blank'>
|
||||
The World Factbook
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A massive CIA database on almost every country
|
||||
in the world.<br>
|
||||
Handy when you need to check what kind
|
||||
of power outlets a country uses before you get there.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://refactoringenglish.com/chapters/rules-for-software-tutorials/' target='_blank'>
|
||||
Rules for Writing Software Tutorials
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Most software tutorials suck. Here's how to make one that doesn't.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://en.wikipedia.org/wiki/Pangram' target='_blank'>
|
||||
Pangram
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A sentence using every letter of a given alphabet
|
||||
at least once.<br>
|
||||
<a href='https://www.artlebedev.ru/kovodstvo/sections/33/' target='_blank'>
|
||||
An additional list in Russian and other languages</a>.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
about:translations
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Firefox has a built-in translator.
|
||||
Not so smart, but works offline.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://datagubbe.se/noconf/' target='_blank'>
|
||||
No Config for Old Men
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A rant about how modern software won’t let you
|
||||
customize it anymore.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://webcompat.com/' target='_blank'>
|
||||
Webcompat.com
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Bug reporting for the web.<br>
|
||||
If a site breaks in one browser but not another,
|
||||
it’s a web compatibility bug. Report it.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://jsfuck.com/' target='_blank'>
|
||||
JSFuck
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
An esoteric and educational programming style
|
||||
based on the atomic parts of JavaScript.<br>
|
||||
It uses only six different
|
||||
characters to write and execute code.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://paulbutler.org/2025/smuggling-arbitrary-data-through-an-emoji/' target='_blank'>
|
||||
Smuggling arbitrary data through an emoji
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Unicode is broken.
|
||||
You can encode data in any unicode character.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href='https://informationisbeautiful.net/visualizations/common-mythconceptions' target='_blank'>
|
||||
Common Mythconceptions
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A list of the world’s biggest
|
||||
myths and misconceptions — with myth-busting included.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
<a href="https://en.wikipedia.org/wiki/A_Mathematician's_Lament">
|
||||
A Mathematician’s Lament — by Paul Lockhart
|
||||
</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A passionate critique of how math is taught
|
||||
and a vision of how beautiful
|
||||
it could be instead.<br>
|
||||
Also <a href='https://www.nbspace.ru/math/' target='_blank'>
|
||||
available in Russian</a>.
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
25
docs/posts/2020/index.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="description"
|
||||
content="Redirect to '/posts/#2020'.">
|
||||
<meta
|
||||
http-equiv="Refresh"
|
||||
content="0; URL=/posts/#2020">
|
||||
<title>
|
||||
Redirect | oddsquat
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
Redirect to
|
||||
<a
|
||||
style="color: inherit;"
|
||||
href="/posts/#2020">
|
||||
/posts/#2020
|
||||
</a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
123
docs/posts/2020/initial_post/index.html
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
initial post | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="Первый пост в этом фэнзине, рассказывающий о его внутреннем устойстве.">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/">
|
||||
posts</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/#2020">
|
||||
2020</a></li>
|
||||
|
||||
|
||||
<li>initial post</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="initial-post">Initial Post</h1>
|
||||
<p><strong>Oddsquat</strong> — мой маленький личный фэнзин про эксперименты, код и прочий киберпанк. Обычно такие сайты называются блогами, но в слове «фэнзин» есть приятный отголосок панк-культуры и её неотъемлемой DIY-составляющей.</p>
|
||||
<p>Основные причины, побудившие меня на создание своего уютного куска интернета:</p>
|
||||
<ul>
|
||||
<li>Хочется писать буквы, а возвращаться в ЖЖ не хочется.</li>
|
||||
<li>Фронтендер без портфолио — подозрительный фронтендер.</li>
|
||||
<li>Современный веб — памятник оверинженирингу. Лендинг с двумя абзацами текста, тремя картинками и мегабайтным бандлом из модных библиотек — одновременно безумие и норма в текущих реалиях. Мне больно на это смотреть, поэтому пришлось строить себе персональный оазис.</li>
|
||||
</ul>
|
||||
<p>В первом посте хочется рассказать, какие инструменты были использованы для создания этого сайта, какие решения были приняты и почему.</p>
|
||||
<h2 id="-">Название</h2>
|
||||
<p>Я вырос в девяностые, поэтому квикимобиль, бэтпещера и пузиблинчики для меня не пустой звук, а образец системы нейминга. Вот, например, у меня есть <a href="https://github.com/He4eT/oddkb" target="_blank">oddkb</a>.</p>
|
||||
<p>Свою квартиру с момента заселения я называю oddsquat (даже локация в Foursquare была), так пусть теперь и мой форпост в сети называется так же.</p>
|
||||
<p>Назвал сайт в честь SSID домашнего Wi-Fi, потому что не смог придумать более достойного названия :)</p>
|
||||
<h2 id="-">Платформа</h2>
|
||||
<p>Для статического сайта не нужен серверный код, поэтому WordPress и прочие CMS в качестве платформы я даже не рассматривал.</p>
|
||||
<p>Про генераторы статических сайтов раньше слышал, но подробно не изучал. Их оказалось так много (тут есть <a href="https://jamstack.org/generators/">длинный список</a>), что даже страшно.</p>
|
||||
<p>Посмотрев на самые популярные решения, я понял, что как-то неправильно понимаю значение термина «генератор статических сайтов», потому что все хором предлагают мне сгенерировать Progressive Web Application из React-компонентов с импортом данных из GraphQL-сервера вместо россыпи HTML-документов.</p>
|
||||
<p>Уверен, что уютный бложик — это точно не Web Application, а от Progressive в данном случае всем будет только хуже: искусственные трудности с роутингом, серверным рендерингом и сохранением позиции скрола успешно решены, когда каждая страница — это отдельный документ. Браузеры восхитительно умеют работать с HTML-документами!</p>
|
||||
<p>После непродолжительных поисков был найден <a href="https://doug2k1.github.io/nanogen/">Nanogen</a>, который делает именно то, что нужно, и не похож на космический корабль:</p>
|
||||
<ul>
|
||||
<li>Умеет Markdown и HTML (ejs);</li>
|
||||
<li>Имеет очень гибкую систему шаблонов;</li>
|
||||
<li>Не выходит за рамки стека Node.js.</li>
|
||||
</ul>
|
||||
<p>Да, в репозитории проекта давно не было коммитов, но хочется думать, что это по причине надёжности и закончености :)</p>
|
||||
<h2 id="-">Типографика</h2>
|
||||
<p>Я люблю текст и не умею в дизайн. Oddsquat — проект про буквы, значит, буквы должны быть красивыми и удобными.</p>
|
||||
<p>К теме типографики в вебе я подходил несколько раз, но безуспешно. Не получается структурировать и сохранить в голове все эти сакральные знания про кернинг и засечки, потому что не вижу никакой связи между теорией и практикой.</p>
|
||||
<p>Очередное погружение системных знаний опять не принесло, зато принесло удобный инструмент: <a href="https://github.com/KyleAMathews/typography.js">Typography.js</a>.</p>
|
||||
<p>Этот тулкит позволяет, покрутив ручки <a href="http://kyleamathews.github.io/typography.js/">интерактивного демо</a>, получить новое, но при этом гармоничное сочетание абзацев, заголовков и отступов между ними.
|
||||
Идеальный инструмент для тех, кто хочет поиграть со шрифтами, но не до конца понимает, как.</p>
|
||||
<p>Авторы настолько заботливые, что даже подключают <code>normalize.css</code> при генерации своих стилей, снимая с вас ещё одну головную боль. Благодаря им весь мой дополнительный css для стилизации сайта уложился в сто строк кода и хорошо работает в любых браузерах и на любых экранах.</p>
|
||||
<p>Со шрифтами всё просто:</p>
|
||||
<ul>
|
||||
<li><strong>Open Sans</strong> для текста и <strong>Open Sans Condensed</strong> для заголовков, потому что мне нравится слово «Open»;</li>
|
||||
<li><strong>Fira Code</strong> для кода, потому что лигатуры (<code>--></code>, <code>!==</code>) — это красиво.</li>
|
||||
</ul>
|
||||
<h2 id="-">Аналитика</h2>
|
||||
<p>Я долго думал, можно ли собирать аналитику просмотров, или нужно играть в идеальный веб до конца и полностью отказаться от слежки за пользователями. С одной стороны высокая идея, с другой — любопытство.</p>
|
||||
<p>После непродолжительного исследования пришёл к выводу, что Метрика и Аналитика от поисковых гигантов мне точно не подходят, а вот автор <a href="https://goatcounter.com/">GoatCounter</a> полностью понимает мою боль.</p>
|
||||
<p><a href="https://www.goatcounter.com/why/">Why I made GoatCounter</a> — отличный пост, где создатель сервиса ответил на все вопросы, которые у меня к нему возникли. Вот, например, замечательный абзац про причины бесплатности начального тарифа:</p>
|
||||
<blockquote>
|
||||
<p>I think it’s important to make the barrier of entry for software like this low as feasible to make actual meaningful inroads to “de-Google-fi” the internet a bit, and make pervasive tracking less common. Making it freely available (for personal use) is part of that.</p>
|
||||
</blockquote>
|
||||
<h2 id="-">Содержание</h2>
|
||||
<p>Самое главное. С формой и техническими вопросами всё ясно, но что же я буду складывать внутрь? Пока план такой:</p>
|
||||
<ul>
|
||||
<li>Я довольно часто изучаю штуки из разных областей и хочу описывать эти опыты. Вот, например, описал создание легковесного сайта.</li>
|
||||
<li>Мне нравится настраивать инструменты и рассказывать об этом.</li>
|
||||
<li>Есть несколько почтовых рассылок, которые я очень хочу начать переводить, чтобы начать читать.</li>
|
||||
</ul>
|
||||
<p>Признаюсь, что был очарован сайтом от ребят из команды <a href="https://100r.co/site/home.html">Hundred Rabbits</a> и захотел такое же, только своё.</p>
|
||||
<p>Stay tuned!</p>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
2020-11-08
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
109
docs/posts/2020/typographic_linter/index.html
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
typographic linter | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="Prettier для текста. Автоматизация рутинной типографики.">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/">
|
||||
posts</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/#2020">
|
||||
2020</a></li>
|
||||
|
||||
|
||||
<li>typographic linter</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="-code-style">Типографика как code style</h1>
|
||||
<h2 id="-">Основы</h2>
|
||||
<p>Для того, чтобы тексты было удобно читать и неудобно писать, человечество придумало разные правила. С орфографией и пунктуацией всё понятно, но про типографические правила говорят редко.</p>
|
||||
<p>Типографика — набор правил вёрстки текста, призванных повысить эстетику и читаемость. Именно в таком порядке, потому что люди сначала замечают текст и только потом начинают читать.</p>
|
||||
<p>Про шрифты, отступы и прочие кернинги в этот раз говорить не будем, потому что мне понравилось крутить ползунки в <a href="https://github.com/KyleAMathews/typography.js">Typography.js</a> и не переживать об эстетике.</p>
|
||||
<p>Разговор пойдёт про висячие предлоги и прочие тире с кавычками.</p>
|
||||
<h2 id="-">Проблематика</h2>
|
||||
<p>Сайты в интернете делятся на два типа: те, кто запаривается за подготовку текстов, и те, кто забивает. <a href="https://meduza.io/">Meduza.io</a> расставляет неразрывные пробелы в текстах новостей, а <a href="https://www.rbc.ru/">rbc.ru</a> — нет. Это не единственное отличие этих новостных порталов, но очень знаковое, как мне кажется.</p>
|
||||
<h3 id="-">Нужно забить!</h3>
|
||||
<p>Я прекрасно понимаю тех, кто не запаривается. Часть не знает, часть забивает (<a href="https://vas3k.ru/">Вастрик</a>, например), а у остальных user-generated content, который довольно рискованно автоматически форматировать. Аргументы для каждой группы:</p>
|
||||
<ul>
|
||||
<li>И так всё понятно.</li>
|
||||
<li>Условный Markdown со спецсимволами теряет половину обаяния.</li>
|
||||
<li>Правильные автоматические кавычки-ёлочки не сделают из комментария в Facebook литературный памятник, зато случайный японский эмотикон может разрушить цивилизацию: <br>
|
||||
<code>Прости ^_^" Забыл купить "Доширак"</code> превращается в <br>
|
||||
<code>Прости ^_^« Мы испортили »Твой текст«</code>.</li>
|
||||
<li>У студентов должен быть отдельный курс по расшифровке смайликов и компилированию C++ из чатов. Бонусный аргумент.</li>
|
||||
</ul>
|
||||
<h3 id="-">Нужно запариться!</h3>
|
||||
<p>Википедия утверждает, что <a href="https://ru.wikipedia.org/wiki/%D0%A2%D0%B8%D0%BF%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%BA%D0%B0">типографика</a> — это «свод строгих правил», но список не показывает и ссылок не даёт. Стандартом де-факто в рунете являются правила из <a href="https://www.artlebedev.ru/kovodstvo/sections/62/">§ 62 «Ководства»</a>. С ними или соглашаются, или <a href="https://medium.com/@kapanaga/62-6c664105dd30">громко спорят с некоторыми</a>.</p>
|
||||
<p>Пусть мне и не нравится полное отсутствие у Лебедева обоснований или ссылок на источники, но самому погружаться в историю вопроса и читать какой-нибудь авторитетный <a href="https://www.artlebedev.ru/izdal/spravochnik-izdatelya-i-avtora-2017/">«Справочник издателя и автора»</a> страшно: в нём 1010 страниц.</p>
|
||||
<p>Авторы, которые хотят радовать читателей с помощью тире, обычно не пишут в блокноте (мне так кажется) или носят свои тексты из блокнота в <a href="https://www.artlebedev.ru/typograf/">Типограф</a> от Артемия Лебедева, а потом назад. Про первых я ничего не знаю, а ко вторым сам отношусь, когда нужно причесать один или два текста.</p>
|
||||
<p>У Типографа и аналогичных веб-сервисов есть фундаментальные проблемы:</p>
|
||||
<ul>
|
||||
<li>Копировать текст из редактора в браузер, а потом назад — неудобно.</li>
|
||||
<li>Сервис может быть офлайн в неподходящий момент, я тоже.</li>
|
||||
<li>Сервис может читать мои тексты до публикации.</li>
|
||||
<li>Некоторые тексты никому нельзя показывать (по разным причинам).</li>
|
||||
</ul>
|
||||
<p>Признаюсь, что раньше я ни разу не думал об использовании типографа вне браузера, но как только образовалась перспектива заниматься этим на постоянной основе, стало понятно, что копировать текст из редактора в браузер и назад — не самое оптимальное решение и нужно что-то с этим делать.</p>
|
||||
<h2 id="-">Решение</h2>
|
||||
<p>Очевидный вариант — научить текстовый редактор автоматически расставлять неразрывные пробелы, исправлять мелкие опечатки, приводить кавычки к правильному виду, заменять дефисы на тире в нужных местах и многое другое. <br>
|
||||
Вот <a href="https://marketplace.visualstudio.com/items?itemName=rusnasonov.vscode-typograf">плагин для VS Code</a>.</p>
|
||||
<p>Мне больше нравится другой подход. Мы уже давно используем линтеры для языков программирования, давайте использовать их и для контента.</p>
|
||||
<p>Конечно, я не первый, кто так подумал. Плагин выше построен на основе замечательной библиотеки <a href="https://github.com/typograf/typograf">typograf</a> и прямо в README.md файле этого проекта можно найти ссылки на плагины для Babel, Gulp и Grunt.</p>
|
||||
<p>Библиотека очень гибко настраивается, содержит <a href="https://github.com/typograf/typograf/blob/dev/docs/RULES.ru.md">сотню готовых правил</a> и позволяет легко добавлять кастомные.</p>
|
||||
<p>В комплекте есть cli-утилита, которая умеет читать файлы, форматировать их и выводить результат в терминал, но почему-то не умеет файл перезаписывать. Поэтому для своих целей я написал небольшой скрипт, который, получив локаль и имя файла, изменяет его содержимое. С правилами тоже пришлось немного поиграть, потому что дефолтные принимают списки за диалоги и ломают markdown-разметку.</p>
|
||||
<p>Теперь для расстановки всех хитрых значков не нужно ходить в браузер, достаточно просто запустить скрипт, посмотреть с помощью <code>git diff</code> на результат и закоммитить нужные изменения.</p>
|
||||
<p>Красивые тексты лучше некрасивых.</p>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
2020-11-18
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
25
docs/posts/2024/index.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="description"
|
||||
content="Redirect to '/posts/#2024'.">
|
||||
<meta
|
||||
http-equiv="Refresh"
|
||||
content="0; URL=/posts/#2024">
|
||||
<title>
|
||||
Redirect | oddsquat
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
Redirect to
|
||||
<a
|
||||
style="color: inherit;"
|
||||
href="/posts/#2024">
|
||||
/posts/#2024
|
||||
</a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
317
docs/posts/2024/selfhosted_llm/index.html
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
selfhosted LLM | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="Персональные LLM в docker-контейнере на твоём компьютере.">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/">
|
||||
posts</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/#2024">
|
||||
2024</a></li>
|
||||
|
||||
|
||||
<li>selfhosted LLM</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="your-own-private-large-language-models">Your Own Private Large Language Models</h1>
|
||||
<p>С одной стороны, я сомневаюсь, что Большие Языковые Модели (LLM) смогут однажды эволюционировать в AGI. С другой — я по-настоящему впечатлён тем, что щепотка статистики справляется с написанием текстов лучше меня.</p>
|
||||
<p>В любом случае, джинна обратно в бутылку уже не вернуть, и все те письма в различные организации, которые я не хочу писать сам, будут теперь написаны месивом из байтиков.</p>
|
||||
<p>Действительно пугает меня в этой ситуации только то, что флагманом новой эры почему-то стала компания OpenAI, которая, вопреки названию, совершенно не Open.
|
||||
Множество компаний и людей вписали их продукты в свою рутину и не страшатся такой неподконтрольной зависимости.</p>
|
||||
<p>Я так не могу. К счастью, я не один такой, и на данный момент уже есть множество альтернативных моделей от разных вендоров. Они отличаются друг от друга качеством, размером, возможностями и лицензиями, так что при желании можно надолго занять себя знакомством с обширным ассортиментом. Например, на портале <a href="https://huggingface.co/">HuggingFace</a>, который можно описать как «GitHub для LLM и всего, что вокруг».</p>
|
||||
<p>Должен признаться, что очень слабо разбираюсь в параметрах и характеристиках языковых моделей, но оказалось, что для того, чтобы начать, эти знания не так уж и необходимы.</p>
|
||||
<p>Ниже инструкция, как запустить LLM на своём железе, как упаковать всё это в docker-контейнер, чтобы не размазать случайно по всей файловой системе, как получить совместимый с OpenAI API и как потом этим пользоваться.</p>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="#setup">Установка и настройка</a><ul>
|
||||
<li><a href="#setup-ollama">Установка Ollama</a></li>
|
||||
<li><a href="#setup-model">Загрузка модели и диалог с ней</a></li>
|
||||
<li><a href="#custom-model">Кастомные модели и их тонкая настройка</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#usage">Использование</a><ul>
|
||||
<li><a href="#fake-open-ai">Мимикрия под API от OpenAI</a></li>
|
||||
<li><a href="#ollama-nvim">Интеграция с NeoVim</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#update-delete">Обновление и удаление</a></li>
|
||||
<li><a href="#performance">Производительность</a></li>
|
||||
<li><a href="#why">Зачем всё это нужно?</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id='setup'>
|
||||
Установка и настройка
|
||||
</h2>
|
||||
|
||||
<p>Существует несколько продуктов, которые стараются избавить пользователя от головной боли и возни с инфраструктурой. Мне понятнее всего оказался проект <a href="https://ollama.ai/">Ollama</a>, с ним мы и будем экспериментировать.</p>
|
||||
<p>Кроме бинарников для Linux и MacOS, они <a href="https://ollama.ai/blog/ollama-is-now-available-as-an-official-docker-image">предоставляют</a> официальный <a href="https://hub.docker.com/r/ollama/ollama">docker-образ</a>, работу с которым я и опишу.</p>
|
||||
<p>Использование docker-контейнеров, к сожалению, слегка усложняет взаимодействие с Ollama, так что большая часть текста и кода в этом посте посвящены решению проблем, которые, по сути, я придумал себе сам.</p>
|
||||
<h3 id='setup-ollama'>
|
||||
Установка Ollama
|
||||
</h3>
|
||||
|
||||
<p>Для создания и первого запуска контейнера нужно выполнить команду:</p>
|
||||
<pre><code>docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama</code></pre><p>Счастливые владельцы видеокарт от Nvidia могут установить <a href="https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installation">Nvidia container toolkit</a> и активировать поддержку GPU с помощью флага <code>--gpus=all</code>.</p>
|
||||
<p>После создания запускать и останавливать контейнер <code>ollama</code> можно так:</p>
|
||||
<pre><code>docker start ollama
|
||||
docker stop ollama</code></pre><p>Контейнер предоставляет доступ к <a href="https://github.com/jmorganca/ollama/blob/main/docs/api.md">Ollama API</a> на 11434 порту, а также позволяет устанавливать и общаться с установленными LLM через терминал.</p>
|
||||
<h3 id='setup-model'>
|
||||
Загрузка модели и диалог с ней
|
||||
</h3>
|
||||
|
||||
<p>Ollama позволяет запускать любые GGUF, PyTorch или Safetensors модели (что бы это ни значило), но самый простой путь — загрузка моделей из специальной <a href="https://ollama.ai/library">библиотеки</a>.</p>
|
||||
<p>Для того, чтобы скачать модель и начать с ней диалог, нужно выполнить команду:</p>
|
||||
<pre><code>docker exec -it ollama ollama run mistral</code></pre><p>Вместо <code>mistral</code> от одноимённой комании можно выбрать любую другую модель из библиотеки, например, легкую <code>phi</code> от Microsoft Research.</p>
|
||||
<p>Кроме <code>run</code> доступны также <code>list</code>, <code>pull</code> и <code>rm</code> для просмотра списка, скачивания и удаления моделей соответственно.</p>
|
||||
<p>Чтобы не писать такие длинные заклинания каждый раз, я добавил в <code>.zshrc</code> пару алиасов:</p>
|
||||
<pre><code>alias summonable='docker exec -it ollama ollama list'
|
||||
alias summon='clear && docker exec -it ollama ollama run'</code></pre><p>Теперь можно смотреть на список установленных моделей и запускать диалог с выбранной:</p>
|
||||
<pre><code>summonable
|
||||
summon phi</code></pre><h3 id='custom-model'>
|
||||
Кастомные модели и их тонкая настройка
|
||||
</h3>
|
||||
|
||||
<p>Ollama позволяет на основе существующих создавать производные модели с заранее определёнными инструкциями или параметрами. Для этого нужно создать специальный файл, в котором указана родительская модель и определены желаемые значения параметров. Подробнее о формате этих файлов можно прочесть в документации: <a href="https://github.com/jmorganca/ollama/blob/main/docs/modelfile.md">Modelfile</a>.</p>
|
||||
<blockquote>
|
||||
<p>В какой-то момент Ollama Web UI превратился в Open WebUI, а OllamaHub прекратил существовать. Все ссылки в следующем абзаце больше не представляют какой-либо ценности.</p>
|
||||
</blockquote>
|
||||
<p>Чтобы посмотреть, как должен выглядеть Modelfile, можно посетить <a href="https://ollamahub.com/">OllamaHub</a> от разработчиков стороннего <a href="https://github.com/ollama-webui/ollama-webui/">Ollama Web UI</a>. На сайте есть <a href="https://ollamahub.com/m/smoothbrainape/hu-tao:latest">примеры очень тонкой настройки множества параметров</a> модели для соответствия образу конкретного персонажа, но в качестве образца я буду использовать небольшой <a href="https://ollamahub.com/m/kamjin/english-teacher:latest">English Teacher Modelfile</a>:</p>
|
||||
<h4 id="englishteacher-modelfile">EnglishTeacher.Modelfile</h4>
|
||||
<pre><code>FROM llama2
|
||||
SYSTEM """
|
||||
I want you to act as a English teacher.
|
||||
Your main responsibility will be to instruct me in all aspects of English, including grammar, vocabulary, reading, writing and speaking.
|
||||
You should always take the initiative to correct my mistakes in grammar and vocabulary, and you can give me two or three examples at any appropriate time to help me understand better.
|
||||
"""</code></pre><p>Вообще, для загрузки кастомной модели достаточно выполнить команду <code>create</code>, но в случае использования Ollama внутри docker-контейнера возникает необходимость каким-то образом файл с моделью в этот контейнер передать.</p>
|
||||
<p>Для решения этой проблемы я набросал небольшой bash-скрипт:</p>
|
||||
<h4 id="applymodelfile-bash">applyModelfile.bash</h4>
|
||||
<pre><code class="language-bash">#!/bin/bash
|
||||
|
||||
containerId=$(docker ps | grep ollama/ollama | cut -d' ' -f1)
|
||||
|
||||
echo "Container ID:"
|
||||
echo $containerId
|
||||
echo ""
|
||||
|
||||
if [ -z "$containerId" ]; then
|
||||
echo "Container does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
modelName=$1
|
||||
echo "Model name:"
|
||||
echo $modelName
|
||||
echo
|
||||
|
||||
sourcePath="./models/${modelName}.Modefile"
|
||||
targetPath="/home/${modelName}.Modefile"
|
||||
|
||||
if ! test -f $sourcePath; then
|
||||
echo "File does not exist."
|
||||
echo $sourcePath
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker cp $sourcePath "${containerId}:${targetPath}"
|
||||
clear
|
||||
docker exec -it ollama ollama create $modelName -f $targetPath</code></pre>
|
||||
<p>Этот скрипт нужно поместить по соседству с директорией <code>models</code> и сделать исполняемым с помощью <code>chmod +x applyModelfile.bash</code>.
|
||||
Должна получиться примерно такая структура:</p>
|
||||
<pre><code>├── models
|
||||
│ └── EnglishTeacher.Modefile
|
||||
└── applyModelfile.bash</code></pre><p>После этого модель можно загрузить в контейнер и начать с ней диалог:</p>
|
||||
<pre><code>./applyModelfile.bash EnglishTeacher
|
||||
summon EnglishTeacher</code></pre><h2 id='usage'>
|
||||
Использование
|
||||
</h2>
|
||||
|
||||
<p>Разговоры с галлюцинирующим искусственным интеллектом в терминале — это, конечно, волшебно, но потенциал больших языковых моделeй по-настоящему раскрывается, когда они начинают портить данные в соседних приложениях!</p>
|
||||
<p>В <a href="https://github.com/jmorganca/ollama#community-integrations">GitHub-репозитории Ollama</a> можно найти ссылки на множество веб-интерфейсов, библиотек и плагинов для текстовых редакторов и прочих Obsidian'ов.</p>
|
||||
<p>Не ручаюсь за весь список, но расскажу про то, с чем экспериментировал сам.</p>
|
||||
<h3 id='fake-open-ai'>
|
||||
Мимикрия под API от OpenAI
|
||||
</h3>
|
||||
|
||||
<blockquote>
|
||||
<p>В какой-то момент <a href="https://ollama.com/blog/openai-compatibility">в Ollama появилась поддержка совместимости с форматом API от OpenAI</a> и этот раздел потерял актуальность.</p>
|
||||
</blockquote>
|
||||
<p>API Ollama используется в меньшем числе продуктов, чем API от OpenAI. К счастью, это не проблема: с помощью прокси-прослойки под названием <a href="https://github.com/BerriAI/litellm">LiteLLM</a> можно сделать их совместимыми. Инструкция по установке и использованию в общем случае есть в репозитории и довольно тривиальна, но мне опять потребовалось немного кода, чтобы заставить их работать вместе на моих условиях.</p>
|
||||
<p>Я хотел, чтобы LiteLLM-прокси и Ollama работали на разных компьтерах, и не хотел ставить pip-пакеты в систему. В результате родилось решение из docker-файла с хаками и скрипта, который в нём запускается. Я не специалист в написании docker-файлов, так что уверен в неоптимальности финального решения. Точно можно и нужно обойтись без <code>run --net=host</code> и отдельного скрипта, например.</p>
|
||||
<p>Несмотря на костыльность связки, она справляется со своей задачей:</p>
|
||||
<h4 id="dockerfile">Dockerfile</h4>
|
||||
<pre><code class="language-Dockerfile">FROM python:3.10
|
||||
|
||||
COPY startProxy.sh /usr/src/app/startProxy.sh
|
||||
RUN chmod +x /usr/src/app/startProxy.sh
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Prevent ollama run attempt
|
||||
RUN echo '#!/bin/sh\necho "$1"' > /usr/bin/ollama && \
|
||||
chmod +x /usr/bin/ollama
|
||||
|
||||
CMD ["./startProxy.sh"]</code></pre>
|
||||
<h4 id="startproxy-sh">startProxy.sh</h4>
|
||||
<pre><code class="language-sh">#!/bin/bash
|
||||
|
||||
pip install litellm
|
||||
|
||||
litellm --model ollama/mistral --api_base http://ollama.internal:11434 --drop_params</code></pre>
|
||||
<p>Собрать и запустить docker-контейнер можно с помощью этих двух команд:</p>
|
||||
<pre><code class="language-sh">docker build -t diy-ollama-proxy .
|
||||
docker run --net=host diy-ollama-proxy</code></pre>
|
||||
<p>После запуска вы получите API, который совместим с API от OpenAI и доступен по адресу <code>http://localhost:8000/</code>.</p>
|
||||
<h3 id='ollama-nvim'>
|
||||
Интеграция с NeoVim
|
||||
</h3>
|
||||
|
||||
<p>Языковые модели отлично умеют взаимодействовать с текстом, так что использование их в текстовом редакторе кажется разумной идеей.</p>
|
||||
<p>Мне не очень нравится идея Copilot, который зачем-то постоянно подсовывает тебе странные куски кода. Я пробовал использовать <a href="https://codeium.com/">Codeium</a> в ручном режиме, но оказалось, что странные куски кода по запросу мне тоже не очень нужны. Гораздо более привлекательной мне кажется возможность выделить существующий фрагмент текста или кода и попросить бездушную машину что-нибудь с ним сделать: упростить, дополнить, изменить или даже перевести с одного языка на другой. Идеальным для такого подхода оказался <a href="https://github.com/nomnivore/ollama.nvim">плагин ollama.nvim</a>.</p>
|
||||
<p>Кроме того, что он поддерживает кастомные промпты (в том числе интерактивные), он позволил мне обращаться к LLM, которая запущена на другом компьютере в локальной сети (для удобства я указал его адрес в <code>/etc/hosts/</code>).</p>
|
||||
<p>Установка и настройка с использованием пакетного менеджера lazy.nvim выглядит примерно так:</p>
|
||||
<pre><code>{
|
||||
'nomnivore/ollama.nvim',
|
||||
dependencies = {
|
||||
'nvim-lua/plenary.nvim',
|
||||
},
|
||||
cmd = { 'Ollama', 'OllamaModel' },
|
||||
keys = {
|
||||
{
|
||||
'<leader>j',
|
||||
':Ollama<CR>',
|
||||
desc = 'Ollama Menu',
|
||||
mode = { 'v' },
|
||||
},
|
||||
{
|
||||
'<leader>j',
|
||||
":lua require('ollama').prompt('Generate_Code')<cr>",
|
||||
desc = 'Ollama Code Generation',
|
||||
mode = { 'n' },
|
||||
},
|
||||
},
|
||||
opts = {
|
||||
model = 'mistral',
|
||||
url = 'http://ollama.internal:11434', -- see /etc/hosts
|
||||
prompts = {
|
||||
Ask_About_Code = false,
|
||||
Simplify_Code = false,
|
||||
Improve_Text = {
|
||||
prompt = 'Check the following sentence for grammar and clarity: "$sel".\nRewrite it for better readability while maintaining its original meaning.',
|
||||
extract = false,
|
||||
action = 'replace',
|
||||
},
|
||||
Modify_Text = {
|
||||
prompt = 'Modify this text in the following way: $input\n\n```$sel```',
|
||||
extract = false,
|
||||
action = 'replace',
|
||||
},
|
||||
Use_Selection_as_Prompt = {
|
||||
prompt = '$sel',
|
||||
extract = false,
|
||||
action = 'replace',
|
||||
},
|
||||
},
|
||||
},
|
||||
},</code></pre><p>В этом конфиге я выключил несколько дефолтных промптов и добавил несколько своих:</p>
|
||||
<ul>
|
||||
<li><code>Improve_Text</code> заменяет выделенный кусок текста «улучшенным».</li>
|
||||
<li><code>Modify_Text</code> является аналогом встроенного <code>Modify_Code</code> и позволяет делать с выделенным текстом всякие глупости. Например, заменить все числа на слова.</li>
|
||||
<li><code>Use_Selection_as_Prompt</code> просто заменяет выделенный текст на ответ от LLM.</li>
|
||||
</ul>
|
||||
<p>В итоге получается два сценария использования, оба доступны по <code><leader> + j</code>:</p>
|
||||
<ul>
|
||||
<li>В <code>normal</code> mode плагин спрашивает меня, какой код мне нужен, и вставляет его.</li>
|
||||
<li>В <code>visual</code> mode появляется меню действий над выделенным текстом.</li>
|
||||
</ul>
|
||||
<p>Как видно из конфига, я использую только <code>mistral</code>, но можно указать модель для кажого промпта и делегировать, например, манипуляции над кодом <code>codellama</code>, а операции над текстом — <code>llama2</code>.</p>
|
||||
<p>Возможность добавления кастомных промптов позволяет в будущем реализовать новые сценарии или вынести повторяющиеся действия в отдельный пункт меню или даже на отдельный шорткат.</p>
|
||||
<h2 id='update-delete'>
|
||||
Обновление и удаление
|
||||
</h2>
|
||||
|
||||
<p>Для обновления и удаления моделей можно использовать команды <code>pull</code> и <code>rm</code>:</p>
|
||||
<pre><code>docker exec -it ollama ollama pull mixtral
|
||||
docker exec -it ollama ollama rm mistral</code></pre><p>Я знаю, что для обновления и удаления docker-образов и docker-контейнеров тоже есть специальные команды (это тоже <code>pull</code> и <code>rm</code>), но каждый раз ленюсь в этом разобраться, просто сношу всё с помощью утилиты <a href="https://github.com/TomasTomecek/sen">sen</a> и разворачиваю нужное заново.</p>
|
||||
<h2 id='performance'>
|
||||
Производительность
|
||||
</h2>
|
||||
|
||||
<p>Для эксплуатации LLM требуется гораздо меньше ресурсов, чем для её обучения. Запустить 7b-модель средней тупости можно практически на любом CPU и 8 GB RAM, но нагрузка на систему и скорость генерации ответов часто будут далеки от комфортных значений.</p>
|
||||
<p>Например, на моём немолодом Intel Core i7-10510U @ 8x 4.9GHz неаккуратный запрос к <code>llama2</code> может заставить систему шуршать вентиляторами пару-тройку минут. При этом <code>phi</code> на этом же процессоре способна отвечать на какие-нибудь не очень сложные вопросы практически мгновенно.</p>
|
||||
<p>К счастью, у меня случайно завалялся MacBook на процессоре M1 и он уже показывает куда более впечатляющие результаты. <code>Mistral</code> даже на непростые запросы отвечает за считанные секунды, а в режиме чата токены вылетают на экран заметно быстрее, чем в веб-интерфейсе ChatGPT.</p>
|
||||
<p>Неприятным открытием стало то, что docker-версия Ollama на MacOS выполняется заметно медленнее (от 3 до 5 раз, если верить ощущениям), чем нативная. Возможно, всё дело в том, что я как-то неправильно настроил docker или приложение в контейнере нужно запускать с какими-нибудь специальными флагами для максимальной утилизации ресурсов. В любом случае, к порядку на этом ноутбуке я отношусь гораздо менее трепетно, поэтому просто установил и использую приложение с сайта Ollama.</p>
|
||||
<h2 id='why'>
|
||||
Зачем всё это нужно?
|
||||
</h2>
|
||||
|
||||
<p>Конечно, ChatGPT умнее и умеет из коробки гораздо больше.<br>
|
||||
Конечно, ChatGPT требует меньше телодвижений для использования.<br>
|
||||
Конечно, самые умные модели требуют внушительных ресурсов, ведь для запуска нашумевшей <a href="https://ollama.ai/library/mixtral">mixtral</a> или аналогичной модели нужно иметь 48 Gb оперативной памяти.<br>
|
||||
Конечно, сидя в кафе задать вопрос Bard от Google гораздо проще, чем достучаться до модели в закрытом ноутбуке, который остался дома.<br></p>
|
||||
<p>Я всё это прекрасно понимаю, но ничего из этого не стоит того, чтобы добровольно ставить себя в зависимость от монополистов с их закрытыми чёрными ящиками.</p>
|
||||
<p>Даже если закрыть глаза на все идеологические вопросы, то любая локальная LLM отличается от любого облачного провайдера тем, что:</p>
|
||||
<ul>
|
||||
<li>Может работать в оффлайне.</li>
|
||||
<li>Не хранит и не сливает вашу переписку.</li>
|
||||
<li>Не станет завтра тупее, чем есть сегодня.</li>
|
||||
<li>Не забанит тебя за возмутивший кого-то там запрос.</li>
|
||||
<li>Обладает тем уровнем цензуры, который выбрал ты сам.</li>
|
||||
</ul>
|
||||
<p>Я искренне рад, что для доступа даже к передовым технологиям, всё ещё не обязательно поступаться своей приватностью и своими свободами.</p>
|
||||
<hr>
|
||||
<p>Этот пост написан без использования LLM =)</p>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
2024-01-15
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
152
docs/posts/2024/wrapped_bw_ru/index.html
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
wrapped bw | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="Превращаем fully-featured Bitwarden command-line interface в удобный.">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/">
|
||||
posts</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/#2024">
|
||||
2024</a></li>
|
||||
|
||||
|
||||
<li>wrapped bw</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="-bitwarden-cli-fzf-">Интеграция Bitwarden CLI с fzf и буфером обмена</h1>
|
||||
<p>Менеджер паролей — это специальное приложение, которое помогает делать вид, что я помню разные пароли для разных аккаунтов, а не ввожу везде один и тот же. Мне нравится Bitwarden: открытый исходный код, возможность поднять собственный сервер, клиенты под разные устройства и расширения под разные браузеры.</p>
|
||||
<p>Самым удобным, внезапно, оказался клиент для Android, который не заставляет меня каждый раз вводить 12+ знаков мастер-пароля (такую длину требуют Bitwarden и здравый смысл), а может быть разблокирован с помощью биометрии. Похожего удобства захотелось достичь и на Linux.</p>
|
||||
<h2 id="-cli">Дикий CLI</h2>
|
||||
<p>На ноутбуке я использую <a href="https://bitwarden.com/help/cli/">Bitwarden CLI</a>. Это powerful, fully-featured tool, которым на практике оказалось не слишком удобно пользоваться, если ты человек.</p>
|
||||
<h3 id="-">Ключи от ключей</h3>
|
||||
<blockquote>
|
||||
<p>You are responsible for maintaining your session key.</p>
|
||||
</blockquote>
|
||||
<p>Bitwarden CLI поддерживает <a href="https://bitwarden.com/help/cli/#using-a-session-key">механизм сессий</a>, который призван избавить пользователя от бесконечного ввода мастер-пароля. Приложение позволяет разблокировать хранилище и получить временный сессионный ключ, который можно либо хранить в беззащитной переменной окружения, либо прикладывать к каждому запросу вручную.</p>
|
||||
<p>По сути своей, сессионный ключ отличается от мастер-пароля тем, что его можно моментально деактивировать, но совершенно невозможно запомнить, а, значит, нужно где-то хранить. Хочется делать это удобно и безопасно, а не в общедоступной переменной окружения.</p>
|
||||
<h3 id="-stdout">Пароли в stdout</h3>
|
||||
<blockquote>
|
||||
<p>The get command can only return one result, so you should use specific search terms. If multiple results are found, the CLI will return an error.</p>
|
||||
</blockquote>
|
||||
<p>Где-то в этот момент чтения документации я окончательно начал подозревать, что официальный CLI предназначен для скриптов: всё строго, никакого автодополнения, никакого интерактивного поиска, а пароли лаконично вываливаются в стандартный вывод терминала, откуда их ещё нужно как-то переправить в место назначения.</p>
|
||||
<h2 id="-cli">Приручение CLI</h2>
|
||||
<p>Может показаться, что я ругаюсь, но отсутствие удобств и излишеств в официальном CLI — это хорошо:</p>
|
||||
<ul>
|
||||
<li>Отсутствие фич всегда приятнее, чем кривые фичи.</li>
|
||||
<li>Минимализм упрощает жизнь мейнтейнерам.</li>
|
||||
<li>Минимализм повышает надёжность.</li>
|
||||
<li>Меньше сторонних зависимостей.</li>
|
||||
<li>Стандартные интерфейсы идеально подходят для автоматизации.</li>
|
||||
</ul>
|
||||
<p>Идея сделать Bitwarden CLI удобнее, разумеется, пришла в голову не только мне, так что на GitHub предсказуемо быстро нашёлся <a href="https://gist.github.com/loeschzwerg/c2b9d0b50f712a026aa6454af3b58598">скрипт-обёртка</a> от <a href="https://github.com/loeschzwerg">@loeschzwerg</a>. Этот ZSH-скрипт менее требователен к пользователю и позволяет в случае, когда под пользовательский поисковый запрос подходит несколько аккаунтов, выбрать нужный из списка с помощью fzf и автоматически скопировать логин, пароль и даже TOTP в буфер обмена.</p>
|
||||
<p>К сожалению, найденный скрипт никак не решал проблему управления сессиями, так что я решил его немного доработать, избавив заодно от избытка многоточий в интерфейсе.</p>
|
||||
<h3 id="-">«Безопасное» хранение сессионного ключа</h3>
|
||||
<p>Как я писал выше, мне нравится подход Android-клиента: нужно один раз ввести свой невероятно длинный мастер-пароль, после чего можно разблокировать хранилище отпечатком пальца. В ходе непродолжительных размышлений я решил, что самое простое и надёжное подобие для приложения в терминале — один раз получить сессионный ключ и сохранить его в файл, который будет доступен для чтения только пользователю <code>root</code> и недоступен любым другим приложениям, запущенным от имени текущего пользователя.</p>
|
||||
<p>Приятный бонус для владельцев биометрических сканеров: они отлично интегрируются с утилитой <code>sudo</code>.</p>
|
||||
<p>В результате скрипт обогатился двумя функциями и одной проверкой:</p>
|
||||
<pre><code class="language-zsh">local sessionfile="$HOME/.bitwarden_session"
|
||||
|
||||
get_saved_sessionkey () {
|
||||
sudo touch $sessionfile
|
||||
echo $(sudo cat $sessionfile)
|
||||
}
|
||||
|
||||
save_sessionkey () {
|
||||
local sessionkey=$1
|
||||
sudo chmod 600 $sessionfile
|
||||
sudo sh -c "echo $sessionkey > $sessionfile"
|
||||
}</code></pre>
|
||||
<pre><code>local sessionkey=$(get_saved_sessionkey)
|
||||
|
||||
if [[ -z $sessionkey ]] ; then
|
||||
# Get and save a new session key
|
||||
sessionkey=$(bw unlock --raw)
|
||||
save_sessionkey $sessionkey
|
||||
else
|
||||
echo "Using the existing session key from '$sessionfile'."
|
||||
fi</code></pre><p>При первом запуске сессионный ключ, полученный после ввода мастер-пароля, записывается в файл, который после выполнения команды <code>chmod 600</code> становится недоступен для чтения никому, кроме суперпользователя:</p>
|
||||
<pre><code>~ » ls -lah
|
||||
...
|
||||
-rw-------. 1 root root 89 Jul 24 22:15 .bitwarden_session
|
||||
...
|
||||
|
||||
~ » less .bitwarden_session
|
||||
.bitwarden_session: Permission denied</code></pre><p>Парольный менеджер и скрипт-обёртка запускаются от имени текущего пользователя, повышение привилегий требуется только в момент записи и чтения сессионного ключа.</p>
|
||||
<p>Деактивировать сохранённый ключ можно с помощью команды <code>bw lock</code>.
|
||||
К сожалению, я так и не понял, как с помощью утилиты <code>bw</code> можно проверить, валиден ли ключ, так что после деактивации придётся удалить файл <code>~/.bitwarden_session</code> вручную, иначе скрипт так и будет подставлять протухший сохранённый ключ, а <code>bw</code> будет каждый раз игнорировать его и настойчиво спрашивать мастер-пароль.</p>
|
||||
<p><strong>Update [2026-03-29]</strong>:
|
||||
Нормального способа проверить валидность сессионного ключа <a href="https://github.com/bitwarden/clients/issues/9254">всё ещё нет</a>,
|
||||
но я научил утилиту удалять файл с протухшим ключом по косвенным признакам.</p>
|
||||
<h2 id="-">Применять с осторожностью</h2>
|
||||
<p>Взаимодействие с менеджером паролей выглядит для меня теперь примерно так:</p>
|
||||
<pre><code>~ » bwc github
|
||||
[sudo] password for $USER:
|
||||
Using the existing session key from '/home/$USER/.bitwarden_session'.
|
||||
Searching for 'github'...
|
||||
|
||||
abcdefgh-ijkl-mnop-qrst-uvwxyz123456
|
||||
github.com
|
||||
|
||||
Username 'username' copied to clipboard.
|
||||
[Press any key to copy the password]
|
||||
Password copied to clipboard.</code></pre><p>Финальный вариант скрипта можно найти в репозитории <a href="https://github.com/He4eT/fuzzy-bitwarden-clipboard">He4eT/fuzzy-bitwarden-clipboard</a>.</p>
|
||||
<p>Настоятельно рекомендую читать любой код перед тем, как запускать его. Особенно в тех случаях, когда речь идёт о настолько чувствительных данных.</p>
|
||||
<p><strong>Важно!</strong> На системах без шифрования диска все эти танцы с правами на доступ к файлу не несут никакой пользы и превращают затею в увлекательный цирк.</p>
|
||||
<p>Нельзя исключать, что я что-то совершенно неправильно понимаю в принципах работы системы прав доступа в Linux и совершил какие-нибудь грубейшие, с точки зрения настоящих специалистов по информационной безопасности, ошибки. Пожалуйста, сообщите, если я где-то неправ.</p>
|
||||
<p>Нужно помнить, что такое упрощение жизни ведёт к новым рискам: теперь любой, кто знает ваш пароль для учётной записи системного пользователя и имеет доступ к компьютеру, будет также иметь доступ и ко всем паролям, сохранённым в Bitwarden.</p>
|
||||
<p>Пользуйтесь с осторожностью и/или храните свои пароли в надёжных местах =)</p>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
2024-07-27
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1326
docs/posts/2026/ugly_keyboards_ru/index.html
Normal file
139
docs/posts/index.html
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
posts | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="Сomplete list of posts">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li>posts</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="posts">Posts</h1>
|
||||
<p>Announcements are available via <a href="/rss.xml">RSS</a>.</p>
|
||||
<hr>
|
||||
<h2 id="2026">2026</h2>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
[ru]
|
||||
<strong>
|
||||
<a href='/posts/2026/ugly_keyboards_ru/'>ugly keyboards</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Почему нас окружают уродливые клавиатуры и что с этим можно сделать.
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="2024">2024</h2>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
[ru]
|
||||
<strong>
|
||||
<a href='/posts/2024/wrapped_bw_ru/'>wrapped bw</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Превращаем fully-featured Bitwarden command-line interface в удобный.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
[ru]
|
||||
<strong>
|
||||
<a href='/posts/2024/selfhosted_llm/'>selfhosted LLM</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Персональные LLM в docker-контейнере на твоём компьютере.
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="2020">2020</h2>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
[ru]
|
||||
<strong>
|
||||
<a href='/posts/2020/typographic_linter/'>typographic linter</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Prettier для текста. Автоматизация рутинной типографики.
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
[ru]
|
||||
<strong>
|
||||
<a href='/posts/2020/initial_post/'>initial post</a>
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Какие инструменты были использованы для создания этого сайта, какие решения были приняты и почему.
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
351
docs/projects/index.html
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
projects | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="Incomplete list of projects and experiments.">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
|
||||
|
||||
<li>projects</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="projects">Projects</h1>
|
||||
<p>Incomplete list of my projects and experiments.</p>
|
||||
<hr>
|
||||
<ul>
|
||||
<li><a href="#interactive-fiction-tools">Interactive Fiction Tools</a></li>
|
||||
<li><a href="#games">Games</a></li>
|
||||
<li><a href="#services">Services</a></li>
|
||||
<li><a href="#inventory">Inventory</a><ul>
|
||||
<li><a href="#hardware">Hardware</a></li>
|
||||
<li><a href="#settings">Settings</a></li>
|
||||
<li><a href="#software-plugins-and-tools">Software, Plugins and Tools</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id="interactive-fiction-tools">Interactive Fiction Tools</h2>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
ElseIFPlayer
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Interactive Fiction player for the web.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/elseifplayer' target='_blank'>repository</a>
|
||||
| <a href='https://www.ifwiki.org/ElseIFPlayer' target='_blank'>ifwiki</a>
|
||||
| <a href='https://he4et.github.io/elseifplayer/' target='_blank'>app</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
cheap-glkote
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Abstract implementation of the GlkOte library interface
|
||||
designed to be used with Emglken.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/cheap-glkote' target='_blank'>repository</a>
|
||||
| <a href='https://www.npmjs.com/package/cheap-glkote' target='_blank'>npm</a>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id="games">Games</h2>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
DASH DOT PIT
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Endless top-down shooter for TIC-80 where Morse code
|
||||
is your weapon.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/DotDashPit' target='_blank'>repository</a>
|
||||
| <a href='https://tic80.com/play?cart=4301' target='_blank'>play</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
Yet Another Experiment
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Tiny “escape the room” style TADS3 game.
|
||||
<br>Designed as a set of examples
|
||||
for Interactive Fiction development.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/yet-another-experiment' target='_blank'>repository</a>
|
||||
| <a href='https://ifdb.org/viewgame?id=rsssdo3anjpwnt6e' target='_blank'>ifdb</a>
|
||||
| <a href='https://he4et.github.io/elseifplayer/#/focus/https%3A%2F%2Fifarchive.org%2Fif-archive%2Fgames%2Ftads%2Fyet_another_experiment.t3/serika_dark/' target='_blank'>play</a>
|
||||
<section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id="services">Services</h2>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
Simple SpaceAPI
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
A lightweight SpaceAPI server with both web and REST interfaces.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/simple-spaceapi' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id="inventory">Inventory</h2>
|
||||
<h3 id="hardware">Hardware</h3>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
Cantor MX Tastatura
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Small custom ergonomic mechanical keyboard.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/cantor-mx-tastatura' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
oddkb
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Huge custom ergonomic mechanical Dactyl-Manuform (5×6) keyboard.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/oddkb' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h3 id="settings">Settings</h3>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
dotfiles
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Custom-made desktop environment based on i3wm.
|
||||
<br>It comes with settings for
|
||||
i3wm, Polybar, Rofi, dunst, NeoVim, and some other applications.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/dotfiles' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h3 id="software-plugins-and-tools">Software, Plugins and Tools</h3>
|
||||
<ul>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
desolate.nvim
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Another customizable not-so-colorful NeoVim colorscheme.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/desolate.nvim' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
desolate-gtk
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Another customizable not-so-colorful GTK theme for your tiling WM.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/Desolate-GTK' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
tabswitcher
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Switch between Firefox tabs and manage them with a fuzzy search
|
||||
and basic command toolset.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/tabswitcher' target='_blank'>repository</a>
|
||||
| <a href='https://addons.mozilla.org/en-GB/firefox/addon/tabswitcher/' target='_blank'>extension</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
tabswiper
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Firefox extension that makes trimming your tabs as effortless
|
||||
as swiping on a dating app.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/tabswiper' target='_blank'>repository</a>
|
||||
| <a href='https://addons.mozilla.org/en-GB/firefox/addon/tabswiper/' target='_blank'>extension</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
fuzzsoma
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
SomaFM CLI station selector.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/fuzzsoma' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
fuzzy-bitwarden-clipboard
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Interactive Bitwarden CLI wrapper.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/fuzzy-bitwarden-clipboard' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
huge-link
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Minimalistic serverless publishing tool for small markdown posts.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/huge-link' target='_blank'>repository</a>
|
||||
| <a href='https://he4et.github.io/huge-link/' target='_blank'>app</a>
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
<li><article class='entry'>
|
||||
<header>
|
||||
<strong>
|
||||
conky-dashboard
|
||||
</strong>
|
||||
</header>
|
||||
<section class='description'>
|
||||
Desktop widgets for system status monitoring.
|
||||
</section>
|
||||
<section>
|
||||
<a href='https://github.com/He4eT/conky-dashboard' target='_blank'>repository</a>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
25
docs/qr/index.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="description"
|
||||
content="Redirect to '/images/qr/oddsquat.org.png'.">
|
||||
<meta
|
||||
http-equiv="Refresh"
|
||||
content="0; URL=/images/qr/oddsquat.org.png">
|
||||
<title>
|
||||
Redirect | oddsquat
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
Redirect to
|
||||
<a
|
||||
style="color: inherit;"
|
||||
href="/images/qr/oddsquat.org.png">
|
||||
/images/qr/oddsquat.org.png
|
||||
</a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
1
docs/rss.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[oddsquat]]></title><description><![CDATA[A fanzine about experiments, code and other cyberpunk stuff.]]></description><link>https://oddsquat.org</link><image><url>https://oddsquat.org/icon.svg'</url><title>oddsquat</title><link>https://oddsquat.org</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 18 Mar 2026 14:48:21 +0100</lastBuildDate><atom:link href="https://oddsquat.org/rss.xml" rel="self" type="application/rss+xml"/><item><title><![CDATA[[RU] ugly keyboards]]></title><description><![CDATA[Почему нас окружают уродливые клавиатуры и что с этим можно сделать.]]></description><link>https://oddsquat.org/posts/2026/ugly_keyboards_ru/</link><guid isPermaLink="true">https://oddsquat.org/posts/2026/ugly_keyboards_ru/</guid><pubDate>Wed, 18 Mar 2026 00:00:00 +0100</pubDate></item><item><title><![CDATA[[RU] wrapped bw]]></title><description><![CDATA[Превращаем fully-featured Bitwarden command-line interface в удобный.]]></description><link>https://oddsquat.org/posts/2024/wrapped_bw_ru/</link><guid isPermaLink="true">https://oddsquat.org/posts/2024/wrapped_bw_ru/</guid><pubDate>Sat, 27 Jul 2024 00:00:00 +0200</pubDate></item><item><title><![CDATA[[RU] selfhosted LLM]]></title><description><![CDATA[Персональные LLM в docker-контейнере на твоём компьютере.]]></description><link>https://oddsquat.org/posts/2024/selfhosted_llm/</link><guid isPermaLink="true">https://oddsquat.org/posts/2024/selfhosted_llm/</guid><pubDate>Mon, 15 Jan 2024 00:00:00 +0100</pubDate></item><item><title><![CDATA[[RU] typographic linter]]></title><description><![CDATA[Prettier для текста. Автоматизация рутинной типографики.]]></description><link>https://oddsquat.org/posts/2020/typographic_linter/</link><guid isPermaLink="true">https://oddsquat.org/posts/2020/typographic_linter/</guid><pubDate>Thu, 18 Nov 2021 00:00:00 +0100</pubDate></item><item><title><![CDATA[[RU] initial post]]></title><description><![CDATA[Первый пост в этом фэнзине, рассказывающий о его внутреннем устойстве.]]></description><link>https://oddsquat.org/posts/2020/initial_post/</link><guid isPermaLink="true">https://oddsquat.org/posts/2020/initial_post/</guid><pubDate>Sun, 08 Nov 2020 00:00:00 +0100</pubDate></item></channel></rss>
|
||||
390
docs/test/index.html
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
|
||||
|
||||
<title>
|
||||
markdown test page | oddsquat
|
||||
</title>
|
||||
<meta name="description" content="A test document written using the Markdown language.">
|
||||
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-32.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans_condensed-27.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-25.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-24.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/fonts/open_sans-17.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/fonts.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/typography.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="stripesContainer">
|
||||
<div class="stripes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">oddsquat</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/">
|
||||
posts</a></li>
|
||||
|
||||
|
||||
<li><a href="/posts/#2020">
|
||||
2020</a></li>
|
||||
|
||||
|
||||
<li>markdown test page</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 id="markdown-syntax">Markdown: Syntax</h1>
|
||||
<p>Markdown is intended to be as easy-to-read and easy-to-write as is feasible.</p>
|
||||
<p>Readability, however, is emphasized above all else. A Markdown-formatted
|
||||
document should be publishable as-is, as plain text, without looking
|
||||
like it's been marked up with tags or formatting instructions. While
|
||||
Markdown's syntax has been influenced by several existing text-to-HTML
|
||||
filters — including <a href="http://docutils.sourceforge.net/mirror/setext.html">Setext</a>, <a href="http://www.aaronsw.com/2002/atx/">atx</a>, <a href="http://textism.com/tools/textile/">Textile</a>, <a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>,
|
||||
<a href="http://www.triptico.com/software/grutatxt.html">Grutatext</a>, and <a href="http://ettext.taint.org/doc/">EtText</a> — the single biggest source of
|
||||
inspiration for Markdown's syntax is the format of plain text email.</p>
|
||||
<p><strong>Note:</strong> This document is itself written using Markdown.</p>
|
||||
<hr>
|
||||
<h2 id="table-of-content">Table Of Content</h2>
|
||||
<ul>
|
||||
<li><a href="#block-elements">Block Elements</a><ul>
|
||||
<li><a href="#paragraphs-and-line-breaks">Paragraphs and Line Breaks</a></li>
|
||||
<li><a href="#headers">Headers</a></li>
|
||||
<li><a href="#blockquotes">Blockquotes</a></li>
|
||||
<li><a href="#lists">Lists</a></li>
|
||||
<li><a href="#code-blocks">Code Blocks</a></li>
|
||||
<li><a href="#tables">Tables</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#span-elements">Span Elements</a><ul>
|
||||
<li><a href="#links">Links</a></li>
|
||||
<li><a href="#emphasis">Emphasis</a></li>
|
||||
<li><a href="#code">Code</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id="block-elements">Block Elements</h2>
|
||||
<h3 id="paragraphs-and-line-breaks">Paragraphs and Line Breaks</h3>
|
||||
<p>A paragraph is simply one or more consecutive lines of text, separated
|
||||
by one or more blank lines. (A blank line is any line that looks like a
|
||||
blank line — a line containing nothing but spaces or tabs is considered
|
||||
blank.) Normal paragraphs should not be indented with spaces or tabs.</p>
|
||||
<p>The implication of the "one or more consecutive lines of text" rule is
|
||||
that Markdown supports "hard-wrapped" text paragraphs. This differs
|
||||
significantly from most other text-to-HTML formatters (including Movable
|
||||
Type's "Convert Line Breaks" option) which translate every line break
|
||||
character in a paragraph into a <code><br /></code> tag.</p>
|
||||
<p>When you <em>do</em> want to insert a <code><br /></code> break tag using Markdown, you
|
||||
end a line with two or more spaces, then type return.</p>
|
||||
<h3 id="headers">Headers</h3>
|
||||
<p>Headers are lines that start with the <code>#</code> symbol.
|
||||
The number of characters defines the header level,
|
||||
from one (<code>#</code>) for <code><h1></code> to six (<code>######</code>) for <code><h6></code>.</p>
|
||||
<h3 id="blockquotes">Blockquotes</h3>
|
||||
<p>Markdown uses email-style <code>></code> characters for blockquoting. If you're
|
||||
familiar with quoting passages of text in an email message, then you
|
||||
know how to create a blockquote in Markdown. It looks best if you hard
|
||||
wrap the text and put a <code>></code> before every line:</p>
|
||||
<blockquote>
|
||||
<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
|
||||
<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||
id sem consectetuer libero luctus adipiscing.</p>
|
||||
</blockquote>
|
||||
<p>Markdown allows you to be lazy and only put the <code>></code> before the first
|
||||
line of a hard-wrapped paragraph:</p>
|
||||
<blockquote>
|
||||
<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||
id sem consectetuer libero luctus adipiscing.</p>
|
||||
</blockquote>
|
||||
<p>Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
|
||||
adding additional levels of <code>></code>:</p>
|
||||
<blockquote>
|
||||
<p>This is the first level of quoting.</p>
|
||||
<blockquote>
|
||||
<p>This is nested blockquote.</p>
|
||||
</blockquote>
|
||||
<p>Back to the first level.</p>
|
||||
</blockquote>
|
||||
<p>Blockquotes can contain other Markdown elements, including headers, lists,
|
||||
and code blocks:</p>
|
||||
<blockquote>
|
||||
<h3 id="here-s-a-list">Here's a list</h3>
|
||||
<ol>
|
||||
<li>This is the first list item.</li>
|
||||
<li>This is the second list item.</li>
|
||||
</ol>
|
||||
<h3 id="here-s-an-example-code-block">Here's an example code block</h3>
|
||||
<pre><code>const PI = 3.1415`</code></pre></blockquote>
|
||||
<p>Any decent text editor should make email-style quoting easy. For
|
||||
example, with BBEdit, you can make a selection and choose Increase
|
||||
Quote Level from the Text menu.</p>
|
||||
<h3 id="lists">Lists</h3>
|
||||
<p>Markdown supports ordered (numbered) and unordered (bulleted) lists.</p>
|
||||
<p>Unordered lists use asterisks, pluses, and hyphens — interchangably
|
||||
— as list markers:</p>
|
||||
<ul>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ul>
|
||||
<p>is equivalent to:</p>
|
||||
<ul>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ul>
|
||||
<p>and:</p>
|
||||
<ul>
|
||||
<li>Red</li>
|
||||
<li>Green</li>
|
||||
<li>Blue</li>
|
||||
</ul>
|
||||
<p>Ordered lists use numbers followed by periods:</p>
|
||||
<ol>
|
||||
<li>Bird</li>
|
||||
<li>McHale</li>
|
||||
<li>Parish</li>
|
||||
</ol>
|
||||
<p>It's important to note that the actual numbers you use to mark the
|
||||
list have no effect on the HTML output Markdown produces. The HTML
|
||||
Markdown produces from the above list is:</p>
|
||||
<p>If you instead wrote the list in Markdown like this:</p>
|
||||
<ol>
|
||||
<li>Bird</li>
|
||||
<li>McHale</li>
|
||||
<li>Parish</li>
|
||||
</ol>
|
||||
<p>or even:</p>
|
||||
<ol start="3">
|
||||
<li>Bird</li>
|
||||
<li>McHale</li>
|
||||
<li>Parish</li>
|
||||
</ol>
|
||||
<p>you'd get the exact same HTML output. The point is, if you want to,
|
||||
you can use ordinal numbers in your ordered Markdown lists, so that
|
||||
the numbers in your source match the numbers in your published HTML.
|
||||
But if you want to be lazy, you don't have to.</p>
|
||||
<p>To make lists look nice, you can wrap items with hanging indents:</p>
|
||||
<ul>
|
||||
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||
viverra nec, fringilla in, laoreet vitae, risus.</li>
|
||||
<li>Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||
Suspendisse id sem consectetuer libero luctus adipiscing.</li>
|
||||
</ul>
|
||||
<p>But if you want to be lazy, you don't have to:</p>
|
||||
<ul>
|
||||
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||
viverra nec, fringilla in, laoreet vitae, risus.</li>
|
||||
<li>Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||
Suspendisse id sem consectetuer libero luctus adipiscing.</li>
|
||||
</ul>
|
||||
<p>List items may consist of multiple paragraphs. Each subsequent
|
||||
paragraph in a list item must be indented by either 4 spaces
|
||||
or one tab:</p>
|
||||
<ol>
|
||||
<li><p>This is a list item with two paragraphs. Lorem ipsum dolor
|
||||
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
|
||||
mi posuere lectus.</p>
|
||||
<p>Vestibulum enim wisi, viverra nec, fringilla in, laoreet
|
||||
vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
|
||||
sit amet velit.</p>
|
||||
</li>
|
||||
<li><p>Suspendisse id sem consectetuer libero luctus adipiscing.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<p>It looks nice if you indent every line of the subsequent
|
||||
paragraphs, but here again, Markdown will allow you to be
|
||||
lazy:</p>
|
||||
<ul>
|
||||
<li><p>This is a list item with two paragraphs.</p>
|
||||
<p>This is the second paragraph in the list item. You're
|
||||
only required to indent the first line. Lorem ipsum dolor
|
||||
sit amet, consectetuer adipiscing elit.</p>
|
||||
</li>
|
||||
<li><p>Another item in the same list.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>To put a blockquote within a list item, the blockquote's <code>></code>
|
||||
delimiters need to be indented:</p>
|
||||
<ul>
|
||||
<li><p>A list item with a blockquote:</p>
|
||||
<blockquote>
|
||||
<p>This is a blockquote
|
||||
inside a list item.</p>
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
<p>To put a code block within a list item, the code block needs
|
||||
to be indented with list item indentation in mind:</p>
|
||||
<ul>
|
||||
<li><p>A list item with a code block:</p>
|
||||
<pre><code>code goes here
|
||||
code goes here</code></pre></li>
|
||||
</ul>
|
||||
<h3 id="code-blocks">Code Blocks</h3>
|
||||
<p>Pre-formatted code blocks are used for writing about programming or
|
||||
markup source code. Rather than forming normal paragraphs, the lines
|
||||
of a code block are interpreted literally. Markdown wraps a code block
|
||||
in both <code><pre></code> and <code><code></code> tags.</p>
|
||||
<pre><code>tell application "Foo"
|
||||
beep
|
||||
end tell</code></pre><p>Regular Markdown syntax is not processed within code blocks. E.g.,
|
||||
asterisks are just literal asterisks within a code block. This means
|
||||
it's also easy to use Markdown to write about Markdown's own syntax.</p>
|
||||
<pre><code>## This is **not** a Markdown</code></pre><h3 id="tables">Tables</h3>
|
||||
<p>Markdown tables are created using pipes (|) to separate columns and hyphens (-) to define the header row. Here’s the basic structure:</p>
|
||||
<ol>
|
||||
<li>Header Row: The first row contains column names.</li>
|
||||
<li>Divider Line: The second row uses hyphens to separate the header from the data.</li>
|
||||
<li>Data Rows: The rows below the divider contain the actual data.</li>
|
||||
</ol>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Age</th>
|
||||
<th>City</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>Alice</td>
|
||||
<td>25</td>
|
||||
<td>New York</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bob</td>
|
||||
<td>30</td>
|
||||
<td>London</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>You can style text inside Markdown table cells just like regular Markdown.
|
||||
This includes making text bold, italic, monospaced, or adding links or code.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Column 1</th>
|
||||
<th>Column 2</th>
|
||||
<th>Column 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><strong>Things</strong></td>
|
||||
<td><em>Don't</em></td>
|
||||
<td><a href="/">Need</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>To</td>
|
||||
<td><em><strong>Look</strong></em></td>
|
||||
<td><code>Pretty</code></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>You can align text in Markdown table columns to the left, right, or center by placing a colon (:) in different positions within the header divider row.</p>
|
||||
<ol>
|
||||
<li>Left-aligned: <code>:---</code> (Colon on the left)</li>
|
||||
<li>Right-aligned: <code>---:</code> (Colon on the right)</li>
|
||||
<li>Center-aligned: <code>:---:</code> (Colons on both sides)</li>
|
||||
</ol>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Column 1</th>
|
||||
<th align="center">Column 2</th>
|
||||
<th align="right">Column 3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td align="left">Cell Contents</td>
|
||||
<td align="center">More Stuff</td>
|
||||
<td align="right">And Again</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left">You Can Also</td>
|
||||
<td align="center">Put Pipes In</td>
|
||||
<td align="right">Like this [|]</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Markdown tables don’t support merging cells (like in HTML).
|
||||
Each cell is treated separately.
|
||||
But if you want an empty space, just leave it blank.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Header 1</th>
|
||||
<th>Header 2</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>Cell 1</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cell 3</td>
|
||||
<td>Cell 4</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<h2 id="span-elements">Span Elements</h2>
|
||||
<h3 id="links">Links</h3>
|
||||
<p>To create an inline link, use a set of regular parentheses immediately
|
||||
after the link text's closing square bracket. Inside the parentheses,
|
||||
put the URL where you want the link to point, along with an <em>optional</em>
|
||||
title for the link, surrounded in quotes. For example:</p>
|
||||
<p>This is <a href="http://example.com/">an example</a> inline link.</p>
|
||||
<p><a href="/test/markdown/" title="This is an example title">This link</a> has a title attribute.</p>
|
||||
<h3 id="emphasis">Emphasis</h3>
|
||||
<p>Markdown treats asterisks (<code>*</code>) and underscores (<code>_</code>) as indicators of
|
||||
emphasis. Text wrapped with one <code>*</code> or <code>_</code> will be wrapped with an
|
||||
HTML <code><em></code> tag; double <code>*</code>'s or <code>_</code>'s will be wrapped with an HTML
|
||||
<code><strong></code> tag. E.g., this input:</p>
|
||||
<p><em>single asterisks</em></p>
|
||||
<p><em>single underscores</em></p>
|
||||
<p><strong>double asterisks</strong></p>
|
||||
<p><strong>double underscores</strong></p>
|
||||
<h3 id="code">Code</h3>
|
||||
<p>To indicate a span of code, wrap it with backtick quotes (<code>`</code>).
|
||||
Unlike a pre-formatted code block, a code span indicates code within a
|
||||
normal paragraph. For example:</p>
|
||||
<p>Use the <code>printf()</code> function.</p>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
2020-10-30
|
||||
</footer>
|
||||
|
||||
<script async
|
||||
data-goatcounter="https://he4et.goatcounter.com/count"
|
||||
src="https://gc.zgo.at/count.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||