16 Commits

Author SHA1 Message Date
00b810599a Add descriptions to new articles
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 16:21:01 -07:00
3403a28e35 Publish two new articles
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 16:19:06 -07:00
53e638cb17 Use new flag to avoid deduplicating series
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 16:18:36 -07:00
aabbc66bb2 Grammar pass
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 16:09:27 -07:00
767545dda4 Update theme
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 16:02:27 -07:00
f90760d66a Add a link to the source code.
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 16:01:28 -07:00
d696183690 Split into two files
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 16:00:31 -07:00
21463ede20 Make some edits.
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-04-05 15:42:26 -07:00
a278d1f572 Add initial (bundled) draft of flashcard writeup 2026-04-05 15:22:12 -07:00
bdd2be48bd Update theme
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-03-22 01:31:20 -05:00
a350cd14f8 Publich 'On Spiders'
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-03-22 01:08:11 -05:00
49ab3982f1 Extract writing CSS into a separate file from the void.
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-03-22 01:04:58 -05:00
4d35ca04fe Slightly tweak wording
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-01-19 10:23:02 -08:00
5e117e3f48 Ignore warnings encoded as syntax highlighting
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-01-04 16:33:43 -08:00
a6f3cd3f9a Update theme.
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-01-03 18:08:51 -08:00
ccc8d6f0eb Re-enable hiding LaTeX
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2026-01-03 16:34:57 -08:00
14 changed files with 1313 additions and 57 deletions

12
agda.rb
View File

@@ -46,7 +46,17 @@ class AgdaContext
# assumes that links can't span multiple pages, and that links
# aren't nested, so ensure that the parent of the textual node
# is the preformatted block itself.
raise "unsupported Agda HTML output" if at.parent.name != "pre"
if at.parent.name != "pre"
# Costmetic highlight warnings are sometimes applied to newlines.
# If they don't have content, treat them as normal newlines at the
# top level.
#
# This is an <a class="CosmeticProblem">\n</a> node.
unless at.parent.name == "a" and at.parent['class'] == "CosmeticProblem" and at.content.strip.empty?
raise "unsupported Agda HTML output in file #{file} at line #{line} (content #{at.content.inspect})"
end
end
# Increase the line and track the final offset. Written as a loop
# in case we eventually want to add some handling for the pieces

View File

@@ -0,0 +1,19 @@
@import "variables.scss";
@import "mixins.scss";
.spiderweb {
position: fixed;
top: 0;
right: 0;
opacity: 0.25;
max-width: 900px;
@include below-container-width {
width: $container-width-threshold;
}
}
code {
background: none;
border: none;
}

View File

