diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..5b7f96f --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +oddsquat.org diff --git a/docs/about/index.html b/docs/about/index.html new file mode 100644 index 0000000..056f55b --- /dev/null +++ b/docs/about/index.html @@ -0,0 +1,125 @@ + + + + + + + + + + + about | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + Hello, + my name is He4eT. + +

+ It’s a Latin transliteration and + is read as “Nechet” [ˈnʲet͡ɕet].
+ The name comes from the Russian word “нечет”, + a short form of “нечётный”, + a mathematical term meaning “odd”. +

+
+
+ +

+ I’m a front-end developer and a big fan of open-source, + customization, and minimalist software.
+ Member of the + BadBar crew, + part of the + Decentrala + and Xecut communities. +

+ +

+ Oddsquat is my own private fanzine + about experiments, code, and other cyberpunk stuff.
+ RSS feed available. +

+ +
+ + + +

+ Public keys and fingerprints available. +

+ +
+
+ + + + + + + + diff --git a/docs/about/keys/index.html b/docs/about/keys/index.html new file mode 100644 index 0000000..36c8142 --- /dev/null +++ b/docs/about/keys/index.html @@ -0,0 +1,96 @@ + + + + + + + + + + + keys | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Public Keys and Fingerprints

+

SSH

+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOeN9/Pil2rhvhu+65PlMw5850AK55hfEd4zFyj00v0Q black
+

OMEMO

+ + +

+ + He4eT@pod.oddsquat.org + +

+ +
81da50ce-e0c98a31-10602a9f-1e82df25-d52f7d04-c69fa612-a8b4635e-a6472705
+f359aa0d-77f36772-bc836b53-a0eaf03c-0c978b72-3a8d48f7-d1e680f2-6782df3f
+9ba77efb-5959f878-57e4bb82-1a82368f-8e9bdd57-4c147003-511de5d0-8b790130
+237f6bc5-bf773e3f-b833dd5b-8f97513b-6a0cbed7-da3146dc-17786300-0cf92600
+

Fingerprints for He4eT@pod.oddsquat.org

+

+ + He4eT@dmz.rs + +

+ +
e540567f-bfaf223f-e755ed8a-f70fc558-48283338-4ff00262-0b2ab9c2-7774f510
+7562243c-00acffbf-081e6724-9c3328d2-73367e5d-07d2efac-bd717069-65a3e63c
+61b7bda6-e09d7b61-42e6dd81-f80ad677-64788fc3-d3914290-ac078d2e-98f2a975
+

Fingerprints for He4eT@dmz.rs

+ +
+
+ + + + + + + + diff --git a/docs/css/fonts.css b/docs/css/fonts.css new file mode 100644 index 0000000..156b14b --- /dev/null +++ b/docs/css/fonts.css @@ -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; +} diff --git a/docs/css/index.css b/docs/css/index.css new file mode 100644 index 0000000..30faa4b --- /dev/null +++ b/docs/css/index.css @@ -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: ''; +} diff --git a/docs/css/main.css b/docs/css/main.css new file mode 100644 index 0000000..3f1bebf --- /dev/null +++ b/docs/css/main.css @@ -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); +} diff --git a/docs/css/typography.css b/docs/css/typography.css new file mode 100644 index 0000000..c97f0e6 --- /dev/null +++ b/docs/css/typography.css @@ -0,0 +1 @@ +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}html{font:112.5%/1.5 'Open Sans',sans-serif;box-sizing:border-box;overflow-y:scroll;}*{box-sizing:inherit;}*:before{box-sizing:inherit;}*:after{box-sizing:inherit;}body{color:hsl(0, 0%, 0%, 0.8);font-family:'Open Sans',sans-serif;font-weight:normal;word-wrap:break-word;font-kerning:normal;-moz-font-feature-settings:"kern", "liga", "clig", "calt";-ms-font-feature-settings:"kern", "liga", "clig", "calt";-webkit-font-feature-settings:"kern", "liga", "clig", "calt";font-feature-settings:"kern", "liga", "clig", "calt";}img{max-width:100%;margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}h1{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;color:hsl(0, 0%, 0%, 0.7);font-family:'Open Sans Condensed',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:3rem;line-height:1.1;}h2{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;color:hsl(0, 0%, 0%, 0.7);font-family:'Open Sans Condensed',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:1.93318rem;line-height:1.1;}h3{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;color:hsl(0, 0%, 0%, 0.7);font-family:'Open Sans Condensed',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:1.55185rem;line-height:1.1;}h4{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;color:hsl(0, 0%, 0%, 0.7);font-family:'Open Sans Condensed',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:1rem;line-height:1.1;}h5{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;color:hsl(0, 0%, 0%, 0.7);font-family:'Open Sans Condensed',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:0.80274rem;line-height:1.1;}h6{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;color:hsl(0, 0%, 0%, 0.7);font-family:'Open Sans Condensed',sans-serif;font-weight:bold;text-rendering:optimizeLegibility;font-size:0.71922rem;line-height:1.1;}hgroup{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}ul{margin-left:1.5rem;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;list-style-position:outside;list-style-image:none;}ol{margin-left:1.5rem;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;list-style-position:outside;list-style-image:none;}dl{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}dd{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}p{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}figure{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}pre{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;font-size:0.85rem;line-height:1.5rem;}table{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;font-size:1rem;line-height:1.5rem;border-collapse:collapse;width:100%;}fieldset{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}blockquote{margin-left:1.5rem;margin-right:1.5rem;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}form{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}noscript{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}iframe{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}hr{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:calc(1.5rem - 1px);background:hsla(0,0%,0%,0.2);border:none;height:1px;}address{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.5rem;}b{font-weight:bold;}strong{font-weight:bold;}dt{font-weight:bold;}th{font-weight:bold;}li{margin-bottom:calc(1.5rem / 2);}ol li{padding-left:0;}ul li{padding-left:0;}li > ol{margin-left:1.5rem;margin-bottom:calc(1.5rem / 2);margin-top:calc(1.5rem / 2);}li > ul{margin-left:1.5rem;margin-bottom:calc(1.5rem / 2);margin-top:calc(1.5rem / 2);}blockquote *:last-child{margin-bottom:0;}li *:last-child{margin-bottom:0;}p *:last-child{margin-bottom:0;}li > p{margin-bottom:calc(1.5rem / 2);}code{font-size:0.85rem;line-height:1.5rem;}kbd{font-size:0.85rem;line-height:1.5rem;}samp{font-size:0.85rem;line-height:1.5rem;}abbr{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;}acronym{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;}abbr[title]{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;text-decoration:none;}thead{text-align:left;}td,th{text-align:left;border-bottom:1px solid hsla(0,0%,0%,0.12);font-feature-settings:"tnum";-moz-font-feature-settings:"tnum";-ms-font-feature-settings:"tnum";-webkit-font-feature-settings:"tnum";padding-left:1rem;padding-right:1rem;padding-top:0.75rem;padding-bottom:calc(0.75rem - 1px);}th:first-child,td:first-child{padding-left:0;}th:last-child,td:last-child{padding-right:0;} \ No newline at end of file diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000..e6e1950 Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/fonts/fira_code-0.woff2 b/docs/fonts/fira_code-0.woff2 new file mode 100644 index 0000000..b8a2908 Binary files /dev/null and b/docs/fonts/fira_code-0.woff2 differ diff --git a/docs/fonts/fira_code-1.woff2 b/docs/fonts/fira_code-1.woff2 new file mode 100644 index 0000000..8bd4abb Binary files /dev/null and b/docs/fonts/fira_code-1.woff2 differ diff --git a/docs/fonts/fira_code-2.woff2 b/docs/fonts/fira_code-2.woff2 new file mode 100644 index 0000000..06b6f7a Binary files /dev/null and b/docs/fonts/fira_code-2.woff2 differ diff --git a/docs/fonts/fira_code-3.woff2 b/docs/fonts/fira_code-3.woff2 new file mode 100644 index 0000000..eb5b302 Binary files /dev/null and b/docs/fonts/fira_code-3.woff2 differ diff --git a/docs/fonts/fira_code-4.woff2 b/docs/fonts/fira_code-4.woff2 new file mode 100644 index 0000000..0d2103f Binary files /dev/null and b/docs/fonts/fira_code-4.woff2 differ diff --git a/docs/fonts/fira_code-5.woff2 b/docs/fonts/fira_code-5.woff2 new file mode 100644 index 0000000..00c8383 Binary files /dev/null and b/docs/fonts/fira_code-5.woff2 differ diff --git a/docs/fonts/open_sans-10.woff2 b/docs/fonts/open_sans-10.woff2 new file mode 100644 index 0000000..7404f02 Binary files /dev/null and b/docs/fonts/open_sans-10.woff2 differ diff --git a/docs/fonts/open_sans-11.woff2 b/docs/fonts/open_sans-11.woff2 new file mode 100644 index 0000000..f53b8df Binary files /dev/null and b/docs/fonts/open_sans-11.woff2 differ diff --git a/docs/fonts/open_sans-12.woff2 b/docs/fonts/open_sans-12.woff2 new file mode 100644 index 0000000..c787ad8 Binary files /dev/null and b/docs/fonts/open_sans-12.woff2 differ diff --git a/docs/fonts/open_sans-13.woff2 b/docs/fonts/open_sans-13.woff2 new file mode 100644 index 0000000..f762e91 Binary files /dev/null and b/docs/fonts/open_sans-13.woff2 differ diff --git a/docs/fonts/open_sans-14.woff2 b/docs/fonts/open_sans-14.woff2 new file mode 100644 index 0000000..f3b2c4d Binary files /dev/null and b/docs/fonts/open_sans-14.woff2 differ diff --git a/docs/fonts/open_sans-15.woff2 b/docs/fonts/open_sans-15.woff2 new file mode 100644 index 0000000..8e05a7f Binary files /dev/null and b/docs/fonts/open_sans-15.woff2 differ diff --git a/docs/fonts/open_sans-16.woff2 b/docs/fonts/open_sans-16.woff2 new file mode 100644 index 0000000..b852126 Binary files /dev/null and b/docs/fonts/open_sans-16.woff2 differ diff --git a/docs/fonts/open_sans-17.woff2 b/docs/fonts/open_sans-17.woff2 new file mode 100644 index 0000000..f482ce1 Binary files /dev/null and b/docs/fonts/open_sans-17.woff2 differ diff --git a/docs/fonts/open_sans-18.woff2 b/docs/fonts/open_sans-18.woff2 new file mode 100644 index 0000000..612ff5d Binary files /dev/null and b/docs/fonts/open_sans-18.woff2 differ diff --git a/docs/fonts/open_sans-19.woff2 b/docs/fonts/open_sans-19.woff2 new file mode 100644 index 0000000..b7bc862 Binary files /dev/null and b/docs/fonts/open_sans-19.woff2 differ diff --git a/docs/fonts/open_sans-20.woff2 b/docs/fonts/open_sans-20.woff2 new file mode 100644 index 0000000..e1ce55f Binary files /dev/null and b/docs/fonts/open_sans-20.woff2 differ diff --git a/docs/fonts/open_sans-21.woff2 b/docs/fonts/open_sans-21.woff2 new file mode 100644 index 0000000..35bac5d Binary files /dev/null and b/docs/fonts/open_sans-21.woff2 differ diff --git a/docs/fonts/open_sans-22.woff2 b/docs/fonts/open_sans-22.woff2 new file mode 100644 index 0000000..18862e8 Binary files /dev/null and b/docs/fonts/open_sans-22.woff2 differ diff --git a/docs/fonts/open_sans-23.woff2 b/docs/fonts/open_sans-23.woff2 new file mode 100644 index 0000000..9c609c8 Binary files /dev/null and b/docs/fonts/open_sans-23.woff2 differ diff --git a/docs/fonts/open_sans-24.woff2 b/docs/fonts/open_sans-24.woff2 new file mode 100644 index 0000000..7cd1174 Binary files /dev/null and b/docs/fonts/open_sans-24.woff2 differ diff --git a/docs/fonts/open_sans-25.woff2 b/docs/fonts/open_sans-25.woff2 new file mode 100644 index 0000000..0beab54 Binary files /dev/null and b/docs/fonts/open_sans-25.woff2 differ diff --git a/docs/fonts/open_sans-6.woff2 b/docs/fonts/open_sans-6.woff2 new file mode 100644 index 0000000..87f0364 Binary files /dev/null and b/docs/fonts/open_sans-6.woff2 differ diff --git a/docs/fonts/open_sans-7.woff2 b/docs/fonts/open_sans-7.woff2 new file mode 100644 index 0000000..387470b Binary files /dev/null and b/docs/fonts/open_sans-7.woff2 differ diff --git a/docs/fonts/open_sans-8.woff2 b/docs/fonts/open_sans-8.woff2 new file mode 100644 index 0000000..3f5ef09 Binary files /dev/null and b/docs/fonts/open_sans-8.woff2 differ diff --git a/docs/fonts/open_sans-9.woff2 b/docs/fonts/open_sans-9.woff2 new file mode 100644 index 0000000..7d385f3 Binary files /dev/null and b/docs/fonts/open_sans-9.woff2 differ diff --git a/docs/fonts/open_sans_condensed-26.woff2 b/docs/fonts/open_sans_condensed-26.woff2 new file mode 100644 index 0000000..1dc5834 Binary files /dev/null and b/docs/fonts/open_sans_condensed-26.woff2 differ diff --git a/docs/fonts/open_sans_condensed-27.woff2 b/docs/fonts/open_sans_condensed-27.woff2 new file mode 100644 index 0000000..42eeffd Binary files /dev/null and b/docs/fonts/open_sans_condensed-27.woff2 differ diff --git a/docs/fonts/open_sans_condensed-28.woff2 b/docs/fonts/open_sans_condensed-28.woff2 new file mode 100644 index 0000000..2dc444d Binary files /dev/null and b/docs/fonts/open_sans_condensed-28.woff2 differ diff --git a/docs/fonts/open_sans_condensed-29.woff2 b/docs/fonts/open_sans_condensed-29.woff2 new file mode 100644 index 0000000..5e40ee7 Binary files /dev/null and b/docs/fonts/open_sans_condensed-29.woff2 differ diff --git a/docs/fonts/open_sans_condensed-30.woff2 b/docs/fonts/open_sans_condensed-30.woff2 new file mode 100644 index 0000000..74fe16e Binary files /dev/null and b/docs/fonts/open_sans_condensed-30.woff2 differ diff --git a/docs/fonts/open_sans_condensed-31.woff2 b/docs/fonts/open_sans_condensed-31.woff2 new file mode 100644 index 0000000..5bd7909 Binary files /dev/null and b/docs/fonts/open_sans_condensed-31.woff2 differ diff --git a/docs/fonts/open_sans_condensed-32.woff2 b/docs/fonts/open_sans_condensed-32.woff2 new file mode 100644 index 0000000..dbfb8c0 Binary files /dev/null and b/docs/fonts/open_sans_condensed-32.woff2 differ diff --git a/docs/icon.svg b/docs/icon.svg new file mode 100644 index 0000000..a97c096 --- /dev/null +++ b/docs/icon.svg @@ -0,0 +1,7 @@ + + + + diff --git a/docs/images/keys/He4eT.dmz.png b/docs/images/keys/He4eT.dmz.png new file mode 100644 index 0000000..adeb889 Binary files /dev/null and b/docs/images/keys/He4eT.dmz.png differ diff --git a/docs/images/keys/He4eT.pod.png b/docs/images/keys/He4eT.pod.png new file mode 100644 index 0000000..c8916a9 Binary files /dev/null and b/docs/images/keys/He4eT.pod.png differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_angel.jpg b/docs/images/posts/ugly_keyboards/keyboard_angel.jpg new file mode 100644 index 0000000..66e0a35 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_angel.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_cantor-mx.jpg b/docs/images/posts/ugly_keyboards/keyboard_cantor-mx.jpg new file mode 100644 index 0000000..5be2d61 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_cantor-mx.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_dactyl-manuform.jpg b/docs/images/posts/ugly_keyboards/keyboard_dactyl-manuform.jpg new file mode 100644 index 0000000..b04a1a6 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_dactyl-manuform.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_default.svg b/docs/images/posts/ugly_keyboards/keyboard_default.svg new file mode 100644 index 0000000..2139ae6 --- /dev/null +++ b/docs/images/posts/ugly_keyboards/keyboard_default.svg @@ -0,0 +1,1990 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/posts/ugly_keyboards/keyboard_ergodox-ez.jpg b/docs/images/posts/ugly_keyboards/keyboard_ergodox-ez.jpg new file mode 100644 index 0000000..01ab1f9 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_ergodox-ez.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_glove80.jpg b/docs/images/posts/ugly_keyboards/keyboard_glove80.jpg new file mode 100644 index 0000000..710bf95 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_glove80.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_helix.jpg b/docs/images/posts/ugly_keyboards/keyboard_helix.jpg new file mode 100644 index 0000000..59d4193 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_helix.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_katana60.jpg b/docs/images/posts/ugly_keyboards/keyboard_katana60.jpg new file mode 100644 index 0000000..65cd4ab Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_katana60.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_keychron-q15.jpg b/docs/images/posts/ugly_keyboards/keyboard_keychron-q15.jpg new file mode 100644 index 0000000..a207311 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_keychron-q15.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_kinesis-advantage2.jpg b/docs/images/posts/ugly_keyboards/keyboard_kinesis-advantage2.jpg new file mode 100644 index 0000000..008635c Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_kinesis-advantage2.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_kinesis-freestyle-2.jpg b/docs/images/posts/ugly_keyboards/keyboard_kinesis-freestyle-2.jpg new file mode 100644 index 0000000..027514b Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_kinesis-freestyle-2.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_microsoft-natural-keyboard-elite.jpg b/docs/images/posts/ugly_keyboards/keyboard_microsoft-natural-keyboard-elite.jpg new file mode 100644 index 0000000..9fbfe5d Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_microsoft-natural-keyboard-elite.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_sofle.jpg b/docs/images/posts/ugly_keyboards/keyboard_sofle.jpg new file mode 100644 index 0000000..fa9180a Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_sofle.jpg differ diff --git a/docs/images/posts/ugly_keyboards/keyboard_svalboard.jpg b/docs/images/posts/ugly_keyboards/keyboard_svalboard.jpg new file mode 100644 index 0000000..eba542c Binary files /dev/null and b/docs/images/posts/ugly_keyboards/keyboard_svalboard.jpg differ diff --git a/docs/images/posts/ugly_keyboards/typewriter.jpg b/docs/images/posts/ugly_keyboards/typewriter.jpg new file mode 100644 index 0000000..309a8da Binary files /dev/null and b/docs/images/posts/ugly_keyboards/typewriter.jpg differ diff --git a/docs/images/posts/ugly_keyboards/typewriter_ergo.jpg b/docs/images/posts/ugly_keyboards/typewriter_ergo.jpg new file mode 100644 index 0000000..612dd75 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/typewriter_ergo.jpg differ diff --git a/docs/images/posts/ugly_keyboards/typing_zones_default.png b/docs/images/posts/ugly_keyboards/typing_zones_default.png new file mode 100644 index 0000000..d0b5512 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/typing_zones_default.png differ diff --git a/docs/images/posts/ugly_keyboards/typing_zones_default_hands.png b/docs/images/posts/ugly_keyboards/typing_zones_default_hands.png new file mode 100644 index 0000000..97309f0 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/typing_zones_default_hands.png differ diff --git a/docs/images/posts/ugly_keyboards/typing_zones_ergo_hands.png b/docs/images/posts/ugly_keyboards/typing_zones_ergo_hands.png new file mode 100644 index 0000000..e75e7d2 Binary files /dev/null and b/docs/images/posts/ugly_keyboards/typing_zones_ergo_hands.png differ diff --git a/docs/images/qr/oddsquat.org.png b/docs/images/qr/oddsquat.org.png new file mode 100644 index 0000000..d407bfd Binary files /dev/null and b/docs/images/qr/oddsquat.org.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..4ed9a45 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + oddsquat + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ + + + + diff --git a/docs/lost+found/index.html b/docs/lost+found/index.html new file mode 100644 index 0000000..ddf724a --- /dev/null +++ b/docs/lost+found/index.html @@ -0,0 +1,590 @@ + + + + + + + + + + + lost+found | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

lost+found

+

Unsorted pile of links I sometimes urgently need +in the middle of a conversation.

+
+

Some file systems contain a special directory, +called lost+found under Unix, where a file system check +places lost and potentially corrupted files when the correct location +cannot be determined, +and so requires manual intervention by the user.

+
+
+ + +
+
+ + + + + + + + diff --git a/docs/posts/2020/index.html b/docs/posts/2020/index.html new file mode 100644 index 0000000..dfb7efd --- /dev/null +++ b/docs/posts/2020/index.html @@ -0,0 +1,25 @@ + + + + + + + + Redirect | oddsquat + + + +
+ Redirect to + + /posts/#2020 + +
+ + diff --git a/docs/posts/2020/initial_post/index.html b/docs/posts/2020/initial_post/index.html new file mode 100644 index 0000000..a5d2445 --- /dev/null +++ b/docs/posts/2020/initial_post/index.html @@ -0,0 +1,123 @@ + + + + + + + + + + + initial post | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Initial Post

+

Oddsquat — мой маленький личный фэнзин про эксперименты, код и прочий киберпанк. Обычно такие сайты называются блогами, но в слове «фэнзин» есть приятный отголосок панк-культуры и её неотъемлемой DIY-составляющей.

+

Основные причины, побудившие меня на создание своего уютного куска интернета:

+ +

В первом посте хочется рассказать, какие инструменты были использованы для создания этого сайта, какие решения были приняты и почему.

+

Название

+

Я вырос в девяностые, поэтому квикимобиль, бэтпещера и пузиблинчики для меня не пустой звук, а образец системы нейминга. Вот, например, у меня есть oddkb.

+

Свою квартиру с момента заселения я называю oddsquat (даже локация в Foursquare была), так пусть теперь и мой форпост в сети называется так же.

+

Назвал сайт в честь SSID домашнего Wi-Fi, потому что не смог придумать более достойного названия :)

+

Платформа

+

Для статического сайта не нужен серверный код, поэтому WordPress и прочие CMS в качестве платформы я даже не рассматривал.

+

Про генераторы статических сайтов раньше слышал, но подробно не изучал. Их оказалось так много (тут есть длинный список), что даже страшно.

+

Посмотрев на самые популярные решения, я понял, что как-то неправильно понимаю значение термина «генератор статических сайтов», потому что все хором предлагают мне сгенерировать Progressive Web Application из React-компонентов с импортом данных из GraphQL-сервера вместо россыпи HTML-документов.

+

Уверен, что уютный бложик — это точно не Web Application, а от Progressive в данном случае всем будет только хуже: искусственные трудности с роутингом, серверным рендерингом и сохранением позиции скрола успешно решены, когда каждая страница — это отдельный документ. Браузеры восхитительно умеют работать с HTML-документами!

+

После непродолжительных поисков был найден Nanogen, который делает именно то, что нужно, и не похож на космический корабль:

+ +

Да, в репозитории проекта давно не было коммитов, но хочется думать, что это по причине надёжности и закончености :)

+

Типографика

+

Я люблю текст и не умею в дизайн. Oddsquat — проект про буквы, значит, буквы должны быть красивыми и удобными.

+

К теме типографики в вебе я подходил несколько раз, но безуспешно. Не получается структурировать и сохранить в голове все эти сакральные знания про кернинг и засечки, потому что не вижу никакой связи между теорией и практикой.

+

Очередное погружение системных знаний опять не принесло, зато принесло удобный инструмент: Typography.js.

+

Этот тулкит позволяет, покрутив ручки интерактивного демо, получить новое, но при этом гармоничное сочетание абзацев, заголовков и отступов между ними. +Идеальный инструмент для тех, кто хочет поиграть со шрифтами, но не до конца понимает, как.

+

Авторы настолько заботливые, что даже подключают normalize.css при генерации своих стилей, снимая с вас ещё одну головную боль. Благодаря им весь мой дополнительный css для стилизации сайта уложился в сто строк кода и хорошо работает в любых браузерах и на любых экранах.

+

Со шрифтами всё просто:

+ +

Аналитика

+

Я долго думал, можно ли собирать аналитику просмотров, или нужно играть в идеальный веб до конца и полностью отказаться от слежки за пользователями. С одной стороны высокая идея, с другой — любопытство.

+

После непродолжительного исследования пришёл к выводу, что Метрика и Аналитика от поисковых гигантов мне точно не подходят, а вот автор GoatCounter полностью понимает мою боль.

+

Why I made GoatCounter — отличный пост, где создатель сервиса ответил на все вопросы, которые у меня к нему возникли. Вот, например, замечательный абзац про причины бесплатности начального тарифа:

+
+

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.

+
+

Содержание

+

Самое главное. С формой и техническими вопросами всё ясно, но что же я буду складывать внутрь? Пока план такой:

+ +

Признаюсь, что был очарован сайтом от ребят из команды Hundred Rabbits и захотел такое же, только своё.

+

Stay tuned!

+ +
+
+ + + + + + + + diff --git a/docs/posts/2020/typographic_linter/index.html b/docs/posts/2020/typographic_linter/index.html new file mode 100644 index 0000000..8848ac3 --- /dev/null +++ b/docs/posts/2020/typographic_linter/index.html @@ -0,0 +1,109 @@ + + + + + + + + + + + typographic linter | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Типографика как code style

+

Основы

+

Для того, чтобы тексты было удобно читать и неудобно писать, человечество придумало разные правила. С орфографией и пунктуацией всё понятно, но про типографические правила говорят редко.