@@ -1,51 +1,3 @@
@import "variables.scss";
body {
background-color: #1c1e26;
--text-color: white;
font-family: $font-code;
}
h1, h2, h3, h4, h5, h6 {
text-align: left;
font-family: $font-code;
}
h1::after {
content: "(writing)";
font-size: 1rem;
margin-left: 0.5em;
position: relative;
bottom: -0.5em;
color: $primary-color;
}
nav .container {
justify-content: flex-start;
a {
padding-left: 0;
margin-right: 1em;
}
}
.header-divider {
visibility: hidden;
}
hr {
height: auto;
border: none;
&::after {
content: "* * *";
color: $primary-color;
font-size: 2rem;
display: block;
text-align: center;
}
}
/* Code for the CSS glitch effect. Originally from: https://codepen.io/mattgrosswork/pen/VwprebG */
.glitch {

47
assets/scss/writing.scss Normal file
View File

@@ -0,0 +1,47 @@
@import "variables.scss";
body {
background-color: #1c1e26;
--text-color: white;
font-family: $font-code;
}
h1, h2, h3, h4, h5, h6 {
text-align: left;
font-family: $font-code;
}
h1::after {
content: "(writing)";
font-size: 1rem;
margin-left: 0.5em;
position: relative;
bottom: -0.5em;
color: $primary-color;
}
nav .container {
justify-content: flex-start;
a {
padding-left: 0;
margin-right: 1em;
}
}
.header-divider {
visibility: hidden;
}
hr {
height: auto;
border: none;
&::after {
content: "* * *";
color: $primary-color;
font-size: 2rem;
display: block;
text-align: center;
}
}

718
assets/svg/spiderweb.svg Normal file
View File

@@ -0,0 +1,718 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="197.21727mm"
height="106.16592mm"
viewBox="0 0 197.21727 106.16592"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
sodipodi:docname="spiderweb.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.2119635"
inkscape:cx="292.91312"
inkscape:cy="235.56816"
inkscape:window-width="1728"
inkscape:window-height="996"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="mainlayer" /><defs
id="defs1" /><g
inkscape:groupmode="layer"
id="mainlayer"
inkscape:label="Main Layer"
transform="translate(-12.908849,0.44133637)"><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 107.50107,0.48134806 -4.01124,4.49258204 c 0,0 5.61573,31.9294199 14.28,37.0637999 8.66426,5.134379 7.54112,5.294828 7.54112,5.294828"
id="path2"
sodipodi:nodetypes="ccsc" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 173.22006,0.15397339 85.91715,39.263214"
id="path3" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.22833,-0.15397339 c 0,0 -49.11751,32.33441039 -64.66882,56.50823339"
id="path4" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 185.53793,-0.30794677 c 0,0 -48.80956,45.57612277 -56.50823,61.43538277"
id="path5" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 184.76806,4.1572814 c 0,0 -42.1887,53.8906846 -31.41057,70.2118646"
id="path6" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 190.46508,0 c 0,0 -31.10262,53.428764 -18.93873,84.99331"
id="path7" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 191.85084,-0.15397339 c 0,0 -17.55297,60.81948839 -3.23344,92.69197939"
id="path8" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 193.54455,-0.15397339 c 0,0 -6.62086,54.50657839 16.32117,86.53304339"
id="path9" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 196.31607,-0.30794677 c 0,0 6.00496,36.33771977 13.54965,48.19366877"
id="path10" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 197.39388,0.15397339 209.86572,26.483423"
id="path11" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 198.62567,0.30794677 209.55778,15.551312"
id="path12" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 115.28177,0.20224871 c 0,0 -2.52811,22.24735829 -0.30337,25.68558729"
id="path13" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 120.33799,1.415741 0.30337,11.932674 v 0 c 0,0 -1.21349,6.269711 -1.21349,10.213561"
id="path14" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 120.64136,13.752913 c 0,0 1.92136,7.584327 2.02249,8.59557"
id="path15" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 125.89983,0.80899486 c 0,0 0,9.50569014 5.05621,17.79788714"
id="path16" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 126.91107,10.112436 c 0,0 -0.4045,5.865213 0.70787,10.314684"
id="path17" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 128.63018,0.20224871 134.39427,17.494514"
id="path18" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 135.30439,0.30337307 c 0,0 0.60675,9.91018693 2.42698,15.57315093"
id="path19" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 139.75386,0.7078705 c 0,0 -1.41574,3.94385 1.61799,13.7529125"
id="path20" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 143.09097,1.1123679 c 0,0 1.82024,8.3933216 3.84272,11.2248041"
id="path21" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 144.30446,6.0674614 c 0,0 -0.809,2.9326065 -0.30337,6.7753316"
id="path22" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 147.54044,0.40449743 148.75393,10.618058"
id="path23" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 150.87754,0.20224871 3.43823,7.78657559"
id="path24" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 153.2034,0.10112436 3.3371,6.37083444"
id="path25" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 155.52926,0.20224871 3.33711,5.66296389"
id="path26" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 159.06861,0.50562179 161.4956,4.7528448"
id="path27" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 162.00122,0.40449743 164.02371,3.94385"
id="path28" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 164.4282,-0.30337307 1.92137,3.33710377"
id="path29" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 115.28177,26.797955 c 0,0 -2.02249,11.427052 11.93267,16.786643"
id="path30" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 120.03461,24.269846 c 0,0 3.84273,11.022554 12.74167,14.86528"
id="path31" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 126.30432,21.33724 c 0,0 0.4045,9.404565 9.00007,15.067529"
id="path32" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 134.69765,17.797887 c 0,0 -4.85397,8.494446 5.15734,14.460782"
id="path33" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 137.428,16.078773 c 0,0 0.30337,6.26971 5.05622,13.95516"
id="path34" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 140.15836,14.15741 c 0,0 -1.01124,6.370835 4.75285,13.854038"
id="path35" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 143.29321,13.045042 c 0,0 -0.91011,7.584327 4.34835,12.640545"
id="path36" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 146.32695,12.842793 c 0,0 -2.32586,4.044975 3.13485,11.325928"
id="path37" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 147.54044,11.427052 c 0,0 0.20225,7.17983 4.75284,10.820307"
id="path38" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 149.76517,9.9101872 c 0,0 1.41574,8.4944458 3.94385,10.3146838"
id="path39" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 151.58541,9.7079384 c 0,0 2.02249,6.4719586 5.25847,8.5955706"
id="path40" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 153.2034,9.0000679 c 0,0 2.12361,5.9663371 4.85397,7.8877001"
id="path41" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 154.31577,8.2921974 5.86521,7.0787046"
id="path42" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 209.52546,105.56381 12.995207,0"
id="path43" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 158.24027,7.5068802 4.05133,6.7919388"
id="path44" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 160.24291,5.8928883 3.88494,7.1587677"
id="path45" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 162.81832,4.4087534 3.71034,6.8968616"
id="path46" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 165.48103,3.666686 3.01192,6.416701"
id="path47" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 167.88184,2.4008064 2.79367,5.9365392"
id="path48" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 171.19932,0.52381229 2.1389,5.98019051"
id="path49" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 173.42552,0.43651024 1.70239,4.62700856"
id="path50" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 176.17554,0.43651024 1.30953,3.14287386"
id="path51" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 176.52474,4.6270087 1.39684,1.7460409"
id="path52" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 174.56045,5.9365393 1.83334,1.920645"
id="path53" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 171.85408,7.8571843 2.70637,2.0952494"
id="path54" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 170.23899,9.4286212 2.09526,2.4881088"
id="path55" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 167.00882,11.654823 3.49208,2.051599"
id="path56" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 165.00087,12.702448 3.44843,2.793666"
id="path57" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 163.25483,14.317536 3.31748,3.011921"
id="path58" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 161.02863,15.32151 4.23415,3.797639"
id="path59" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 158.80243,17.89692 3.75399,3.579384"
id="path60" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 155.6159,18.900894 5.84924,4.103196"
id="path61" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 154.48098,21.432653 5.10717,3.317478"
id="path62" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 151.94922,22.698533 4.58335,4.845264"
id="path63" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 149.06825,25.361245 6.19844,4.190498"
id="path64" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 148.23888,26.539822 c 0,0 1.74604,3.492082 4.71431,5.674635"
id="path65" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 144.57219,28.635073 c 0,0 2.35716,2.575409 6.54766,4.583356"
id="path66" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 142.6952,30.992228 c 0,0 3.66669,4.365101 6.2421,5.019868"
id="path67" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 138.63565,33.916847 c 0,0 4.23415,3.186523 8.25005,4.059544"
id="path68" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 137.10787,35.008121 c 0,0 4.1905,5.89289 7.15877,6.023843"
id="path69" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 134.88167,37.758137 c 0,0 2.79366,4.32145 6.72225,5.500028"
id="path70" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 132.69912,39.373225 c 0,0 3.53573,5.325425 7.50797,6.111142"
id="path71" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 130.77847,41.512124 c 0,0 3.66669,6.02384 7.20242,7.289721"
id="path72" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 128.42131,43.781977 c 0,0 2.44446,5.369076 7.07147,6.984164"
id="path73" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 127.06813,45.528018 c 0,0 2.70637,7.420676 6.11115,8.555601"
id="path74" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 124.49272,48.452637 c 0,0 2.92462,6.373051 6.94051,7.988139"
id="path75" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 182.0798,2.2845037 2.38172,2.0414713"
id="path76" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.92685,5.4925301 2.2845,1.7498327 2.81918,2.0900778"
id="path77" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 188.7875,10.110144 4.03433,-0.729097 4.71483,-1.3123744"
id="path78" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 197.68248,7.8256402 2.38171,-0.8749163"
id="path79" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 200.55026,5.055072 1.60401,-1.0693422"
id="path80" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 201.6196,3.8399103 204.29296,0"
id="path81" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 203.32083,6.9021176 c 0,0 3.35384,-2.7219619 4.18015,-6.65908529"
id="path82" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 204.92484,8.4089178 c 0,0 3.45106,-2.2358971 4.61761,-7.3881821"
id="path83" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 180.76743,4.4717943 1.84705,2.6247489"
id="path84" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 182.46866,7.1451498 2.57614,2.7219619"
id="path85" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 184.80176,10.547602 3.49967,1.992865"
id="path86" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 188.44725,13.220958 4.37458,-0.194426"
id="path87" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 192.67601,13.804235 5.88138,-1.992865"
id="path88" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 198.55739,10.742028 3.0136,-1.0693422"
id="path89" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 203.51525,7.5340016 -1.94426,1.6526195"
id="path90" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 205.11927,9.4782599 -2.57615,2.0414711"
id="path91" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 177.65661,7.5340016 2.43033,2.0900776"
id="path92" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 180.23276,10.450389 3.0622,2.770569"
id="path93" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.10054,14.14448 4.27737,2.381717"
id="path94" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 188.39864,15.748494 4.13155,0.09721"
id="path95" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 193.3079,15.991526 5.44392,-1.360981"
id="path96" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 199.57813,15.408248 4.76343,-0.243032"
id="path97" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 204.04992,13.220958 2.77057,-1.360981"
id="path98" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 206.62607,10.596209 2.96499,-3.7913043"
id="path99" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 207.74402,12.929319 1.70122,-2.284504"
id="path100" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 175.71236,9.5754729 2.81917,2.3331101"
id="path101" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 178.04547,13.609809 4.13155,2.867781"
id="path102" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 182.37144,16.428984 4.81204,2.235897 5.2495,0.09721"
id="path103" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 192.28716,18.713488 7.4854,-0.82631"
id="path104" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 200.21001,19.296765 4.95786,-1.701226"
id="path105" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 205.4109,15.311035 2.77057,-1.5068"
id="path106" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 200.8419,20.852172 -7.97146,1.5068"
id="path107" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 186.50299,22.164546 6.85351,0.194426"
id="path108" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 180.71882,19.393978 5.83278,2.770568"
id="path109" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 176.49006,15.408248 4.71483,3.499665"
id="path110" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 173.96252,11.568338 2.91639,3.208026"
id="path111" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 171.62941,13.46399 3.30524,3.15942"
id="path112" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 174.88605,18.178817 4.76343,3.062207"
id="path113" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 179.40645,23.039463 7.14515,2.18729"
id="path114" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 185.87111,26.150276 6.70769,-0.534671"
id="path115" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 200.79329,23.428314 -8.2631,2.187291"
id="path116" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 207.74402,22.504792 c 0,0 -5.00647,2.867781 -5.88139,3.062207"
id="path117" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 168.42139,16.185952 5.2981,2.624749"
id="path118" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 166.91459,17.692752 4.76343,3.888517"
id="path119" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 172.65015,20.657746 5.63835,3.402452"
id="path120" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 178.48292,26.053063 7.04794,1.652619"
id="path121" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 185.28783,28.726418 7.63121,-0.68049"
id="path122" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 201.71681,26.393308 -8.79777,1.604014"
id="path123" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 202.49452,29.066664 193.3565,31.74002"
id="path124" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 185.23922,31.399773 8.2145,-0.14582"
id="path125" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 176.68449,29.455514 8.65195,1.166556"
id="path126" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 169.9768,25.858637 6.41605,3.256634"
id="path127" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 171.09474,23.379708 c 0,0 3.93713,3.353845 6.41605,3.645484"
id="path128" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 165.16475,20.074469 c 0,0 3.30524,3.645484 5.34672,3.985729"
id="path129" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 162.88025,21.678482 c 0,0 4.4718,4.325975 6.56187,4.569007"
id="path130" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 160.49853,23.914379 c 0,0 4.90926,3.791303 7.14515,4.131549"
id="path131" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 175.42072,31.691413 c 0,0 -4.61762,-0.388853 -7.77704,-3.499665"
id="path132" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 184.80176,33.100999 c 0,0 -7.14515,-0.194424 -9.13801,-1.604013"
id="path133" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 184.65594,35.142471 8.79778,-1.263769"
id="path134" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 203.32083,31.5942 -10.20736,2.478929"
id="path135" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 166.91459,29.892974 c 0,0 -5.58975,-1.020736 -8.06868,-4.180156"
id="path136" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 166.37992,30.524857 c 0,0 6.12441,3.548272 8.84637,3.839911"
id="path137" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 184.51013,36.114601 c 0,0 -9.33244,-2.041472 -9.67269,-2.624749"
id="path138" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 184.36431,37.47558 c 0,0 8.36031,-0.437457 9.0408,-0.729096"
id="path139" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 204.34156,33.975915 c 0,0 -10.01293,3.402455 -10.83924,3.402455"
id="path140" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 204.6332,36.211814 -11.13088,3.305238"
id="path141" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.73242,41.023854 9.96433,-1.701228"
id="path142" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 173.52507,38.544923 10.20735,2.478931"
id="path143" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 174.4972,36.697877 9.86711,1.992866"
id="path144" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 164.97033,33.635672 9.13801,2.867781"
id="path145" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 156.36699,29.018058 8.94358,3.353845"
id="path146" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 164.24123,34.899438 c 0,0 -8.3117,-2.916385 -9.18662,-5.152284"
id="path147" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 173.71949,38.787956 c 0,0 -7.97146,-2.138685 -9.96432,-4.714827"
id="path148" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 206.33443,38.593529 -12.20022,2.867782"
id="path149" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.97545,42.530654 9.7213,-1.166556"
id="path150" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 172.74736,42.093194 c 0,0 9.04081,1.798439 10.5476,0.923523"
id="path151" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 162.58861,37.864433 c 0,0 7.19376,4.034338 10.15875,3.694091"
id="path152" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 153.20756,32.12887 c 0,0 6.22163,5.881383 9.08941,5.686956"
id="path153" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 150.72864,34.559195 c 0,0 8.11727,5.443923 10.20735,5.395317"
id="path154" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 149.31905,35.725748 c 0,0 6.70769,7.290969 10.79063,6.075809"
id="path155" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 171.58081,46.516382 c 0,0 -4.76344,1.749835 -11.66555,-4.277368"
id="path156" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.00333,48.071791 c 0,0 -4.81204,1.117949 -11.2767,-1.701226"
id="path157" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 194.91191,46.710808 c 0,0 -9.42965,2.819176 -11.47113,1.749833"
id="path158" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 194.37724,43.94024 -10.74203,0.729099"
id="path159" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 160.64435,41.995981 11.13088,2.527538"
id="path160" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 183.19775,46.224745 171.82384,44.572126"
id="path161" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 206.43164,41.169672 194.23142,44.08606"
id="path162" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 207.35516,43.11393 -12.73489,3.694092"
id="path163" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 208.08426,45.301222 195.54379,49.57859"
id="path164" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 147.28059,37.851884 c 0,0 7.4801,7.80252 11.22015,6.448364"
id="path165" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 145.41056,39.850877 c 0,0 5.28766,7.99597 11.86499,7.480102"
id="path166" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 142.44432,43.139542 c 0,0 6.38387,6.899749 13.41259,7.738033"
id="path167" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 141.15465,44.751632 c 0,0 3.09521,8.576321 12.70327,12.058439"
id="path168" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 139.67152,46.621657 c 0,0 4.51385,13.283626 12.63879,15.991938"
id="path169" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 137.35011,49.00755 c 0,0 8.18942,11.607054 15.41159,9.93048"
id="path170" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 135.86699,51.071028 c 0,0 -3.67557,8.124936 15.02468,15.218135"
id="path171" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 131.74004,56.874552 2.64383,8.124936"
id="path172" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 146.76472,71.641302 4.57834,-2.192443"
id="path173" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 131.4821,56.229718 5.28766,0.709318"
id="path174" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 157.72694,45.58992 13.15465,4.320402"
id="path175" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 157.27555,47.00856 c 0,0 9.60806,5.739043 13.15466,4.642821"
id="path176" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 156.56623,49.00755 c 0,0 6.19043,5.739043 12.83224,5.15869"
id="path177" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 154.88966,53.198987 14.63778,4.062468"
id="path178" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 168.94708,63.387398 c 0,0 -13.67052,-6.319393 -14.31536,-11.284633"
id="path179" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 169.14053,59.840799 161.85389,59.32493"
id="path180" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 153.5355,56.874552 c 0,0 3.35315,5.094208 14.89572,9.479092"
id="path181" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 151.66548,63.838785 c 0,0 13.79949,6.254911 16.83022,5.287655"
id="path182" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 168.68915,72.28614 c 0,0 -17.41058,-2.127962 -17.99093,-4.126953"
id="path183" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 170.62366,49.910322 c 0,0 11.60705,2.450378 13.02569,1.354156"
id="path184" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 170.04331,55.971783 c 0,0 6.83526,-1.289674 12.70327,0.902769"
id="path185" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.52038,55.907299 c 0,0 12.18741,-0.644837 12.63879,-1.547606"
id="path186" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 182.87555,49.587903 c 0,0 12.76775,1.998994 13.15465,3.095215"
id="path187" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 170.23676,51.457928 c 0,0 4.3204,-0.19345 5.22317,0.902772 0.90277,1.096221 6.06146,2.063475 7.22217,0.902771"
id="path188" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 183.32693,52.038281 4.25592,-1.547606"
id="path189" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 190.87151,50.619641 4.19144,-1.354156"
id="path190" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 196.61056,54.295209 c 0,0 12.50982,-5.868011 13.34811,-3.933503"
id="path191" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 195.12744,52.16725 c 0,0 14.18639,-0.515869 14.63778,-3.997987"
id="path192" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 169.72089,57.841808 c 0,0 11.67153,3.159696 13.15466,1.547606"
id="path193" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 197.31988,56.939036 -14.37985,3.288665"
id="path194" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 197.77126,60.356667 c 0,0 -9.4146,-0.451384 -9.67254,-1.354156"
id="path195" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 169.2695,61.517373 c 0,0 13.28363,0 13.67053,1.22519"
id="path196" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 168.81812,64.161201 c 0,0 5.93249,-0.902769 14.37985,1.354156"
id="path197" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 168.36673,67.965735 14.89572,0.967255"
id="path198" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 169.39847,73.769261 13.7995,-2.192443"
id="path199" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 168.23777,71.641302 c 0,0 1.48312,5.674559 15.92745,2.643828"
id="path200" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 169.33399,76.735508 c 0,0 7.0932,-0.773803 14.31536,10.833248"
id="path201" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 184.74557,76.671024 -8.96322,2.192443"
id="path202" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 184.55212,79.766238 177.26547,79.44382"
id="path203" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 162.11182,80.346591 7.35113,-2.966246"
id="path204" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 165.27152,82.023166 4.12695,-2.256928"
id="path205" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 171.07504,82.539035 c 0,0 14.25089,-0.128969 14.63779,1.870024"
id="path206" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 180.74759,89.890168 c 0,0 4.90075,-3.417634 5.99698,-2.256928"
id="path207" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 182.87555,67.901253 15.60503,-4.642821"
id="path208" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 199.44784,65.773294 c 0,0 -15.21814,2.837278 -15.66952,2.837278"
id="path209" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 182.94003,71.899237 17.3461,-3.224181"
id="path210" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 183.52038,73.640296 201.5758,70.996465"
id="path211" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 183.71383,77.444827 202.3496,73.575811"
id="path212" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 185.06799,80.153141 c 0,0 19.47405,-2.063477 21.08614,-0.128968"
id="path213" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 203.70376,77.057926 -7.02872,2.385894"
id="path214" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 186.35766,85.440797 c 0,0 9.67254,2.450377 13.54156,1.80554"
id="path215" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 188.1632,90.470518 209.37831,86.02115"
id="path216" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 186.6156,87.568756 c 0,0 17.34609,-2.127959 20.44131,-3.804534"
id="path217" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 197.70678,57.068005 12.25189,-2.966246"
id="path218" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 198.09368,60.55012 11.86499,-3.804534"
id="path219" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 199.06094,62.613595 10.63979,-2.643828"
id="path220" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 199.77026,65.773294 c 0,0 8.83425,-4.255921 9.8015,-3.546599"
id="path221" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 200.86648,68.610572 c 0,0 4.38488,1.354156 8.83425,-1.483122"
id="path222" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 201.70476,71.060949 2.32141,-2.063474"
id="path223" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 202.99444,74.220646 6.96423,-3.030728"
id="path224" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 205.1224,79.314854 4.64282,-2.64383"
id="path225" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="m 208.34658,83.635256 1.41864,-0.838287"
id="path226" /><path
style="fill:none;stroke:#ffffff;stroke-width:0.365;stroke-dasharray:none;stroke-opacity:1"
d="M 29.690113,9.1690057 175.30265,0"
id="path227" /></g></svg>

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -412,4 +412,4 @@ ideas to absurd and humorous extremes. So, my final reason:
kind, welcoming and enthusiastic people, who dedicate much of their
time to spreading the joy of the field.
I ❤️ you.
I ❤️ the programming languages community.

View File

@@ -0,0 +1,190 @@
---
title: "Personal Software with the Help of LLMs"
date: 2026-04-05T16:03:00-07:00
tags: ["LLMs"]
series: ["LLM-Assisted Flashcard Generator"]
description: "In this post, I describe an inherently individual and outcome-focused class of LLM-generated software"
---
In [the previous post in this series]({{< relref "pdf_flashcards_llm" >}}),
I wrote about a little utility I created for detecting underlined words
in a book and creating vocabulary study material for them.
Like I mentioned earlier, this was one of my earliest experiences with
LLM-driven development, and I think it shaped my outlook on the technology
quite a bit. For me, the bottom line is this: _with LLMs, I was able to
rapidly solve a problem that was holding me back in another area of my life_.
My goal was never to "produce software", but to "acquire vocabulary",
and, viewed from this perspective, I think the experience has been a
colossal success.
As someone who works on software, I am always reminded that end-users rarely
care about the technology as much as we technologists; they care about
having their problems solved. I find taking that perspective to be challenging
(though valuable) because software is my craft, and because in thinking
about the solution, I have to think about the elements that bring it to life.
With LLMs, I was able --- allowed? --- to view things more from the
end-user perspective. I didn't know, and didn't need to know, the API
for `PyMuPDF`, `argostranslate`, or `spaCy`. I didn't need to understand
the PDF format. I could move one step away from the nitty-gritty and focus
on the 'why' and the 'what', on the challenge of what I wanted to accomplish.
I wrestled with the inherent complexity and
avoided altogether the unrelated difficulties that merely happened to be
there (downloading language modules; learning translation APIs; etc.)
By enabling me to do this, the LLM let me make rapid progress, and to produce
solutions to problems I would've previously deemed "too hard" or "too tedious".
This did, however, markedly reduce the care with which I was examining
the output. I don't think I've _ever_ read the code that produces the
pretty colored boxes in my program's debug output. This shift, I think,
has been a divisive element of AI discourse in technical communities.
I think that this has to do, at least in part, with different views
on code as a medium.
#### The Builders and the Craftsmen
There are two perspectives through which one may view software:
as a craft in and of itself, and as a means to some end.
My flashcard extractor can be viewed in vastly different ways when faced
from these two perspectives. In terms of craft, I think that it is at best
mediocre; most of the code is generated, slightly verbose and somewhat
tedious. The codebase is far from inspiring, and if I had written it by hand,
I would not be particularly proud of it. In terms of product, though,
I think it tells an exciting story: here I am, reading Camus again, because
I was able to improve the workflow around said reading. In a day, I was able
to achieve what I couldn't muster in a year or two on my own.
The truth is, the "builder vs. craftsman" distinction is a simplifying one,
another in the long line of "us vs. them" classifications. Any one person is
capable of being any combination of these two camps at any given time. Indeed,
different sorts of software demand to be viewed through different lenses.
I will _still_ treat work on my long-term projects as a craft, because
I will come back to it again and again, and because our craft has evolved
to engender stability and maintainability.
However, I am more than happy to settle for 'underwhelming' when it means an
individual need of mine can be addressed in record time. I think this
gives rise to a new sort of software: highly individual, explicitly
non-robust, and treated differently from software crafted with
deliberate thought and foresight.
#### Personal Software
I think as time goes on, I am becoming more and more convinced by the idea
of "personal software". One might argue that much of the complexity in many
pieces of software is driven by the need of that software to accommodate
the diverse needs of many users. Still, software remains somewhat inflexible and
unable to accommodate individual needs. Features or uses that demand
changes at the software level move at a slower pace: finite developer time
needs to be spent analyzing what users need, determining the costs of this new
functionality, choosing which of the many possible requests to fulfill.
On the other hand, software that enables the users to build their customizations
for themselves, by exposing numerous configuration options and abstractions,
becomes, over time, very complicated to grasp.
Now, suppose that the complexity of such software scales superlinearly with
the number of features it provides. Suppose also that individual users
leverage only a small subset of the software's functionality. From these
assumptions it would follow that individual programs, made to serve a single
user's need, would be significantly less complicated than the "whole".
By definition, these programs would also be better tailored to the users'
needs. With LLMs, we're getting to a future where this might be possible.
I think that my flashcard generator is an early instance of such software.
It doesn't worry about various book formats, or various languages, or
various page layouts. The heuristic was tweaked to fit my use case, and
now works 100% of the time. I understand the software in its entirety.
I thought about sharing it --- and, in a way, I did, since it's
[open source](https://dev.danilafe.com/DanilaFe/vocab-builder) --- but realized
that outside of the constraints of my own problem, it likely will not be
of that much use. I _could_ experiment with more varied constraints, but
that would turn it back into the sort of software I discussed above:
general, robust, and complex.
Today, I think that there is a whole class of software that is amenable to
being "personal". My flashcard generator is one such piece of software;
I imagine file-organization (as served by many "bulk rename and move" pieces
of software out there), video wrangling (possible today with `ffmpeg`'s
myriad of flags and switches), and data visualization to be other
instances of problems in that class. I am merely intuiting here, but
if I had to give a rough heuristic, it would be problems that:
* __fulfill a short-frequency need__, because availability, deployment,
etc. significantly raises the bar for quality.
* e.g., I collect flashcards once every two weeks;
I organize my filesystem once a month; I don't spend nearly enough money
to want to regenerate cash flow charts very often
* __have an "answer" that's relatively easy to assess__, because
LLMs are not perfect and iteration must be possible and easy.
* e.g., I can see that all the underlined words are listed in my web app;
I know that my files are in the right folders, named appropriately,
by inspection; my charts seem to track with reality
* __have a relatively complex technical implementation__, because
why would you bother invoking an LLM if you can "just" click a button somewhere?
* e.g., extracting data from PDFs requires some wrangling;
bulk-renaming files requires some tedious and possibly case-specific
pattern matching; cash flow between N accounts requires some graph
analysis
* __have relatively low stakes__, again, because LLMs are not perfect,
and nor is (necessarily) one's understanding of the problem.
* e.g., it's OK if I miss some words I underlined; my cash flow
charts only give me an impression of my spending;
* I recognize that moving files is a potentially destructive operation.
I dream of a world in which, to make use of my hardware, I just _ask_,
and don't worry much about languages, frameworks, or sharing my solution
with others --- that last one because they can just ask as well.
#### The Unfair Advantage of Being Technical
I recognize that my success described here did not come for free. There
were numerous parts of the process where my software background helped
me get the most out of Codex.
For one thing, writing software trains us to think precisely about problems.
We learn to state exactly what we want, to decompose tasks into steps,
and to intuit the exact size of these steps; to know what's hard and what's
easy for the machine. When working with an LLM, these skills make it possible
to hit the ground running, to know what to ask and to help pluck out a particular
solution from the space of various approaches. I think that this greatly
accelerates the effectiveness of using LLMs compared to non-technical experts.
For another, the boundary between 'manual' and 'automatic' is not always consistent.
Though I didn't touch any of the `PyMuPDF` code, I did need to look fairly
closely at the logic that classified my squiggles as "underlines" and found
associated words. It was not enough to treat LLM-generated code as a black box.
Another advantage software folks have when leveraging LLMs is the established
rigor of software development. LLMs can and do make mistakes, but so do people.
Our field has been built around reducing these mistakes' impact and frequency.
Knowing to use version control helps turn the pathological downward spiral
of accumulating incorrect tweaks into monotonic, step-wise improvements.
Knowing how to construct a test suite and thinking about edge cases can
provide an agent LLM the grounding it needs to iterate rapidly and safely.
In this way, I think the dream of personal software is far from being realized
for the general public. Without the foundation of experience and rigor,
LLM-driven development can easily devolve into a frustrating and endless
back-and-forth, or worse, successfully build software that is subtly and
convincingly wrong.
#### The Shoulders of Giants
The only reason all of this was possible is that the authors of `PyMuPDF`,
`genanki`, `spaCy`, and `argos-translate` made them available for me to use from
my code. These libraries provided the bulk of the functionality that Codex and I
were able to glue into a final product. It would be a mistake to forget this,
and to confuse the sustained, thoughtful efforts of the people behind these
projects for the one-off, hyper-specific software I've been talking about.
We need these packages, and others like them, to provide a foundation for the
things we build. They bring stability, reuse, and the sort of cohesion that
is not possible through an amalgamation of home-grown personal scripts.
In my view, something like `spaCy` is to my flashcard script as a brick is to
grout. There is a fundamental difference.
I don't know how LLMs will integrate into the future of large-scale software
development. The discipline becomes something else entirely when the
constraints of "personal software" I floated above cease to apply. Though
LLMs can still enable doing what was previously too difficult, tedious,
or time consuming (like my little 'underline visualizer'), it remains
to be seen how to integrate this new ease into the software lifecycle
without threatening its future.

View File

@@ -0,0 +1,239 @@
---
title: "Generating Flashcards from PDF Underlines"
date: 2026-04-05T16:02:00-07:00
tags: ["LLMs", "Python"]
series: ["LLM-Assisted Flashcard Generator"]
description: "In this post, I describe my personal PDF-to-vocab-flashcards pipeline"
---
__TL;DR__: I, with the help of ChatGPT, wrote [a program](https://dev.danilafe.com/DanilaFe/vocab-builder)
that helps me extract vocabulary words from PDFs. Scroll just a bit further down
to see what it looks like.
Sometime in 2020 or 2021, during the COVID-19 pandemic, I overheard from some
source that Albert Camus, in his book _La Peste_ (The Plague), had quite
accurately described the experience that many of us were going through
at the time. Having studied French for several years, I decided that the
best way to see for myself what _La Peste_ is all about was to read it
in its original, untranslated form.
I made good progress, but I certainly did not know every word. At the surface,
I was faced with two choices: guess the words from context and read without
stopping, or interrupt my reading to look up unfamiliar terms. The former
seemed unfortunate since it stunted my ability to acquire new vocabulary;
the latter was unpleasant, making me constantly break from the prose
(and the e-ink screen of my tablet) to consult a dictionary.
In the end, I decided to underline the words, and come back to them later.
However, even then, the task is fairly arduous. For one, words I don't recognize
aren't always in their canonical form (they can be conjugated, plural, compound,
and more): I have to spend some time deciphering what I should add to a
flashcard. For another, I had to bounce between a PDF of my book
(from where, fortunately, I can copy-paste) and my computer. Often, a word
confused the translation software out of context, so I had to copy more of the
surrounding text. Finally, I learned that given these limitations, the pace of
my reading far exceeds the rate of my translation. This led me to underline
fewer words.
I thought,
> Perhaps I can just have some software automatically extract the underlined
> portions of the words, find the canonical forms, and generate flashcards?
Even thinking this thought was a mistake. From then on, as I read and went
about underlining my words, I thought about how much manual effort I will
be taking on that could be automated. However, I didn't know how to start
the automation. In the end, I switched to reading books in English.
Then, LLMs got good at writing code. With the help of
Codex, I finally got the tools that I was dreaming about. Here's what it looks
like.
{{< figure src="./underlines.png" caption="Detected underlined words on a page" label="Detected underlined words on a page" >}}
{{< figure src="./result.png" caption="Auto-flashcard application" label="Auto-flashcard application" class="fullwide" >}}
This was my first foray into LLM-driven development. My commentary about that
experience (as if there isn't enough of such content out there!) will be
interleaved with the technical details.
### The Core Solution
The core idea has always been:
1. Find things that look like underlines
2. See which words they correspond to
3. Perform {{< sidenote "right" "lemmatization-node" "lemmatization" >}}
Lemmatization (<a href="https://en.wikipedia.org/wiki/Lemmatization">Wikipedia</a>) is the
process of turning non-canonical forms of words (like <code>am</code> (eng) /
<code>suis</code> (fr)) into their canonical form which might be found in the
dictionary (<code>to be</code> / <code>être</code>).
{{< /sidenote >}} and translate.
My initial direction was shaped by the impressive demonstrations of OCR
models, which could follow instructions at the same time as reading a document.
For these models, a prompt like "extract all the text in the red box"
constituted the entire targeted OCR pipeline. My hope was that a similar
prompt, "extract all underlined words", would be sufficient to accomplish
steps 1 and 2. However, I was never to find out: as it turns out,
OCR models are large and very expensive to run. In addition, the model
that I was looking at was specifically tailored for NVIDIA hardware which
I, with my MacBook, simply didn't have access to.
In the end, I came to the conclusion that a VLM is overkill for the problem
I'm tackling. This took me down the route of analyzing the PDFs. The
problem, of course, is that I know nothing of the Python landscape
of PDF analysis tools, and that I also know nothing about the PDF format
itself. This is where a Codex v1 came in. Codex opted (from its training
data, I presume) to use the [`PyMuPDF`](https://pymupdf.readthedocs.io) package.
It also guessed (correctly) that the PDFs exported by my tablet used
the 'drawings' part of the PDF spec to encode what I penned. I was instantly
able to see (on the console) the individual drawings.
The LLM also chose to approach the problem by treating each drawing as just
a "cloud of points", discarding the individual line segment data. This
seemed like a nice enough simplification, and it worked well in the long run.
#### Iterating on the Heuristic
The trouble with the LLM agent was that it had no good way of verifying
whether the lines it detected (and indeed, the words it considered underlined)
were actually lines (and underlined words). Its initial algorithm missed
many words, and misidentified others. I had to resort to visual inspection
to see what was being missed, and for the likely cause.
The exact process of the iteration is not particularly interesting. I'd
tweak a threshold, re-run the code, and see the new list of words.
I'd then cross-reference the list with the page in question, to see
if things were being over- or under-included. Rinse, repeat.
This got tedious fast. In some cases, letters or words I penned would get picked
up as underlines, and slightly diagonal strokes would get missed. I ended up
requesting Codex to generate a debugging utility that highlighted (in a box)
all the segments that it flagged, and the corresponding words. This
is the first picture I showed in the post. Here it is again:
{{< figure src="./underlines.png" caption="Detected underlined words on a page" label="Detected underlined words on a page" >}}
In the end, the rough algorithm was as follows:
1. __Identify all "cloud points" that are not too tall__. Lines that
vertically span too many lines of text are likely not underlines.
* The 'height threshold' ended up being larger than I anticipated:
turns out I don't draw very straight horizontal lines.
{{< figure src="tallmarks.png" caption="My angled underlines" label="My angled underlines" >}}
2. __Create a bounding box for the line,__ with some padding.
I don't draw the lines _directly_ underneath the text, but a bit below.
* Sometimes, I draw the line quite a bit below; the upward padding
had to be sizeable.
{{< figure src="lowmarks.png" caption="My too-low underlines" label="My too-low underlines" >}}
3. __Intersect `PyMuPDF` bounding boxes with the line__. Fortunately,
`PyMuPDF` provides word rectangles out of the box.
* I required the intersection to overlap with at least 60% of the word's
horizontal width, so accidental overlaps don't count.
{{< figure src="widemarks.png" caption="My too-wide underline hitting `Cela`" label="My too-wide underline hitting `Cela`" >}}
* The smallest underlines are roughly the same size as the biggest strokes
in my handwriting. The 60% requirement filtered out the latter, while
keeping the former.
{{< figure src="flaggedmarks.png" caption="Letters of a hand-writing word detected as lines" label="Letters of a hand-writing word detected as lines" >}}
4. __Reject underlines that overlap from the top__. Since, as I mentioned,
my underlines are often so low that they touch the next line.
#### Lemmatization and Translation
I don't recall now how I arrived at [`spaCy`](https://github.com/explosion/spaCy),
but that's what I ended up using for my lemmatization. There was only
one main catch: sometimes, instead of underlining words I didn't know,
I underlined whole phrases. Lemmatization did not work well in those
contexts; I had to specifically restrict my lemmatization to single-word
underlines, and to strip punctuation which occasionally got tacked on.
With lemmatization in hand, I moved on to the next step: translation.
I wanted my entire tool to work completely offline. As a result, I had to
search for "python offline translation", to learn about
[`argos-translate`](https://github.com/argosopentech/argos-translate).
Frankly, the translation piece is almost entirely uninteresting:
it boils down to invoking a single function. I might add that
`argos-translate` requires one to download language packages --- they
do not ship with the Python package. Codex knew to write a script to do
so, which saved a little bit of documentation-reading and typing.
The net result is a program that could produce:
```
Page 95: fougueuse -> fougueux -> fiery
```
Pretty good!
### Manual Intervention
That "pretty good" breaks down very fast. There are several points of failure:
the lemmatization can often get confused, and the offline translation
fails for some of the more flowery Camus language.
In the end, for somewhere on the order of 70% of the words I underlined,
the automatic translation was insufficient, and required small tweaks
(changing the tense of the lemma, adding "to" to infinitive English verbs, etc.)
I thought --- why not just make this interactive? Fortunately, there are
plenty of Flask applications in Codex's training dataset. In one shot,
it generated a little web application that enabled me to tweak the source word
and final translation. It also enabled me to throw away certain underlines.
This was useful when, across different sessions, I forgot and underlined
the same word, or when I underlined a word but later decided it was not worth
including in my studying. This application produced an Anki deck, using
the Python library [`genanki`](https://github.com/kerrickstaley/genanki).
Anki has a nice mechanism to de-duplicate decks, which meant that every
time I exported a new batch of words, I could add them to my running
collection.
Even then, however, cleaning up the auto-translation was not always easy.
The OCR copy of the book had strange idiosyncrasies: the letters 'fi' together
would OCR to '=' or '/'. Sometimes, I would underline a compound phrase
that spanned two lines; though I knew the individual words (and would be surprised
to find them in my list), I did not know their interaction.
In the end, I added (had Codex add) both a text-based context and a visual
capture of the word in question to the web application. This led to the final
version, whose screenshot I included above. Here it is again:
{{< figure src="./result.png" caption="Auto-flashcard application" label="Auto-flashcard application" class="fullwide" >}}
The net result was that, for many words, I could naively accept the
automatically-generated suggestion. For those where this was not possible,
in most cases I only had to tweak a few letters, which still saved me time.
Finally, I was able to automatically include the context of the word in
my flashcards, which often helps reinforce the translation and remember
the exact sense in which the word was used.
To this day, I haven't found a single word that was underlined and missed,
nor one that was mis-identified as underlined.
### Future Direction
In many ways, this software is more than good enough for my needs.
I add a new batch of vocabulary roughly every two weeks, during which time
I manually export a PDF of _La Peste_ from my tablet and plug it into
my software.
In my ideal world, I wouldn't have to do that. I would just underline some
words, and come back to my laptop a few days later to find a set of draft
flashcards for me to review and edit. In an even more ideal world, words
I underline get "magically" translated, and the translations appear somewhere
in the margins of my text (while also being placed in my list of flashcards).
I suspect LLMs --- local ones --- might be a decent alternative technology
to "conventional" translation. By automatically feeding them the context
and underlined portion, it might be possible to automatically get a more
robust translation and flashcard. I experimented with this briefly
early on, but did not have much success. Perhaps better prompting or newer
models would improve the outcomes.
That said, I think that those features are way beyond the 80:20 transition:
it would be much harder for me to get to that point, and the benefit would
be relatively small. Today, I'm happy to stick with what I already have.
In the [next part of this series]({{< relref "llm_personal_software" >}}),
I will talk more about how this project influenced my views on LLMs.

View File

@@ -0,0 +1,11 @@
+++
title = "LLM-Assisted Flashcard Generator"
summary = """
In this series, I write up a little program I wrote for myself,
which detects vocabulary words I underline in a book and turns them
into flashcards. I view this through the lens of a first foray into
development that heavily relies on LLMs.
"""
status = "complete"
donotunique = true
+++

View File

@@ -0,0 +1,44 @@
---
title: "On Spiders"
date: 2026-03-22T01:03:00-05:00
type: onspiders
---
```
Whenever I stay still,
I feel the spiders weave their webs around me.
Their tiny legs entangle me in silk,
traverse my body as they seem to proudly
inspect their work and into darkness sink.
There're times I see the spiders' threads on others,
They shimmer gently in the purple dusk,
Or sway with zephyrs in the hair of lovers,
stuck.
To listen to the webs is to hear echoes
of social butterflies ensnared within
arachnid galleries of human ethos,
whose pieces come and go upon a whim.
Their curators are blind and want for stories,
delivered as vibrations through the links,
for righteous anger, anguish, joy and worries,
superimposing frequencies that mix,
reveberating through the sprawling complex
of radial lines and newly captured prey,
resisting first but ultimately hopeless,
these signals grow in urgency and they
form standing waves that make the victims sing
and even from myself bring out a resonant ring.
```
<!--
the signals cause a standing wave
intensify with every passing moment
arachnid galleries of human drama,
temples to the human mind
(traverse my body all while I sleep soundly,)
They shimmer gently in the setting sun, -->

View File

@@ -68,13 +68,12 @@ files.each do |file|
t.replace(rendered)
end
found_any ||= document.at('meta[name="needs-latex"]')
# If we didn't find any mathematical equations, no need to include KaTeX CSS.
# Disabled here because Bergamot technically doesn't require math blocks
# on the page but does need the CSS.
#
# unless found_any
# document.css('link[href$="katex.css"], link[href$="katex.min.css"]').each(&:remove)
# end
unless found_any
document.css('link[href$="katex.css"], link[href$="katex.min.css"]').each(&:remove)
end
File.write(file, document.to_html(encoding: 'UTF-8'))
end

View File

@@ -0,0 +1,25 @@
{{- /* Note: changing the baseof template because the title, tags, etc. of a regular post are still valid. */ -}}
<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
{{- partial "head.html" . -}}
<body>
{{ $writingcss := resources.Get "scss/writing.scss" | css.Sass | resources.Minify }}
<link rel="stylesheet" href="{{ $writingcss.Permalink }}">
{{ $spidercss := resources.Get "scss/onspiders.scss" | css.Sass | resources.Minify }}
<link rel="stylesheet" href="{{ $spidercss.Permalink }}">
<svg class="spiderweb" viewBox="0 0 197.21727 106.16592">
<use href="{{ (resources.Get "svg/spiderweb.svg").Permalink }}#mainlayer"></use>
</svg>
{{- partial "header.html" . -}}
<div class="container"><hr class="header-divider"></div>
<main class="container">
{{- if .Draft -}}
{{- partial "warning.html" (i18n "postDraft") -}}
{{- end -}}
{{- block "main" . }}{{- end }}
</main>
{{- block "after" . }}{{- end }}
</body>
</html>

View File

@@ -3,6 +3,8 @@
<html lang="{{ .Site.Language.Lang }}">
{{- partial "head.html" . -}}
<body>
{{ $writingcss := resources.Get "scss/writing.scss" | css.Sass | resources.Minify }}
<link rel="stylesheet" href="{{ $writingcss.Permalink }}">
{{ $voidcss := resources.Get "scss/thevoid.scss" | css.Sass | resources.Minify }}
<link rel="stylesheet" href="{{ $voidcss.Permalink }}">
{{- partial "header.html" . -}}