+

Типографика — набор правил вёрстки текста, призванных повысить эстетику и читаемость. Именно в таком порядке, потому что люди сначала замечают текст и только потом начинают читать.

+

Про шрифты, отступы и прочие кернинги в этот раз говорить не будем, потому что мне понравилось крутить ползунки в Typography.js и не переживать об эстетике.

+

Разговор пойдёт про висячие предлоги и прочие тире с кавычками.

+

Проблематика

+

Сайты в интернете делятся на два типа: те, кто запаривается за подготовку текстов, и те, кто забивает. Meduza.io расставляет неразрывные пробелы в текстах новостей, а rbc.ru — нет. Это не единственное отличие этих новостных порталов, но очень знаковое, как мне кажется.

+

Нужно забить!

+

Я прекрасно понимаю тех, кто не запаривается. Часть не знает, часть забивает (Вастрик, например), а у остальных user-generated content, который довольно рискованно автоматически форматировать. Аргументы для каждой группы:

+ +

Нужно запариться!

+

Википедия утверждает, что типографика — это «свод строгих правил», но список не показывает и ссылок не даёт. Стандартом де-факто в рунете являются правила из § 62 «Ководства». С ними или соглашаются, или громко спорят с некоторыми.

+

Пусть мне и не нравится полное отсутствие у Лебедева обоснований или ссылок на источники, но самому погружаться в историю вопроса и читать какой-нибудь авторитетный «Справочник издателя и автора» страшно: в нём 1010 страниц.

+

Авторы, которые хотят радовать читателей с помощью тире, обычно не пишут в блокноте (мне так кажется) или носят свои тексты из блокнота в Типограф от Артемия Лебедева, а потом назад. Про первых я ничего не знаю, а ко вторым сам отношусь, когда нужно причесать один или два текста.

+

У Типографа и аналогичных веб-сервисов есть фундаментальные проблемы:

+ +

Признаюсь, что раньше я ни разу не думал об использовании типографа вне браузера, но как только образовалась перспектива заниматься этим на постоянной основе, стало понятно, что копировать текст из редактора в браузер и назад — не самое оптимальное решение и нужно что-то с этим делать.

+

Решение

+

Очевидный вариант — научить текстовый редактор автоматически расставлять неразрывные пробелы, исправлять мелкие опечатки, приводить кавычки к правильному виду, заменять дефисы на тире в нужных местах и многое другое.
+Вот плагин для VS Code.

+

Мне больше нравится другой подход. Мы уже давно используем линтеры для языков программирования, давайте использовать их и для контента.

+

Конечно, я не первый, кто так подумал. Плагин выше построен на основе замечательной библиотеки typograf и прямо в README.md файле этого проекта можно найти ссылки на плагины для Babel, Gulp и Grunt.

+

Библиотека очень гибко настраивается, содержит сотню готовых правил и позволяет легко добавлять кастомные.

+

В комплекте есть cli-утилита, которая умеет читать файлы, форматировать их и выводить результат в терминал, но почему-то не умеет файл перезаписывать. Поэтому для своих целей я написал небольшой скрипт, который, получив локаль и имя файла, изменяет его содержимое. С правилами тоже пришлось немного поиграть, потому что дефолтные принимают списки за диалоги и ломают markdown-разметку.

+

Теперь для расстановки всех хитрых значков не нужно ходить в браузер, достаточно просто запустить скрипт, посмотреть с помощью git diff на результат и закоммитить нужные изменения.

+

Красивые тексты лучше некрасивых.

+ +
+
+ + + + + + + + diff --git a/docs/posts/2024/index.html b/docs/posts/2024/index.html new file mode 100644 index 0000000..e3e2fda --- /dev/null +++ b/docs/posts/2024/index.html @@ -0,0 +1,25 @@ + + + + + + + + Redirect | oddsquat + + + +
+ Redirect to + + /posts/#2024 + +
+ + diff --git a/docs/posts/2024/selfhosted_llm/index.html b/docs/posts/2024/selfhosted_llm/index.html new file mode 100644 index 0000000..71cafc6 --- /dev/null +++ b/docs/posts/2024/selfhosted_llm/index.html @@ -0,0 +1,317 @@ + + + + + + + + + + + selfhosted LLM | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Your Own Private Large Language Models

+

С одной стороны, я сомневаюсь, что Большие Языковые Модели (LLM) смогут однажды эволюционировать в AGI. С другой — я по-настоящему впечатлён тем, что щепотка статистики справляется с написанием текстов лучше меня.

+

В любом случае, джинна обратно в бутылку уже не вернуть, и все те письма в различные организации, которые я не хочу писать сам, будут теперь написаны месивом из байтиков.

+

Действительно пугает меня в этой ситуации только то, что флагманом новой эры почему-то стала компания OpenAI, которая, вопреки названию, совершенно не Open. +Множество компаний и людей вписали их продукты в свою рутину и не страшатся такой неподконтрольной зависимости.

+

Я так не могу. К счастью, я не один такой, и на данный момент уже есть множество альтернативных моделей от разных вендоров. Они отличаются друг от друга качеством, размером, возможностями и лицензиями, так что при желании можно надолго занять себя знакомством с обширным ассортиментом. Например, на портале HuggingFace, который можно описать как «GitHub для LLM и всего, что вокруг».

+

Должен признаться, что очень слабо разбираюсь в параметрах и характеристиках языковых моделей, но оказалось, что для того, чтобы начать, эти знания не так уж и необходимы.

+

Ниже инструкция, как запустить LLM на своём железе, как упаковать всё это в docker-контейнер, чтобы не размазать случайно по всей файловой системе, как получить совместимый с OpenAI API и как потом этим пользоваться.

+
+ +
+

+ Установка и настройка +

+ +

Существует несколько продуктов, которые стараются избавить пользователя от головной боли и возни с инфраструктурой. Мне понятнее всего оказался проект Ollama, с ним мы и будем экспериментировать.

+

Кроме бинарников для Linux и MacOS, они предоставляют официальный docker-образ, работу с которым я и опишу.

+

Использование docker-контейнеров, к сожалению, слегка усложняет взаимодействие с Ollama, так что большая часть текста и кода в этом посте посвящены решению проблем, которые, по сути, я придумал себе сам.

+

+ Установка Ollama +

+ +

Для создания и первого запуска контейнера нужно выполнить команду:

+
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

Счастливые владельцы видеокарт от Nvidia могут установить Nvidia container toolkit и активировать поддержку GPU с помощью флага --gpus=all.

+

После создания запускать и останавливать контейнер ollama можно так:

+
docker start ollama
+docker stop ollama

Контейнер предоставляет доступ к Ollama API на 11434 порту, а также позволяет устанавливать и общаться с установленными LLM через терминал.

+

+ Загрузка модели и диалог с ней +

+ +

Ollama позволяет запускать любые GGUF, PyTorch или Safetensors модели (что бы это ни значило), но самый простой путь — загрузка моделей из специальной библиотеки.

+

Для того, чтобы скачать модель и начать с ней диалог, нужно выполнить команду:

+
docker exec -it ollama ollama run mistral

Вместо mistral от одноимённой комании можно выбрать любую другую модель из библиотеки, например, легкую phi от Microsoft Research.

+

Кроме run доступны также list, pull и rm для просмотра списка, скачивания и удаления моделей соответственно.

+

Чтобы не писать такие длинные заклинания каждый раз, я добавил в .zshrc пару алиасов:

+
alias summonable='docker exec -it ollama ollama list'
+alias summon='clear && docker exec -it ollama ollama run'

Теперь можно смотреть на список установленных моделей и запускать диалог с выбранной:

+
summonable
+summon phi

+ Кастомные модели и их тонкая настройка +

+ +

Ollama позволяет на основе существующих создавать производные модели с заранее определёнными инструкциями или параметрами. Для этого нужно создать специальный файл, в котором указана родительская модель и определены желаемые значения параметров. Подробнее о формате этих файлов можно прочесть в документации: Modelfile.

+
+

В какой-то момент Ollama Web UI превратился в Open WebUI, а OllamaHub прекратил существовать. Все ссылки в следующем абзаце больше не представляют какой-либо ценности.

+
+

Чтобы посмотреть, как должен выглядеть Modelfile, можно посетить OllamaHub от разработчиков стороннего Ollama Web UI. На сайте есть примеры очень тонкой настройки множества параметров модели для соответствия образу конкретного персонажа, но в качестве образца я буду использовать небольшой English Teacher Modelfile:

+

EnglishTeacher.Modelfile

+
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.
+"""

Вообще, для загрузки кастомной модели достаточно выполнить команду create, но в случае использования Ollama внутри docker-контейнера возникает необходимость каким-то образом файл с моделью в этот контейнер передать.

+

Для решения этой проблемы я набросал небольшой bash-скрипт:

+

applyModelfile.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
+

Этот скрипт нужно поместить по соседству с директорией models и сделать исполняемым с помощью chmod +x applyModelfile.bash. +Должна получиться примерно такая структура:

+
├── models
+│   └── EnglishTeacher.Modefile
+└── applyModelfile.bash

После этого модель можно загрузить в контейнер и начать с ней диалог:

+
./applyModelfile.bash EnglishTeacher
+summon EnglishTeacher

+ Использование +

+ +

Разговоры с галлюцинирующим искусственным интеллектом в терминале — это, конечно, волшебно, но потенциал больших языковых моделeй по-настоящему раскрывается, когда они начинают портить данные в соседних приложениях!

+

В GitHub-репозитории Ollama можно найти ссылки на множество веб-интерфейсов, библиотек и плагинов для текстовых редакторов и прочих Obsidian'ов.

+

Не ручаюсь за весь список, но расскажу про то, с чем экспериментировал сам.

+

+ Мимикрия под API от OpenAI +

+ +
+

В какой-то момент в Ollama появилась поддержка совместимости с форматом API от OpenAI и этот раздел потерял актуальность.

+
+

API Ollama используется в меньшем числе продуктов, чем API от OpenAI. К счастью, это не проблема: с помощью прокси-прослойки под названием LiteLLM можно сделать их совместимыми. Инструкция по установке и использованию в общем случае есть в репозитории и довольно тривиальна, но мне опять потребовалось немного кода, чтобы заставить их работать вместе на моих условиях.

+

Я хотел, чтобы LiteLLM-прокси и Ollama работали на разных компьтерах, и не хотел ставить pip-пакеты в систему. В результате родилось решение из docker-файла с хаками и скрипта, который в нём запускается. Я не специалист в написании docker-файлов, так что уверен в неоптимальности финального решения. Точно можно и нужно обойтись без run --net=host и отдельного скрипта, например.

+

Несмотря на костыльность связки, она справляется со своей задачей:

+

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"]
+

startProxy.sh

+
#!/bin/bash
+
+pip install litellm
+
+litellm --model ollama/mistral --api_base http://ollama.internal:11434 --drop_params
+

Собрать и запустить docker-контейнер можно с помощью этих двух команд:

+
docker build -t diy-ollama-proxy .
+docker run --net=host diy-ollama-proxy
+

После запуска вы получите API, который совместим с API от OpenAI и доступен по адресу http://localhost:8000/.

+

+ Интеграция с NeoVim +

+ +

Языковые модели отлично умеют взаимодействовать с текстом, так что использование их в текстовом редакторе кажется разумной идеей.

+

Мне не очень нравится идея Copilot, который зачем-то постоянно подсовывает тебе странные куски кода. Я пробовал использовать Codeium в ручном режиме, но оказалось, что странные куски кода по запросу мне тоже не очень нужны. Гораздо более привлекательной мне кажется возможность выделить существующий фрагмент текста или кода и попросить бездушную машину что-нибудь с ним сделать: упростить, дополнить, изменить или даже перевести с одного языка на другой. Идеальным для такого подхода оказался плагин ollama.nvim.

+

Кроме того, что он поддерживает кастомные промпты (в том числе интерактивные), он позволил мне обращаться к LLM, которая запущена на другом компьютере в локальной сети (для удобства я указал его адрес в /etc/hosts/).

+

Установка и настройка с использованием пакетного менеджера lazy.nvim выглядит примерно так:

+
{
+  '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',
+      },
+    },
+  },
+},

В этом конфиге я выключил несколько дефолтных промптов и добавил несколько своих:

+ +

В итоге получается два сценария использования, оба доступны по <leader> + j:

+ +

Как видно из конфига, я использую только mistral, но можно указать модель для кажого промпта и делегировать, например, манипуляции над кодом codellama, а операции над текстом — llama2.

+

Возможность добавления кастомных промптов позволяет в будущем реализовать новые сценарии или вынести повторяющиеся действия в отдельный пункт меню или даже на отдельный шорткат.

+

+ Обновление и удаление +

+ +

Для обновления и удаления моделей можно использовать команды pull и rm:

+
docker exec -it ollama ollama pull mixtral
+docker exec -it ollama ollama rm mistral

Я знаю, что для обновления и удаления docker-образов и docker-контейнеров тоже есть специальные команды (это тоже pull и rm), но каждый раз ленюсь в этом разобраться, просто сношу всё с помощью утилиты sen и разворачиваю нужное заново.

+

+ Производительность +

+ +

Для эксплуатации LLM требуется гораздо меньше ресурсов, чем для её обучения. Запустить 7b-модель средней тупости можно практически на любом CPU и 8 GB RAM, но нагрузка на систему и скорость генерации ответов часто будут далеки от комфортных значений.

+

Например, на моём немолодом Intel Core i7-10510U @ 8x 4.9GHz неаккуратный запрос к llama2 может заставить систему шуршать вентиляторами пару-тройку минут. При этом phi на этом же процессоре способна отвечать на какие-нибудь не очень сложные вопросы практически мгновенно.

+

К счастью, у меня случайно завалялся MacBook на процессоре M1 и он уже показывает куда более впечатляющие результаты. Mistral даже на непростые запросы отвечает за считанные секунды, а в режиме чата токены вылетают на экран заметно быстрее, чем в веб-интерфейсе ChatGPT.

+

Неприятным открытием стало то, что docker-версия Ollama на MacOS выполняется заметно медленнее (от 3 до 5 раз, если верить ощущениям), чем нативная. Возможно, всё дело в том, что я как-то неправильно настроил docker или приложение в контейнере нужно запускать с какими-нибудь специальными флагами для максимальной утилизации ресурсов. В любом случае, к порядку на этом ноутбуке я отношусь гораздо менее трепетно, поэтому просто установил и использую приложение с сайта Ollama.

+

+ Зачем всё это нужно? +

+ +

Конечно, ChatGPT умнее и умеет из коробки гораздо больше.
+Конечно, ChatGPT требует меньше телодвижений для использования.
+Конечно, самые умные модели требуют внушительных ресурсов, ведь для запуска нашумевшей mixtral или аналогичной модели нужно иметь 48 Gb оперативной памяти.
+Конечно, сидя в кафе задать вопрос Bard от Google гораздо проще, чем достучаться до модели в закрытом ноутбуке, который остался дома.

+

Я всё это прекрасно понимаю, но ничего из этого не стоит того, чтобы добровольно ставить себя в зависимость от монополистов с их закрытыми чёрными ящиками.

+

Даже если закрыть глаза на все идеологические вопросы, то любая локальная LLM отличается от любого облачного провайдера тем, что:

+ +

Я искренне рад, что для доступа даже к передовым технологиям, всё ещё не обязательно поступаться своей приватностью и своими свободами.

+
+

Этот пост написан без использования LLM =)

+ +
+
+ + + + + + + + diff --git a/docs/posts/2024/wrapped_bw_ru/index.html b/docs/posts/2024/wrapped_bw_ru/index.html new file mode 100644 index 0000000..08e76a1 --- /dev/null +++ b/docs/posts/2024/wrapped_bw_ru/index.html @@ -0,0 +1,152 @@ + + + + + + + + + + + wrapped bw | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Интеграция Bitwarden CLI с fzf и буфером обмена

+

Менеджер паролей — это специальное приложение, которое помогает делать вид, что я помню разные пароли для разных аккаунтов, а не ввожу везде один и тот же. Мне нравится Bitwarden: открытый исходный код, возможность поднять собственный сервер, клиенты под разные устройства и расширения под разные браузеры.

+

Самым удобным, внезапно, оказался клиент для Android, который не заставляет меня каждый раз вводить 12+ знаков мастер-пароля (такую длину требуют Bitwarden и здравый смысл), а может быть разблокирован с помощью биометрии. Похожего удобства захотелось достичь и на Linux.

+

Дикий CLI

+

На ноутбуке я использую Bitwarden CLI. Это powerful, fully-featured tool, которым на практике оказалось не слишком удобно пользоваться, если ты человек.

+

Ключи от ключей

+
+

You are responsible for maintaining your session key.

+
+

Bitwarden CLI поддерживает механизм сессий, который призван избавить пользователя от бесконечного ввода мастер-пароля. Приложение позволяет разблокировать хранилище и получить временный сессионный ключ, который можно либо хранить в беззащитной переменной окружения, либо прикладывать к каждому запросу вручную.

+

По сути своей, сессионный ключ отличается от мастер-пароля тем, что его можно моментально деактивировать, но совершенно невозможно запомнить, а, значит, нужно где-то хранить. Хочется делать это удобно и безопасно, а не в общедоступной переменной окружения.

+

Пароли в stdout

+
+

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.

+
+

Где-то в этот момент чтения документации я окончательно начал подозревать, что официальный CLI предназначен для скриптов: всё строго, никакого автодополнения, никакого интерактивного поиска, а пароли лаконично вываливаются в стандартный вывод терминала, откуда их ещё нужно как-то переправить в место назначения.

+

Приручение CLI

+

Может показаться, что я ругаюсь, но отсутствие удобств и излишеств в официальном CLI — это хорошо:

+ +

Идея сделать Bitwarden CLI удобнее, разумеется, пришла в голову не только мне, так что на GitHub предсказуемо быстро нашёлся скрипт-обёртка от @loeschzwerg. Этот ZSH-скрипт менее требователен к пользователю и позволяет в случае, когда под пользовательский поисковый запрос подходит несколько аккаунтов, выбрать нужный из списка с помощью fzf и автоматически скопировать логин, пароль и даже TOTP в буфер обмена.

+

К сожалению, найденный скрипт никак не решал проблему управления сессиями, так что я решил его немного доработать, избавив заодно от избытка многоточий в интерфейсе.

+

«Безопасное» хранение сессионного ключа

+

Как я писал выше, мне нравится подход Android-клиента: нужно один раз ввести свой невероятно длинный мастер-пароль, после чего можно разблокировать хранилище отпечатком пальца. В ходе непродолжительных размышлений я решил, что самое простое и надёжное подобие для приложения в терминале — один раз получить сессионный ключ и сохранить его в файл, который будет доступен для чтения только пользователю root и недоступен любым другим приложениям, запущенным от имени текущего пользователя.

+

Приятный бонус для владельцев биометрических сканеров: они отлично интегрируются с утилитой sudo.

+

В результате скрипт обогатился двумя функциями и одной проверкой:

+
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"
+}
+
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

При первом запуске сессионный ключ, полученный после ввода мастер-пароля, записывается в файл, который после выполнения команды chmod 600 становится недоступен для чтения никому, кроме суперпользователя:

+
~ » ls -lah
+...
+-rw-------. 1 root root   89 Jul 24 22:15 .bitwarden_session
+...
+
+~ » less .bitwarden_session
+.bitwarden_session: Permission denied

Парольный менеджер и скрипт-обёртка запускаются от имени текущего пользователя, повышение привилегий требуется только в момент записи и чтения сессионного ключа.

+

Деактивировать сохранённый ключ можно с помощью команды bw lock. +К сожалению, я так и не понял, как с помощью утилиты bw можно проверить, валиден ли ключ, так что после деактивации придётся удалить файл ~/.bitwarden_session вручную, иначе скрипт так и будет подставлять протухший сохранённый ключ, а bw будет каждый раз игнорировать его и настойчиво спрашивать мастер-пароль.

+

Update [2026-03-29]: +Нормального способа проверить валидность сессионного ключа всё ещё нет, +но я научил утилиту удалять файл с протухшим ключом по косвенным признакам.

+

Применять с осторожностью

+

Взаимодействие с менеджером паролей выглядит для меня теперь примерно так:

+
~ » 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.

Финальный вариант скрипта можно найти в репозитории He4eT/fuzzy-bitwarden-clipboard.

+

Настоятельно рекомендую читать любой код перед тем, как запускать его. Особенно в тех случаях, когда речь идёт о настолько чувствительных данных.

+

Важно! На системах без шифрования диска все эти танцы с правами на доступ к файлу не несут никакой пользы и превращают затею в увлекательный цирк.

+

Нельзя исключать, что я что-то совершенно неправильно понимаю в принципах работы системы прав доступа в Linux и совершил какие-нибудь грубейшие, с точки зрения настоящих специалистов по информационной безопасности, ошибки. Пожалуйста, сообщите, если я где-то неправ.

+

Нужно помнить, что такое упрощение жизни ведёт к новым рискам: теперь любой, кто знает ваш пароль для учётной записи системного пользователя и имеет доступ к компьютеру, будет также иметь доступ и ко всем паролям, сохранённым в Bitwarden.

+

Пользуйтесь с осторожностью и/или храните свои пароли в надёжных местах =)

+ +
+
+ + + + + + + + diff --git a/docs/posts/2026/encrypted_XMPP/index.html b/docs/posts/2026/encrypted_XMPP/index.html new file mode 100644 index 0000000..dfa0355 --- /dev/null +++ b/docs/posts/2026/encrypted_XMPP/index.html @@ -0,0 +1,428 @@ + + + + + + + + + + + encrypted XMPP | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

End-to-End Encryption in XMPP with OMEMO

+

I find it funny that twenty years ago I was already trying +to get people to switch to XMPP.

+

For a long time, ICQ was extremely popular around me, +but the proprietary messenger kept breaking things for people +using alternative clients, which was quite annoying. +After yet another round of this pointless battle +I realized clearly that I prefer protocols over services.

+

I didn’t have much success back then, +but fortunately, XMPP (and I hope I have too) +has continued moving forward over the past two decades. +It has developed slowly, sometimes awkwardly, but steadily.

+

Here, I won’t talk about why XMPP is great or how it works. +You can check + + this guide +(one of many) and I’d rather not write another one. +In this post, +I want to focus specifically on end-to-end encryption +and the practical aspects of using it.

+

Short Glossary

+

End-to-end encryption is a way +to keep your chats truly private.
+Only you and the person you’re messaging can read the messages. +Not even the server owner has the keys +needed to decrypt or modify them.

+

XMPP is an extensible protocol for instant messaging. +It’s open, decentralized, and mature.

+

OMEMO is a widely supported +XMPP Extension Protocol (XEP) +for secure multi-client end-to-end encryption. +You can read more about +it on a dedicated page by Daniel Gultsch.

+

Client, in this post, +means a specific instance +of an XMPP application on a user device. +
OMEMO-related documentation uses the term Device, +but I find it potentially confusing: +in practice, a single physical device +can run multiple independent clients.

+

Basic Concepts

+

This section introduces some basics of end-to-end encryption.

+

If you’re already familiar with the concepts and terminology, +you can skip ahead to how end-to-end encryption + affects the XMPP user experience, +or jump straight to the step-by-step workflow I personally use.

+

Trade-offs Between Safety and Convenience

+

Unfortunately, things that are truly secure are rarely convenient. +They often require some initial efforts +and a bit of ongoing attention.

+

Telegram, which used to be a benchmark for messenger usability +before its long dive into enshitification, +really draws the line between convenience and security. +Regular chats are easy and flexible, +but “secret” chats come with a full set of limitations: +they’re one-on-one only, +can’t be synced to another device, +aren’t available on desktop at all, +and so on.

+

All commercial so-called “secure” messengers, like Signal or WhatsApp, +end up with pretty similar limitations, +because it’s tricky to make end-to-end encrypted chats +work the way users expect.

+

Luckily, protocols and cryptography don’t care about +convenience or user expectations. +Many XMPP clients let you do almost anything you’re trying to do. +Sometimes it’s clunky and unintuitive, +sometimes it’s the kind of freedom +that lets you shoot yourself in the foot. +At the end of the day, you’d better understand what you’re doing.

+

It might sound messy, but for that price, XMPP actually +gives you a lot of handy features: +your chats are secured with Signal-grade end-to-end encryption, +and you can use as many devices as you want, +all at the same time, +without being tied to any proprietary service.

+

In general, the XMPP experience today +could be described as a “WhatsApp with benefits and frictions”. +It’s kinda ironic, considering that WhatsApp’s protocol +is actually based on XMPP, but incompatibly altered and defederated.

+

Keys, Fingerprints and Trust

+

OMEMO is based on the + Double Ratchet Algorithm. +While the internal details are quite interesting, +for practical purposes it’s enough to know that +each client stores some cryptographic keys +and can derive a human-readable hash from them, +commonly called a fingerprint.

+

Keys are usually managed automatically by the XMPP client, +and in normal use you should never need to handle them manually. +In fact, you probably don’t even need to know what they look like.

+

A fingerprint lets you identify +a specific client of your contact +and verify that it hasn’t been spoofed. +Fingerprints for an account are not secret: +clients publish their own fingerprints to the XMPP server +and automatically receive the fingerprints of others. +Only fingerprints you explicitly mark as trusted are relevant.

+

In an typical scenario, the contact should confirm in person +or through an already trusted and secure communication channel +that the fingerprint belongs to their device, +and only then you mark it as trusted.

+

The list of trusted fingerprints +is used at the moment a message is sent. +Behind the scenes, +OMEMO performs a certain amount of key management, +and only the clients that are present in the trusted list +at the time of encryption +will be able to decrypt the message later.

+

It’s important to understand +that trust cannot be applied retroactively: +it’s not possible to “extend” trust to new clients +after a message has already been encrypted and sent.

+

+ Practical Aspects of OMEMO and XMPP +

+ +

Chat History

+

In theory, XMPP supports server-side message history storage via +XEP-0313: Message Archive Management.

+

In practice, support for this XEP, +as well as retention policies and message lifetime, +depends on the specific server. +You should never assume that all conversations are stored +indefinitely by default. +From a practical standpoint, +the server-side MAM archive is better considered a cache: +it can help you handle recent messages after a short period offline +or synchronize conversations across multiple devices.

+

At the end of the day, +keeping your chat history is your responsibility, +and this is a good place to apply a local-first approach.

+

Synchronisation

+

Seamless switching between clients is handled by +XEP-0280: Message Carbons. +Before its introduction, only incoming messages were synced between devices, +while your own outgoing messages were not. +Protocol-level mirroring of your own messages +is a rather non-obvious feature :D

+

It’s important to note that with end-to-end encryption, +the concept of trusted fingerprints also applies to your own clients. +For seamless synchronisation of outgoing messages, +all your clients must trust each other’s fingerprints. +A new client, +or an old one that was not trusted +at the time messages were sent, +will receive the full history from MAM +but will not be able to decrypt it. +
Yes, even your own messages.

+

In theory, re-encrypting messages on already trusted clients +could solve this issue, but no XMPP client implements it yet. +So in practice you may need to manually resend +some data to a new device.

+

Message Correction

+

It’s worth keeping in mind that +features that seem simple and straightforward at first glance, +such as message editing and deletion, +actually rely on client-side implementation +and may not behave for your recipient the way you expect.

+

They’re fine to use and are well supported in some clients, +but you shouldn’t rely on them to hide anything.

+

Maintenance

+

OMEMO was designed as a set-it-and-forget-it solution +and mostly succeeds in that goal. +If you have a basic understanding of how the protocol works +and check in online from time to time, +there shouldn’t be any surprises.

+

All maintenance comes down to making regular backups +and notifying your contacts +when fingerprints are added or no longer valid +so they can keep their trust list up to date.

+

Step-by-Step Guide

+

Let’s say I have a XMPP account, me@some.server, +and a few devices: +a phone, a laptop, and a desktop computer. +First I’ll describe my mindset at a high level, +then I’ll add some notes about specific clients.

+

Client Roles

+

On the one hand, I have my phone. +It’s almost always with me and almost always online. +That’s where I keep the full chat history +and get real-time notifications.

+

On the other hand, I have a couple of desktop applications. +I only open them +when I need to discuss something using my keyboard +or share some text between devices. +I like to think of them as satellite clients.

+

Before the Start

+

First, enable OMEMO encryption +on every client if it isn’t enabled by default.

+

The next step is to add +all clients to the trust list on each device: +my phone should trust all my computers, +and my computers should trust each other +as well as my phone.

+

Fingerprints do not have to be secret, +so they can be published on +your website or even on social media profiles. +Here is my page with the fingerprints, for example: +
+ https://oddsquat.org/about/keys/ +

+

Start the Conversation in Person

+

Let’s say I meet Alice, +we start talking, +and then decide to continue the conversation online.

+

I open a special QR code on my phone, +and Alice scans it with her client. +This QR code already contains +the fingerprints of all my devices, +so no extra steps are needed on her phone. +After that, I do the same +and scan her QR code as well.

+

Later at home, +I manually mark her devices as trusted on my computers +using the trusted list on my phone, and she does the same.

+

Now we are both sure +that it is really us in the conversation, +and that all messages will be available +on all our devices and only on them.

+

Start the Conversation Online

+

Let’s say Bob and I start discussing something +on a forum or in the Fediverse, +and then decide to continue the discussion on XMPP.

+

Before starting the chat, +Bob can confirm it’s really me using my page with fingerprints. +I can confirm it’s really him +by asking him to send his fingerprints +in a private message on the same forum or via email.

+

Ideally, Bob also has a public page with his fingerprints. +That way, we can both independently verify +that we are who we say we are.

+

In an alternative scenario, +where there has been no prior communication or public pages +and only a single JID is known, +things play out a bit differently: +Bob starts the chat, +I trust the first device he messages me from, +and then we exchange fingerprints for our other devices, +if we have any. +This approach is called TOFU (Trust On First Use).

+

New or Lost Devices

+

If I start using a new device +or install another client application, +the first thing I do is add it to the list +of trusted clients on my existing devices.

+

If I lose one of my devices +or delete any private keys, +the first thing I do is remove the corresponding client +from the trusted list on my other devices.

+

Once I’ve updated all my personal lists, +I should inform my contacts about changes via trusted channels.

+

I can simply ask Alice to scan +my new QR code the next time we meet, +and send Bob a message introducing +my new client or letting him know +that the lost device is no longer trusted +and that no real messages will ever come from it again.

+

Client Applications

+

This section describes +how OMEMO is used in specific client applications +that I personally use.

+

Conversations and Forks

+

+ Conversations is a modern, +fully featured chat application for Android. +It supports everything a messaging app should support: +chats, voice calls, video calls, and sharing files of any kind.

+

There are several forks of it where +the UI or UX may differ, +but the core features work exactly the same. +I personally use + Monocles Chat.

+

On the Contact Details screen (including your own account), +you can see a list of published fingerprints +and manually mark them as trusted or revoke trust.

+

To simplify all these routine operations, +a QR-code-based system is used: +you can show your own QR code or scan other people’s codes +directly from the main screen. +This makes device verification during in-person meetings +simple and effortless.

+

Dino

+

+ Dino is a lightweight GTK-based GUI client.

+

It can be considered a fully functional one, +although some non-essential features are still not implemented. +For example, +it is not possible to clear local chat history +using built-in methods :D

+

Trust and untrust decisions can be easily managed +in the Encryption tab of the Conversation Details window.

+

It is important to note that, +by default, Dino is configured +to automatically trust new fingerprints. +I recommend disabling this feature.

+

Profanity

+

+ Profanity is a powerful TUI client +where everything is controlled through a built-in command system.

+

If you somehow intend to use it, +you can find a small cheat sheet for the omemo command below. +However, I strongly recommend reading the full documentation.

+ +

Late Disclaimer

+

This post was originally intended +as a collection of answers to questions +I had when I first started using XMPP with OMEMO.

+

It isn’t meant to be exhaustive or formal, +but rather to clarify the practical side of things +and reduce that initial feeling of being lost +when you keep running into +“The message was not encrypted for this device” +over and over again.

+

From now on, I hope you won’t encounter such errors +or any other issues with end-to-end encryption, +and you’ll feel confident using it in XMPP.

+ +
+
+ + + + + + + + diff --git a/docs/posts/2026/index.html b/docs/posts/2026/index.html new file mode 100644 index 0000000..91624c7 --- /dev/null +++ b/docs/posts/2026/index.html @@ -0,0 +1,25 @@ + + + + + + + + Redirect | oddsquat + + + +
+ Redirect to + + /posts/#2026 + +
+ + diff --git a/docs/posts/2026/ugly_keyboards_ru/index.html b/docs/posts/2026/ugly_keyboards_ru/index.html new file mode 100644 index 0000000..c9090b5 --- /dev/null +++ b/docs/posts/2026/ugly_keyboards_ru/index.html @@ -0,0 +1,1326 @@ + + + + + + + + + + + ugly keyboards | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Уродливые клавиатуры

+

Этот пост был неизбежен. Я годами +хожу с горящим взором конспиролога и +пытаюсь каждому встречному открыть глаза на то, +что клавиатуры вокруг нас — не что иное, +как затянувшийся розыгрыш, +который давно уже вышел из‑под контроля. +Пришло время собрать все мои вопли, +наблюдения и аргументы в связный и логичный текст.

+

В этом посте не будет ничего +про отличие механических клавиатур от мембранных, +не будет расследования, +какие же переключатели лучше (тактильные и жёсткие, конечно), +не будет сравнений популярных брендов, +а также не будет ни слова про логические раскладки.

+

Критикой QWERTY и дешёвых Genius пусть занимается кто‑нибудь другой, +мы будем говорить только про +физическое расположение клавиш в пространстве.

+
+

Чтение поста займёт примерно 25–30 минут.

+

За это время я попробую рассказать, +чем же так плохи клавиатуры вокруг нас, +как мы очутились в окружении таких клавиатур +и как их можно сделать лучше.

+

В конце покажу хорошие клавиатуры, +расскажу, где их искать, +поделюсь личным опытом, +попробую превентивно поработать с возражениями, +сошлюсь на чужой опыт +и подведу некоторый итог.

+

Текст написан без использования LLM.

+
+

+ Классическая раскладка +

+ +

Вообще, большинство клавиатур так или иначе следует стандартам: + + чаще ANSI, реже ISO.

+

Клавиатуры могут быть разных цветов и размеров, +производители могут переносить +PageUp / PageDown в самые неожиданные места +и экспериментировать с формой +или взаимным расположением стрелочек, +а пользователи могут спорить, +допустимо ли располагать Fn в углу +и разумно ли заменять верхний ряд клавиш тач‑панелью.

+

При всём этом разнообразии центральная и основная часть +(та, где расположены буквы и цифры) +уже более ста лет остаётся неизменной.

+

Классическая ANSI клавиатура

+

Все клавиатуры, похожие на эту, я дальше буду называть +классическими.

+
+

Наследие печатных машинок

+

То, что современные классические клавиатуры унаследовали +раскладку механических печатных машинок +от Remington, — довольно известный, +но недооценённый факт.

+

Перед создателями этого прообраза современных клавиатур +стояли нетривиальные ограничения, +которые им пришлось грациозно обходить нетривиальными способами. +Самые заметные артефакты прошлого всё ещё можно +обнаружить практически на любом устройстве, +и они почему‑то до сих пор кажутся людям чем‑то необходимым.

+

Многие уверены, что без +длинного пробела и характерного горизонтального сдвига рядов +клавиатура вообще не может существовать: +раз уж производители продолжают добавлять +эти конструктивные особенности во все новые устройства, +значит, это что‑то очень важное и зачем‑то всем нам очень нужно.

+

Большим пальцам — большой пробел

+

Сам по себе длинный пробел не кажется чем‑то неправильным. +Раньше он служил рукам подставкой для отдыха, +сейчас по нему сложно промахнуться.

+

Мне, если честно, не до конца понятно, +чем сегодня он принципиально отличается от латинской буквы E, +которая встречается в англоязычных текстах + + примерно так же часто, +и почему в короткий пробел вдруг станет как‑то сложнее попадать, +но это всё на самом деле не очень важно.

+

Настоящая «Проблема большого пальца» с большими пальцами +напрямую вообще не связана +и заключается не в длине пробела, а в том, +что во времена расцвета печатных машинок +ещё не существовало многих клавиш, +без которых клавиатуру сейчас представить совершенно невозможно: +не было ни стрелочек, +ни Esc, ни Ctrl, ни Alt, +ни Win, ни Super, ни Cmd. +Даже Backspace на эту вечеринку опоздал.

+

Процесс постепенного появления новых кнопок +можно охарактеризовать как эволюционный: +клавиши добавляли не туда, +где ими было бы удобно пользоваться, +а туда, где нашлось свободное место. +Чаще всего это свободное место обнаруживалось где‑нибудь сбоку.

+

Если вам вдруг зачем‑то нужно регулярно писать код, +то ситуация становится ещё неприятнее: +почти все скобки и математические операции тоже +располагаются где‑то на краю клавиатуры.

+

Получается, что через сотню лет после изобретения первых клавиатур +мы обнаруживаем себя в ситуации, +когда в зоне ответственности двух маленьких и слабых +мизинцев находится больше двадцати клавиш, +а большие пальцы всё ещё призваны +нажимать вдвоём на один пробел.

+
+ Зоны слепой десятипальцевой печати для классической клавиатуры. +
+ Зоны для десятипальцевого метода печати. Источник: + keybr.com. +
+
+ +

На всякий случай напомню, +что противопоставленный большой палец — это вообще‑то +могучее и ловкое эволюционное достижение, +которое классические клавиатуры просто игнорируют:

+ +

Кстати, для мизинцев, травмированных частым использованием модификаторов, +есть даже специальный термин: + + Emacs Pinky. +Можно ознакомиться со + + списком разработчиков разной степени именитости, + которые с ним сталкивались, +и подсмотреть, +какими методами они с этим недугом боролись.

+

Горизонтальный сдвиг рядов

+

+ Самые первые печатные машинки +напоминали скорее рояль: +большие, шумят, один ряд клавиш. +Довольно быстро люди поняли, +что это слишком широко и не слишком‑то удобно.

+

Идея расположить символы на нескольких рядах витала в воздухе, +но упиралась в конструктивные ограничения: +рычаги, которые ударяют по бумаге, +должны быть расположены в один ряд +и не должны друг с другом пересекаться. +Это значит, что нельзя просто расположить кнопки друг над другом, +у каждой клавиши печатной машинки +должна быть своя уникальная X‑координата.

+
+ Фотография клавиш и рычагов механической печатной машинки. +
+ Рычаги классической печатной машинки. Источник: + + Public Domain Pictures. +
+
+ +

Одним из возможных решений этой проблемы +как раз и стал горизонтальный сдвиг рядов. +На фотографии видно, +что A расположена почти ровно под Q, +а Z почти ровно под A. +Именно в этом «почти» и скрывается магия: +небольшое смещение позволяет избежать пересечения рычагов, +уложить цифры с буквами в четыре ряда +и сделать клавиатуру компактной.

+

С цифровым рядом, кстати, произошло вообще что‑то нелепое: +он идёт первым, а сдвинут как третий. +Мне нравится думать, что это сделали не для экономии пространства, +а для того, чтобы специально всех запутать :D

+

С одной стороны, нельзя не признать красоты инженерного решения: +более 40 клавиш расположили в пространстве так, +чтобы не нужно было тянуться за ними в соседнюю комнату, +и сохранили при этом простоту механизма.

+

Зоны слепой десятипальцевой печати для классической клавиатуры

+

С другой стороны, нельзя не отметить, что, +как и многие другие инженерные решения, +сдвиг рядов имеет свою цену: +люди с симметричными руками вынуждены пользоваться +клавиатурами для двух правых рук, +а споры, каким пальцем нужно нажимать на клавишу B +(та, которая между V и N), не утихают до сих пор.

+

С третьей стороны, хочется напомнить читателю, +что в современных клавиатурах +никаких непересекающихся рычагов вообще‑то нет. +Пересекающихся, кстати, тоже.

+

У меня нет ссылок на какие‑то исследования, +но есть уверенность, +что низкое распространение навыка слепой печати — прямое следствие +существования горизонтального сдвига рядов. +В классической клавиатуре не видно никакой системы, +а при обучении (даже с правильной техникой постановки пальцев) +только для букв с цифрами +нужно запомнить почти 40 уникальных движений. +При этом только в домашнем, среднем, +ряду клавиши расположены симметрично +для левой и правой рук.

+

Простой отказ от горизонтального сдвига +снижает количество асимметричных движений до нуля: +симметричные руки наконец‑то могут двигаться симметрично! +В подарок мы получаем сокращение расстояний между клавишами, +а также предсказуемые, +очевидные и понятные зоны для слепой печати.

+

Зоны слепой десятипальцевой печати для ортолинейной клавиатуры +Зоны слепой десятипальцевой печати для классической клавиатуры

+

Более подробно слепую печать я хочу обсудить в отдельном посте, +а в этом разделе просто озвучу свою главную претензию: +горизонтальный сдвиг рядов — это компромиссное решение проблемы, +которой у нас больше нет.

+

Это одно из тех инженерных решений, +для которых мы можем сегодня ответить на вопрос «Почему всё такое?», +но не можем ответить на вопрос «Зачем нам всё это?». +Человечество поддерживает статус‑кво, +терпит какие‑то неудобства и обрекает на них будущие поколения, +но даже не может объяснить ради чего.

+

Ситуация настолько нелепая, +что я не могу даже привести какой‑нибудь другой +пример из окружающей действительности, +который будет настолько же абсурден. +Хоть как‑то передать бессмысленность ситуации +поможет доисторический анекдот:

+
+

— Скажи, мама, почему ты всегда отрезаешь кончики у сосисок перед тем, как их поджарить?
+— Я не знаю, — ответила мама, — так делала моя мама, твоя бабушка. Я училась готовить у неё и делаю всё так, как она. Такая в нашей семье традиция.

+

Они отправились к бабушке. Бабушка сидела в кресле, укутавшись в плед, и спала. Пришлось её разбудить.

+

— Бабушка, почему ты всегда отрезала кончики у сосисок?
+— Не знаю, — ответила бабушка, — моя мама всегда так делала. Пока не поздно, надо узнать у неё!

+

Прабабушка лежала на смертном одре и готовилась отойти в мир иной.

+

— Скажи, почему мы отрезаем кончики у сосисок перед жаркой?
+— А вы что, до сих пор готовите на той маленькой сковородке?

+
+

Запястья, плечи и другие части тела

+

В этом разделе можно было бы поговорить о том, +что классические клавиатуры +игнорируют существование человеческих плеч, +мешают анатомически верной постановке рук и, поэтому, +вроде как, являются фактором риска +развития синдрома запястного канала +(более известного как карпальный туннельный синдром) +и других нарушений работы опорно‑двигательного аппарата.

+

На + + PubMed, +которым я не умею пользоваться, +как обычно, есть множество статей по теме: +некоторые из них подтверждают опасность классических клавиатур, +другие же, наоборот, утверждают, что риски переоценены.

+

Я не имею достаточной медицинской экспертизы, +чтобы в этом разбираться, +поэтому сошлюсь на + + подборку + страшилок с сайта Kinesis. +Там показывают картинки и предлагают опасаться +локтевого отклонения или избыточного разгибания кисти, +загадочной пронации предплечья +и чрезмерного выноса руки к мыши.

+
+

+ Заложники печатных машинок +

+ +

Продолжительное время навык быстрой и безошибочной печати +относился к hard skills. +Набрать страницу текста без единой опечатки — это вообще‑то достижение, +а заниматься таким изо дня в день — не только пытка, +но и настоящая профессия.

+

В отсутствие привычной нам клавиши Backspace +армии машинисток тратили +многие часы и гектары бумаги на специальных курсах +и становились заложниками той самой классической раскладки. +Когда нет права на ошибку, +учиться в процессе работы правильному методу десятипальцевой печати +или неторопливо переучиваться на новую раскладку просто невозможно: +ты либо умеешь набирать текст быстро и без ошибок, либо нет.

+

Именно поэтому в ходе постепенной замены +механических устройств на электрические стало очевидно: +менять раскладку нельзя. +Чисто технически производители печатных машинок нового типа +могли перепридумать конструкцию заново +уже без учёта пропавших ограничений, +но экономически это стало бы самоубийством:

+ +

Появившиеся позже первые монструозные компьютеры +поднимали аналогичные вопросы, +и производители опять выбрали путь наименьшего сопротивления. +Классическая клавиатура укоренилась в массовом сознании настолько, +что была закреплена цементом официальных стандартов: + + ISO/IEC 9995‑2, + + ANSI‑INCITS 154‑1988, + + JIS X 6002:1980.

+

C появлением персональных компьютеров +и широким распространением клавиши Backspace, +безошибочно набранным текстом стало сложно кого‑либо удивить, +а машинопись моментально перестала быть профессией. +Тихо и незаметно на обочину истории вслед за машинистками +отправились и печатные машинки. +Классическая раскладка пережила устройства, для которых создавалась, +и осталась с нами. +Продолжает решать проблемы, которых больше нет.

+
+

+ Работа над ошибками +

+ +

Разумеется, сложившаяся клавиатурная традиция +не была как‑то исторически предопределена +и текущее положение дел во многом стало результатом +цепочки разнообразных случайностей.

+

Уже во времена печатных машинок были люди, +которые подметили билатеральную симметрию человеческого тела и +пытались учитывать её существование +во время проектирования клавиатур.

+
+ Фотография печатной машинки с симметричной клавиатурой. +
+ Rheinmetall Portable Ergonomic. Источник: + + Vintage Technology Obsessions. +
+
+ +

Если бы когда‑то давно компания Remington +не достигла впечатляющих экономических успехов, +и форм‑фактор именно их печатных машинок +не стал бы стандартом де‑факто, +то этого и десятка подобных постов +могло бы и не существовать.

+

Производители компьютерной периферии периодически пытались +как‑то решать проблемы классической клавиатуры, +чинить сломанное или вообще перепридумывать всё с чистого листа. +Некоторые из этих попыток я ещё упомяну ниже, +но масштаб и временные рамки экспериментов +можно поверхностно оценить на странице + + Ergonomic Keyboard History +от Xah Lee.

+

В XXI веке эксперименты над клавиатурами стали доступны энтузиастам +и сотни недовольных однообразием людей начали делать +свои уникальные устройства пытаясь убежать от боли в руках +или догнать здравый смысл.

+

Ниже я выделил самые популярные подходы +к решению проблем классической клавиатуры +и небрежно отсортировал их по увеличению степени радикализма.

+

+ Изогнутые клавиатуры +

+ +

Иногда производители замечают, +что руки у людей растут не из живота, +а по бокам от туловища. +После некоторых упражнений с транспортиром они +гнут существующую модель клавиатуры +в одном или нескольких измерениях так, +чтобы среднестатистическому пользователю +больше не нужно было гнуть запястья, +называют её эргономичной (Ergonomic Keyboards) +и получают армию преданных среднестатистических фанатов.

+
+ Фотография Microsoft Natural Keyboard Elite. +
+ Microsoft Natural Keyboard Elite. Источник: + + Reddit, u/Ang_xl9. +
+
+ +

Самая, пожалуй, известная линейка таких клавиатур + + принадлежит Microsoft. +Те, кто родились в прошлом веке, +могли видеть их Natural Keyboard при + + установке Windows 95. +Аналогичные модели выпускают и + + другие производители, +а похожий популярный форм‑фактор называется + + Alice Layout.

+

Подобные клавиатуры обычно выбирают те, +у кого уже начали болеть руки, +но кто ещё не готов переходить +на что‑нибудь радикально новое, +что не выглядит как классическая клавиатура +и не продаётся в соседнем магазине.

+

+ Сплит‑клавиатуры +

+ +

Некоторые производители +идут в своих рассуждениях ещё дальше и замечают, +что руки не только растут из плеч, +но и бывают расположены на разной ширине.

+

Очевидный следующий шаг — перестать пытаться угадать +удобный угол между половинками клавиатуры +и позволить разносить их на любое расстояние +и поворачивать, как вздумается. +Такие распиленные пополам устройства называют Split Keyboards.

+
+ Фотография Kinesis Freestyle 2. +
+ Kinesis Freestyle 2. Источник: + + kinesis‑ergo.com. +
+
+ +

Именитые производители заигрывали +с регулируемыми углами между половинками клавиатуры +ещё в начале 90‑х:

+ +

Сейчас разделение клавиатуры пополам встречается довольно часто, +но обычно в комплекте с какими‑нибудь другими нововведениями.

+

+ Ортолинейные клавиатуры +

+ +

Осознание нелепости горизонтального сдвига рядов +регулярно подталкивает людей к очевидному шагу: +отказаться от него.

+

Такие устройства называют Ortholinear Keyboards.

+
+ Фотография Keychron Q15 Max. +
+ Keychron Q15 Max. Источник: + + keychron.com. +
+
+ +

Самыми известными ортолинейными клавиатурами долгое время были + + Planck от OLKB, +но со временем и более именитые производители + + стали экспериментировать +с этим форм‑фактором. +Даже Keychron, производитель дефолтных клавиатур для программистов, +предлагает + + ортолинейную модель.

+

При всей своей красоте, симметрии и логичности, +ортолинейная раскладка всё ещё может считаться +насилием над человеческой анатомией: +руки не растут из живота. +Я, лично, не хотел бы держать что‑то похожее на столе, +но был бы не против иметь ортолинейную клавиатуру + + в ноутбуке.

+

Отдельно нужно сказать, что разделение на две половинки делает +подобную клавиатуру +гораздо более дружелюбной по отношению к запястьям. +Такие модели называют +Split Ortholinear Keyboard.

+
+ Фотография Helix Keyboard. +
+ + Helix. Источник: + + Reddit, u/toramorigan. +
+
+ +

+ Альтернативный горизонтальный сдвиг рядов +

+ +

Вообще, если долго смотреть на классическую клавиатуру, +то станет заметно, +что правая её часть не так уж и плоха: +сдвиг рядов делает её довольно анатомичной. +Очевидный шаг — сделать левую половину такой же удобной.

+
+ Фотография Katana60 Keyboard. +
+ + Katana60. Источник: + + Reddit, u/Rogue_Jellybean. +
+
+ +

Очевидно, что раскладку такого типа +можно было реализовать и на печатных машинках. +Возможно, если бы в своё время рынок захватил не Remington, +а какой‑нибудь другой производитель, +стандартная клавиатура гораздо лучше бы подходила стандартным рукам, а +MacBook продавался бы с + + подобной +раскладкой.

+

Клавиатуры такого типа называют +Symmetrical Staggered или Semi‑Ergonomic.

+

Современный ноутбук с похожей клавиатурой — компьютер мечты =)

+
+ Фотография бумажного прототипа Angel Keyboard. +
+ Бумажный Angel. Источник: + + github.com/jamessa/Angel. +
+
+ +

+ Вертикальный сдвиг рядов +

+ +

Пальцы на человеческой руке разной длины. +Классическая клавиатура этот очевидный факт решительно игнорирует.

+

Если взять клавиатуру, убрать из неё горизонтальный сдвиг рядов +(который учитывает фантомную анатомию печатной машинки) +и добавить вертикальный +(который учитывает реальную анатомию человеческой кисти), +то окажется, что нажимать даже самые неудобные клавиши стало удобно.

+

Такие устройства называют Column‑Staggered Keyboards.

+
+ Фотография Sofle Keyboard. +
+ + Sofle. Источник: + + Reddit, u/Raithmir. +
+
+ +

+ Кластер клавиш для большого пальца +

+ +

Выше я долго ругался на то, +что большой палец отлынивает от работы. +Заставить его трудиться легко — нужно перенести поближе +несколько клавиш из неудобных углов.

+
+ Фотография ErgoDox EZ. +
+ ErgoDox EZ. Источник: + + ergodox‑ez.com. +
+
+ +

Нажимать Backspace большим пальцем без необходимости +двигать рукой или тянуть мизинец — не что иное, +как суперспособность, +которая значительно снижает цену ошибок и опечаток.

+

Аналогично с Enter, Ctrl, Alt, Shift, Tab, Home +и любыми другими клавишами или их сочетаниями. +Самые востребованные можно расположить +в зоне мгновенного доступа, +прямо под большими пальцами — её называют Thumb Cluster.

+

Пользователи Vim сразу заметят, +насколько удобнее может стать привычный Esc.

+

+ Вогнутые клавиатуры +

+ +

Следующая очевидная идея лежит не в плоскости клавиатуры, +а в пространстве вокруг: +если приподнять дальние клавиши, +то тянуться до них будет проще.

+

Такие устройства называют Concave Keyboards, Contoured Keyboards +или Keywell Keyboards.

+
+ Фотография Kinesis Advantage2. +
+ Kinesis Advantage2. Источник: + + kinesis‑ergo.com. +
+
+ +

Плоские настольные клавиатуры часто пытаются достичь похожего эффекта +с помощью колпачков (кейкапов): +если посмотреть сбоку на случайную (не встроенную в ноутбук) +клавиатуру, то можно разглядеть + + OEM, Cherry или какой‑нибудь иной профиль.

+

Изогнутые клавиатуры идут дальше: +их клавиши располагаются на поверхности, +похожей на полусферу, +с учётом длины и анатомии пальцев.

+

«Полусфера» не только сокращает расстояния, +но ещё и позволяет рукам быстрее находить домашнюю позицию: +функцию засечек на F и J берёт на себя гравитация.

+

+ Ещё более радикальные подходы +

+ +

Список сверху довольно подробный, но, конечно же, не исчерпывающий. +За пределами поста остались разные узкоспециализированные устройства, +вроде аккордовых или стенографических клавиатур, +и огромное множество совсем нестандартных поделок.

+

Фантазия людей в приделывании подставок, подсветок, +трекпадов, трекболов, трекпоинтов, +стиков от геймпадов и прочих крутилок поистине безгранична.

+

Некоторые творцы вообще ставят под сомнение +необходимость наличия в клавиатуре клавиш и, +вдохновляясь + + забытыми устройствами, +создают пугающе дикие и футуристичные вещи, вроде + + Svalboard или + + CharaChorder.

+
+ Фотография Svalboard. +
+ Svalboard. Источник: + + svalboard.com. +
+
+ +

Все эти эксперименты вызывают интерес и трепетное уважение, +но как‑то комментировать их полезность +и удобство я не имею морального права: +курсором я почти не пользуюсь, +а что‑то уровня Svalboard держал в руках всего один раз.

+

Отговаривать от самостоятельного путешествия +в эту область непознанного я, +конечно же, не буду.

+
+

+ Неплохая клавиатура +

+ +

Мы обсудили, +чем плоха классическая клавиатура и как её можно исправить. +Если собрать вместе все улучшения и учесть все ошибки прошлого, +получим такой список признаков «хорошей клавиатуры»:

+ +
+ Фотография Glove80. +
+ + Glove80. Источник: + + arslan.io, Fatih Arslan. +
+
+ +

Такие клавиатуры лаконично называются +Column‑Staggered Ergonomic Split Keyboards. +Вместо Column‑Staggered иногда сознательно или ошибочно +используют слово Ortholinear, +но чаще всего такие ненужные подробности просто игнорируют: +Ergonomic Split Keyboard +с горизонтальным сдвигом рядов встречается нечасто.

+

Я полностью понимаю, что мой опыт и всё описанное в посте +нельзя считать какой‑то универсальной истиной, +а опыт других людей может сильно отличаться. +Именно поэтому я попытался показать весь спектр улучшений, +чтобы даже те, +кому «самые хорошие клавиатуры» по каким‑то причинам не подходят, +смогли найти для себя клавиатуру лучше, чем классическая.

+

+ Бесконечный ассортимент +

+ +

Несмотря на нишевость и не самую высокую популярность явления, +количество моделей «хороших клавиатур» давно превышает все разумные пределы. +Масштаб трагедии можно оценить на + + r/ErgoMechKeyboards.

+

Если очень хочется купить что‑то серийное и фабричное, +то можно рассмотреть такие варианты:

+ +

Если есть решимость покупать что‑то, собранное вручную, +делать что‑нибудь своими руками или искать людей, +которые готовы сделать это что‑нибудь за вас, +то количество вариантов возрастает на несколько порядков.

+

Составление списка хороших клавиатур — +популярное в интернете развлечение. +Вот неполный список списков разной степени красоты и полноты:

+ +

Кроме того, на + + compare.splitkb.com +можно сравнить некоторые популярные модели между собой по размеру, +распечатать понравившуюся на бумаге и проверить, +насколько она подходит к конкретным рукам.

+

Существует множество специализированных магазинов, которые предлагают +готовые клавиатуры, наборы для самостоятельной сборки +и отдельные запчасти. +Список таких площадок есть на + + wiki от r/ErgoMechKeyboards. +Популярные модели вроде народных + + Corne или + + Charybdis +регулярно продаются и перепродаются +на локальных и глобальных маркетплейсах.

+

Я сам рассматривал и трогал очень разные устройства. +Подробно расскажу про две клавиатуры, +которыми регулярно пользуюсь уже довольно продолжительное время.

+

+ Dactyl‑ManuForm +

+ +

Дактиль привлёк меня исключительно с эстетической точки зрения. +Сначала я натыкался на него в + + r/MechanicalKeyboards, +а потом уже целенаправленно высматривал в + + r/ErgoMechKeyboards.

+

Пребывая в полном неведении относительно проблем классических клавиатур, +я на каком‑то интуитивном уровне моментально понял: «Мне надо!»

+
+ Фотография Dactyl-ManuForm. +
+ Dactyl‑ManuForm 5×6. Источник: + + github.com/He4eT/oddkb. +
+
+ +

Не буду подробно рассказывать про все перипетии, +с которыми пришлось столкнуться в процессе сборки и прошивки, +но в результате оказалось, +что я случайно стал обладателем +едва ли не лучшей клавиатуры из тех, +что придумало человечество: +раздельной, изогнутой с учётом анатомии кистей, +с оптимальным количеством клавиш и целой дюжиной кнопок +для больших пальцев.

+

Миграция прошла на удивление быстро: первые два дня я всё проклинал, +но уже через неделю средняя скорость на новой клавиатуре сравнялась +со средней скоростью на ноутбучной +и впоследствии продолжила расти. +В итоге она стала выше, +чем мои рекордные показатели на классической раскладке.

+

Я использую Дактиль в качестве основной клавиатуры +уже более шести лет и всё ещё уверен, что сделал правильный выбор.

+

Минусы у этой модели тоже, конечно же, есть:

+ +

Время, к счастью, не стоит на месте, +и заниматься клавиатуростроением сейчас стало гораздо проще. +Если бы я сегодня решился +вдумчиво и неспешно собирать себе совершенную клавиатуру, +то начал бы с того, +что попытался сгенерировать идеальный кластер для большого пальца в + + Cosmos Keyboard Generator +и распечатал бы несколько пробных вариантов +перед окончательной сборкой.

+

+ Cantor +

+ +

С этой моделью меня познакомил друг, который однажды решил, +что в клавиатуре, которой он пользовался до этого, +было слишком много ненужных клавиш. +Не могу сказать, что я моментально стал фанатом Cantor, но +конструктивная простота и минималистичность +определённо вызывали уважение.

+

Впоследствии, когда меня попросили +подобрать для группы энтузиастов недорогую +и наименее сложную для самостоятельной сборки модель, +я, не раздумывая, предложил именно + + Cantor MX. +С того момента я успел +в разной степени поучаствовать в создании +по меньшей мере десятка таких клавиатур +и не собираюсь останавливаться. +Однажды за компанию собрал одну и для себя.

+
+ Фотография Cantor MX. +
+ Cantor MX. Источник: + + github.com/He4eT/cantor‑mx‑tastatura. +
+
+ +

Не могу сказать, +что мне так уж нужна была ещё одна самодельная клавиатура, +но применение ей нашлось сразу же. +Оказалось, что когда хорошая клавиатура +безболезненно помещается в рюкзак, +то пользоваться ею можно на любом столе, +а не только на заранее обустроенном рабочем месте.

+

Главное преимущество 42 клавиш состоит в том, +что перемещать руки не нужно вообще: +искомая кнопка либо уже лежит под пальцем, +либо располагается на соседней позиции.

+

Может показаться, что из‑за небольшого количества клавиш +из раскладки обязательно придётся что‑то выбросить. +В реальности же 42 клавиши +легко превращаются и в 100, и в 200. +Детально рассказывать про слои, макросы, комбо, + + home row modifiers +и остальные возможности современных прошивок +я не буду — на всё подробно и с примерами +лучше посмотреть в классическом видео + + «34 keys is all you need». +Свою раскладку для этой клавиатуры +я однажды тщательно описал на + + форуме Decentrala.

+

В конечном счёте, +после вдумчивой настройки и некоторого времени на привыкание, +единственным заметным неудобством остаются лишь редкие ситуации, +когда внезапно приходится зажимать комбинацию клавиш, +которую раньше никогда не встречал +и пользоваться которой вообще не планировал. +Особенно сильно радуешься, +когда такое случается два раза подряд :D

+

+ Как выбирать? +

+ +

Выбор клавиатуры, как и построение раскладки для неё, +стоит начинать с сеанса рефлексии. +Первым делом нужно попытаться понять, какие клавиши и сочетания +вообще нужны и как часто они используются.

+

Может показаться, что очень нужны все‑все‑все кнопки, +но практика показывает, что это далеко не всегда так: +клавишей ScrollLock не пользуются даже те, +кто знает, зачем она нужна.

+

Хорошей идеей будет недельку потрогать чью‑нибудь чужую клавиатуру, +поиграться с расположением клавиш и комбинациями, +понять, что удобно, а что нет, +и уже после этого делать осознанный выбор.

+

Если такой возможности нет, можно просто +определиться с желаемым количеством кнопок, +взять первую понравившуюся, но не слишком экзотичную модель +и начинать мучения. +Даже если выбор окажется неудачным, +бесценный опыт останется при вас.

+
+

+ Беседы с воображаемыми людьми +

+ +

Я зачем‑то десятки раз участвовал в дискуссиях про клавиатуры. +В этом разделе собраны +ответы на самые частые вопросы и возражения, +которые в таких разговорах встречаются.

+
+

Ряяяя! Ты вообще неправ!

+
+

Первым делом нужно успокоиться и вспомнить, +что классическая клавиатура далека от вымирания, +не нуждается в защите +и совершенно точно никуда не денется +из‑за очередного поста в интернете.

+

Ни я, ни кто‑либо другой +не собирается отбирать у вас любимую клавиатуру. +Единственная цель таких текстов — попытаться показать +привычные вещи с непривычной точки зрения, +продемонстрировать альтернативы и приблизить будущее, +где эти альтернативы будут доступнее.

+

Я допускаю, что могу где‑то ошибаться. +Допускаю даже, +что вообще ничего не понимаю в устройствах ввода +и всё написанное тут — полная чушь. +Рад буду увидеть в комментариях аргументированную критику, +признать, если потребуется, свою неправоту и дополнить пост. +Мечтаю, например, +получить внятный ответ на самый главный и животрепещущий вопрос: +«Зачем нужен горизонтальный сдвиг рядов?»

+
+

Слепая печать — отстой, она устарела и никому не нужна! +Я думаю дольше и чаще, чем печатаю! +Клавиатуры вообще не нужны!

+
+

Это популярные мнения, +которые я собираюсь подробно разобрать в отдельном посте.

+
+

А как же те, кто печатает двумя указательными пальцами?! +Ты о них подумал?!

+
+

Подумал. Более того, +два крупнейших разработчика мобильных операционных систем +как будто специально для меня +нечаянно провели глобальный эксперимент.

+

Оказалось, что пока людям прямо не указать на то, +что клавиатура их смартфона нарушает правила классической раскладки +и вообще‑то местами ортолинейная, они этого и не замечают. +Самое время открыть клавиатуру на телефоне, +проверить и очень удивиться.

+

Подозреваю, что если завтра все физические клавиатуры тоже +волшебным образом превратятся в чуть более симметричные аналоги, +то никто из миллионов людей, +которые печатают двумя указательными пальцами, +вообще не заметит подмены.

+
+

А как я буду пользоваться чужими классическими клавиатурами?

+
+

Изучение новой раскладки не требует стирания старой из памяти.

+

Насколько я знаю, +никто из тех, кто научился кататься на велосипеде, +не утратил в процессе обучения навык управления самокатом. +С клавиатурами примерно так же.

+
+

А как же другие люди? +Они же не смогут пользоваться моим компьютером!

+
+

Мы говорим о персональном инструменте. +Это нормально, когда он настроен так, как удобно владельцу. +В моём случае, например, +неподготовленный человек не сможет пользоваться +любым из моих компьютеров +даже в обнимку с тремя классическими клавиатурами.

+

Мне кажется странным при обсуждении чего‑то, +с чем приходится взаимодействовать каждый день, +ставить чужой эпизодический комфорт выше своего регулярного.

+

Если вам зачем‑то ну очень уж нужно постоянно +пускать за свой компьютер других людей, +просто положите рядом гостевую клавиатуру.

+
+

В правой руке у меня мышь, +а левой я орудую шорткатами в Photoshop. +Зачем мне эта расщелина в центре клавиатуры?!

+
+

Да, для работы +в графических редакторах, видеоредакторах или CAD‑программах +раздельная клавиатура без специальной предварительной настройки +подходит не слишком хорошо. +Такие программы по умолчанию рассчитаны на то, +что некоторые клавиши правой половины клавиатуры +придётся нажимать левой рукой.

+

Проблему можно решить переназначением клавиш в программах или +созданием специального слоя в самой клавиатуре, +но это, к сожалению, требует времени и ломает привычки.

+

Даже если переучиваться лень, +оставаться заложником классической раскладки +совершенно необязательно: +ортолинейная клавиатура — отличный вариант.

+
+

А вот в моём уникальном случае +ничто, кроме классической клавиатуры, вообще‑вообще не подходит!

+
+

Такое бывает. +Здорово, что этот случай такой уникальный и никак не влияет на +всех остальных пользователей клавиатур.

+
+

+ Опыт реальных людей +

+ +

В этом разделе я собрал +несколько ссылок на другие публикации похожей тематики.

+ +

К сожалению, +я пока не встречал развернутых и аргументированных текстов, +которые критикуют идею эргономичных клавиатур или рассказывают, +чем же на самом деле объективно хороша классическая раскладка. +Если такие материалы существуют или появятся, +присылайте ссылки на них, я дополню список.

+
+

+ Заключение +

+ +

Я понимаю, +что субоптимальное расположение кнопочек на клавиатуре — это +не какая‑то глобальная проблема человечества. +В мире есть войны, голод и прочие ужасные явления, я знаю.

+

Ещё я знаю, что кроме больших и настоящих проблем +есть также и множество вещей поменьше, +которые можно было бы сделать как‑то пооптимальнее: +английский алфавит, непредсказуемый календарь, неровные часовые пояса, + + возвратный гортанный нерв жирафа +и прочие тысячи и тысячи разных штук, +которые мы уже в силу привычки перестали замечать вообще.

+

Клавиатуры выгодно выделяются из этого списка тем, +что они, пусть даже и подвержены + + сетевому эффекту, +всё равно остаются персональными устройствами. +Не обязательно ждать, пока за голову возьмётся всё человечество, +можно самостоятельно сделать шаг навстречу менее сумасшедшему миру.

+

Искренне верю, что каждая такая «маленькая персональная революция» +слегка приближает нас к будущему, +где наконец‑то можно будет прийти в магазин и купить ноутбук, +который проектировали с заботой о человеческих руках, +а не в погоне за случайными трендами XIX века.

+
+

+ Комментарии +

+ +

Публично обсудить пост можно на этих площадках:

+ +

Кроме того, я с радостью отвечу на любые вопросы, +ознакомлюсь с предложениями и замечаниями к тексту поста, +помогу выбрать клавиатуру +или даже приду к вам в подкаст для дискуссии. +Не стесняйтесь писать мне + + любым удобным способом.

+ +
+
+ + + + + + + + diff --git a/docs/posts/index.html b/docs/posts/index.html new file mode 100644 index 0000000..d8c3dc0 --- /dev/null +++ b/docs/posts/index.html @@ -0,0 +1,151 @@ + + + + + + + + + + + posts | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Posts

+

Announcements are available via RSS.

+
+

2026

+ +

2024

+ +

2020

+ + +
+
+ + + + + + + + diff --git a/docs/projects/index.html b/docs/projects/index.html new file mode 100644 index 0000000..dceaf64 --- /dev/null +++ b/docs/projects/index.html @@ -0,0 +1,351 @@ + + + + + + + + + + + projects | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Projects

+

Incomplete list of my projects and experiments.

+
+ +
+

Interactive Fiction Tools

+ +
+

Games

+ +
+

Services

+ +
+

Inventory

+

Hardware

+ +
+

Settings

+ +
+

Software, Plugins and Tools

+ +
+ +
+
+ + + + + + + + diff --git a/docs/qr/index.html b/docs/qr/index.html new file mode 100644 index 0000000..08f1dc2 --- /dev/null +++ b/docs/qr/index.html @@ -0,0 +1,25 @@ + + + + + + + + Redirect | oddsquat + + + +
+ Redirect to + + /images/qr/oddsquat.org.png + +
+ + diff --git a/docs/rss.xml b/docs/rss.xml new file mode 100644 index 0000000..b2225c1 --- /dev/null +++ b/docs/rss.xml @@ -0,0 +1 @@ +<![CDATA[oddsquat]]>https://oddsquat.orghttps://oddsquat.org/icon.svg'oddsquathttps://oddsquat.orgRSS for NodeThu, 23 Apr 2026 18:37:37 +0200<![CDATA[[EN] encrypted XMPP]]>https://oddsquat.org/posts/2026/encrypted_XMPP/https://oddsquat.org/posts/2026/encrypted_XMPP/Thu, 23 Apr 2026 00:00:00 +0200<![CDATA[[RU] ugly keyboards]]>https://oddsquat.org/posts/2026/ugly_keyboards_ru/https://oddsquat.org/posts/2026/ugly_keyboards_ru/Wed, 18 Mar 2026 00:00:00 +0100<![CDATA[[RU] wrapped bw]]>https://oddsquat.org/posts/2024/wrapped_bw_ru/https://oddsquat.org/posts/2024/wrapped_bw_ru/Sat, 27 Jul 2024 00:00:00 +0200<![CDATA[[RU] selfhosted LLM]]>https://oddsquat.org/posts/2024/selfhosted_llm/https://oddsquat.org/posts/2024/selfhosted_llm/Mon, 15 Jan 2024 00:00:00 +0100<![CDATA[[RU] typographic linter]]>https://oddsquat.org/posts/2020/typographic_linter/https://oddsquat.org/posts/2020/typographic_linter/Thu, 18 Nov 2021 00:00:00 +0100<![CDATA[[RU] initial post]]>https://oddsquat.org/posts/2020/initial_post/https://oddsquat.org/posts/2020/initial_post/Sun, 08 Nov 2020 00:00:00 +0100 \ No newline at end of file diff --git a/docs/test/index.html b/docs/test/index.html new file mode 100644 index 0000000..4b3867e --- /dev/null +++ b/docs/test/index.html @@ -0,0 +1,390 @@ + + + + + + + + + + + markdown test page | oddsquat + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+

Markdown: Syntax

+

Markdown is intended to be as easy-to-read and easy-to-write as is feasible.

+

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 Setext, atx, Textile, reStructuredText, +Grutatext, and EtText — the single biggest source of +inspiration for Markdown's syntax is the format of plain text email.

+

Note: This document is itself written using Markdown.

+
+

Table Of Content

+ +
+

Block Elements

+

Paragraphs and Line Breaks

+

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.

+

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 <br /> tag.

+

When you do want to insert a <br /> break tag using Markdown, you +end a line with two or more spaces, then type return.

+

Headers

+

Headers are lines that start with the # symbol. +The number of characters defines the header level, +from one (#) for <h1> to six (######) for <h6>.

+

Blockquotes

+

Markdown uses email-style > 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 > before every line:

+
+

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.

+

Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse +id sem consectetuer libero luctus adipiscing.

+
+

Markdown allows you to be lazy and only put the > before the first +line of a hard-wrapped paragraph:

+
+

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.

+
+
+

Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse +id sem consectetuer libero luctus adipiscing.

+
+

Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by +adding additional levels of >:

+
+

This is the first level of quoting.

+
+

This is nested blockquote.

+
+

Back to the first level.

+
+

Blockquotes can contain other Markdown elements, including headers, lists, +and code blocks:

+
+

Here's a list

+
    +
  1. This is the first list item.
  2. +
  3. This is the second list item.
  4. +
+

Here's an example code block

+
const PI = 3.1415`
+

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.

+

Lists

+

Markdown supports ordered (numbered) and unordered (bulleted) lists.

+

Unordered lists use asterisks, pluses, and hyphens — interchangably +— as list markers:

+ +

is equivalent to:

+ +

and:

+ +

Ordered lists use numbers followed by periods:

+
    +
  1. Bird
  2. +
  3. McHale
  4. +
  5. Parish
  6. +
+

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:

+

If you instead wrote the list in Markdown like this:

+
    +
  1. Bird
  2. +
  3. McHale
  4. +
  5. Parish
  6. +
+

or even:

+
    +
  1. Bird
  2. +
  3. McHale
  4. +
  5. Parish
  6. +
+

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.

+

To make lists look nice, you can wrap items with hanging indents:

+ +

But if you want to be lazy, you don't have to:

+ +

List items may consist of multiple paragraphs. Each subsequent +paragraph in a list item must be indented by either 4 spaces +or one tab:

+
    +
  1. This is a list item 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. Donec sit amet nisl. Aliquam semper ipsum +sit amet velit.

    +
  2. +
  3. Suspendisse id sem consectetuer libero luctus adipiscing.

    +
  4. +
+

It looks nice if you indent every line of the subsequent +paragraphs, but here again, Markdown will allow you to be +lazy:

+ +

To put a blockquote within a list item, the blockquote's > +delimiters need to be indented:

+ +

To put a code block within a list item, the code block needs +to be indented with list item indentation in mind:

+ +

Code Blocks

+

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 <pre> and <code> tags.

+
tell application "Foo"
+    beep
+end tell

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.

+
## This is **not** a Markdown

Tables

+

Markdown tables are created using pipes (|) to separate columns and hyphens (-) to define the header row. Here’s the basic structure:

+
    +
  1. Header Row: The first row contains column names.
  2. +
  3. Divider Line: The second row uses hyphens to separate the header from the data.
  4. +
  5. Data Rows: The rows below the divider contain the actual data.
  6. +
+ + + + + + + + + + + + + + + + + + +
NameAgeCity
Alice25New York
Bob30London
+

You can style text inside Markdown table cells just like regular Markdown. +This includes making text bold, italic, monospaced, or adding links or code.

+ + + + + + + + + + + + + + + + + + +
Column 1Column 2Column 3
ThingsDon'tNeed
ToLookPretty
+

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.

+
    +
  1. Left-aligned: :--- (Colon on the left)
  2. +
  3. Right-aligned: ---: (Colon on the right)
  4. +
  5. Center-aligned: :---: (Colons on both sides)
  6. +
+ + + + + + + + + + + + + + + + + + +
Column 1Column 2Column 3
Cell ContentsMore StuffAnd Again
You Can AlsoPut Pipes InLike this [|]
+

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.

+ + + + + + + + + + + + + + + +
Header 1Header 2
Cell 1
Cell 3Cell 4
+

Span Elements

+ +

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 optional +title for the link, surrounded in quotes. For example:

+

This is an example inline link.

+

This link has a title attribute.

+

Emphasis

+

Markdown treats asterisks (*) and underscores (_) as indicators of +emphasis. Text wrapped with one * or _ will be wrapped with an +HTML <em> tag; double *'s or _'s will be wrapped with an HTML +<strong> tag. E.g., this input:

+

single asterisks

+

single underscores

+

double asterisks

+

double underscores

+

Code

+

To indicate a span of code, wrap it with backtick quotes (`). +Unlike a pre-formatted code block, a code span indicates code within a +normal paragraph. For example:

+

Use the printf() function.

+ +
+
+ + + + + + + +