My personal website, in gleam+lustre!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

CV in two languages


Signed-off-by: MLC Bloeiman <mar@strawmelonjuice.com>

+1945 -108
assets/img/GitHub_Lockup_Black.svg assets/svg/GitHub_Lockup_Black.svg
assets/img/tangled.svg assets/svg/dolly.svg
+125
assets/svg/icon_loc.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <svg 3 + id="a" 4 + width="800px" 5 + height="800px" 6 + version="1.1" 7 + viewBox="0 0 512 512" 8 + sodipodi:docname="icon_loc.svg" 9 + inkscape:version="1.4.3 (0d15f75042, 2025-12-25)" 10 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns="http://www.w3.org/2000/svg" 13 + xmlns:svg="http://www.w3.org/2000/svg"> 14 + <defs 15 + id="defs1" /> 16 + <sodipodi:namedview 17 + id="namedview1" 18 + pagecolor="#ffffff" 19 + bordercolor="#000000" 20 + borderopacity="0.25" 21 + inkscape:showpageshadow="2" 22 + inkscape:pageopacity="0.0" 23 + inkscape:pagecheckerboard="0" 24 + inkscape:deskcolor="#d1d1d1" 25 + inkscape:zoom="0.7731284" 26 + inkscape:cx="265.8032" 27 + inkscape:cy="321.42138" 28 + inkscape:window-width="1920" 29 + inkscape:window-height="1166" 30 + inkscape:window-x="0" 31 + inkscape:window-y="0" 32 + inkscape:window-maximized="1" 33 + inkscape:current-layer="a" /> 34 + <style 35 + type="text/css" 36 + id="style1">.st0{fill:#000000;}</style> 37 + <path 38 + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.506964px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 39 + d="M 256.78398,462.36571 388.39196,228.2565" 40 + id="path1" 41 + inkscape:connector-type="polyline" 42 + inkscape:connector-curvature="0" /> 43 + <path 44 + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.506964px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 45 + d="M 256.85976,461.70622 125.25178,227.59701" 46 + id="path1-5" 47 + inkscape:connector-type="polyline" 48 + inkscape:connector-curvature="0" /> 49 + <ellipse 50 + style="fill:none;stroke:#000000;stroke-width:0.6822" 51 + id="path2" 52 + ry="139.86961" 53 + rx="158.95628" 54 + cy="148.28415" 55 + cx="256.77777" /> 56 + <path 57 + style="fill:none;stroke:#000000;stroke-width:0.569393" 58 + id="path3" 59 + sodipodi:type="arc" 60 + sodipodi:cx="258.79782" 61 + sodipodi:cy="148.28879" 62 + sodipodi:rx="120.62109" 63 + sodipodi:ry="107.47599" 64 + sodipodi:start="0" 65 + sodipodi:end="6.275449" 66 + sodipodi:arc-type="arc" 67 + d="M 379.41891,148.28879 A 120.62109,107.47599 0 0 1 259.03111,255.76458 120.62109,107.47599 0 0 1 138.17763,148.70452 120.62109,107.47599 0 0 1 258.09795,40.814607 120.62109,107.47599 0 0 1 379.41531,147.45733" 68 + sodipodi:open="true" /> 69 + <path 70 + style="fill:none;stroke:#000000;stroke-width:0.64" 71 + d="m 378.95354,146.28945 c 0,0.40551 -0.40551,1.21654 0,1.21654 0.40551,0 0,-1.62205 0,-1.21654 0,1.62206 0,3.24412 0,4.86617" 72 + id="path4" /> 73 + <path 74 + style="fill:#000000;stroke:#000000;stroke-width:0.699163" 75 + d="m 301.68899,542.48755 c -54.79692,-97.47887 -99.73543,-177.53487 -99.86335,-177.90222 -0.12792,-0.36736 5.06337,4.62034 11.53619,11.08376 13.6409,13.62109 22.87055,21.20866 37.9874,31.22891 12.76926,8.46413 24.51825,14.95858 37.26355,20.59804 75.32833,33.33079 163.91802,31.23654 236.84628,-5.59903 26.70733,-13.48969 52.7522,-33.16537 70.65564,-53.37705 3.57801,-4.03931 6.61501,-7.23467 6.74888,-7.1008 0.28386,0.28386 -199.62804,356.21154 -200.77377,357.46216 -0.57558,0.62829 -25.92863,-43.91437 -100.40082,-176.39377 z" 76 + id="path5" 77 + transform="scale(0.64)" /> 78 + <path 79 + style="fill:#000000;stroke:#000000;stroke-width:0.322679;stroke-dasharray:none" 80 + d="m 247.6534,246.84562 c -22.5816,-1.88095 -43.20282,-8.62104 -61.2394,-20.01623 -8.6783,-5.4828 -19.06897,-14.42897 -25.08132,-21.59456 -6.26926,-7.47178 -12.73679,-18.20103 -15.96491,-26.48485 -8.49589,-21.8017 -7.48737,-45.7335 2.83368,-67.24147 7.8476,-16.353605 21.57487,-31.233804 38.88552,-42.151475 15.91816,-10.039439 34.83694,-16.535783 55.51922,-19.064234 3.72633,-0.455548 7.06627,-0.576335 15.98283,-0.578007 11.80668,-0.0023 14.36554,0.175411 23.21713,1.611639 24.83887,4.030269 47.71699,14.771209 64.88366,30.461923 16.62416,15.194859 26.64396,33.129444 30.18326,54.025464 0.83665,4.93946 1.02982,16.49045 0.36587,21.87616 -1.7955,14.56417 -6.86169,27.73478 -15.31924,39.82551 -4.49972,6.43271 -8.0578,10.55082 -13.81704,15.99186 -19.18393,18.12399 -44.55678,29.44654 -73.69908,32.88797 -4.96465,0.58628 -21.72962,0.86848 -26.75018,0.4503 z m 23.24857,-22.39739 c 12.09071,-1.19889 22.56443,-4.06694 33.61658,-9.20531 9.47135,-4.40344 16.50869,-9.16668 23.89935,-16.17635 11.59272,-10.99507 18.98609,-24.09603 21.70466,-38.46033 7.88691,-41.67252 -25.24144,-80.684089 -74.62993,-87.883386 -5.88675,-0.858103 -18.66756,-1.181391 -24.88464,-0.629453 -18.1804,1.614015 -35.40379,7.82227 -49.29044,17.766983 -24.85758,17.801386 -36.43563,45.029656 -30.68612,72.165026 4.73258,22.33591 21.29376,41.86246 45.10393,53.17998 4.88483,2.32189 6.41348,2.92621 11.89747,4.70357 7.01802,2.27453 17.32352,4.31907 24.22657,4.80638 4.40964,0.3113 14.65941,0.16753 19.04257,-0.26711 z" 81 + id="path6" /> 82 + <path 83 + style="fill:#000000;stroke:#000000;stroke-width:0.4416;stroke-dasharray:none" 84 + d="m 123.50022,221.49496 c 53.69575,97.99474 211.20327,97.99474 211.20327,97.99474 l 71.14687,-121.2629 -155.27021,77.85883 z" 85 + id="path14" /> 86 + <path 87 + style="fill:#000000;stroke:#000000;stroke-width:1.0123;stroke-dasharray:none" 88 + d="m 291.95229,387.32083 c -54.86943,-23.63506 -100.05886,-42.69215 -100.42097,-42.34909 -0.36211,0.34307 -2.88682,-2.86345 -5.61048,-7.1256 -13.54331,-21.19334 -24.70637,-49.64532 -28.87447,-73.5941 -2.89555,-16.63702 -2.92345,-48.1115 -0.0572,-64.54764 4.16836,-23.90316 15.60768,-53.56806 28.30052,-73.39005 C 236.11231,46.946275 338.16067,2.9322972 438.66198,17.033573 534.0034,30.410869 610.95066,89.945605 638.21362,171.42834 c 7.79805,23.30658 9.9311,38.78392 9.12923,66.24136 -0.73413,25.13787 -2.43856,34.76541 -10.21912,57.72329 l -4.64653,13.71041 -51.05268,25.53444 c -28.07898,14.04394 -51.36354,25.53445 -51.74347,25.53445 -0.37993,0 5.25357,-5.73038 12.5189,-12.73416 34.91095,-33.65425 51.27272,-70.76127 51.22147,-116.16566 C 593.32329,144.32696 521.9235,73.553938 424.36647,63.701533 377.44089,58.962462 328.69781,71.016397 289.4185,97.07353 c -15.35699,10.18753 -38.74943,33.86392 -48.86512,49.45827 -25.54053,39.37331 -32.40713,87.32534 -18.718,130.7148 27.62814,87.57098 126.88025,139.52317 227.03114,118.83644 6.2564,-1.29229 11.6045,-2.12043 11.88464,-1.84027 0.28014,0.28014 -15.13842,8.5062 -34.26346,18.28012 l -34.77282,17.77077 z" 89 + id="path15" 90 + transform="scale(0.64)" /> 91 + <path 92 + style="fill:#000000;stroke:#000000;stroke-width:1.0123;stroke-dasharray:none" 93 + d="" 94 + id="path16" 95 + transform="scale(0.64)" /> 96 + <path 97 + style="fill:#000000;stroke:#000000;stroke-width:1.0123;stroke-dasharray:none" 98 + d="m 372.70999,395.99113 c -59.13703,-9.19022 -113.22885,-46.00346 -137.45968,-93.55085 -5.55873,-10.90771 -4.51916,-12.45415 1.36422,-2.02938 15.0432,26.6551 42.86098,51.03402 75.79933,66.42895 29.83775,13.94575 49.16227,18.28758 85.90295,19.30066 23.36584,0.64429 29.52527,0.31954 44.27945,-2.33462 55.26069,-9.94094 102.22734,-39.66035 127.91731,-80.94311 9.7563,-15.67796 10.30122,-12.80258 0.74052,3.90739 -17.91672,31.31437 -43.89089,54.75623 -79.57164,71.81389 -36.19812,17.305 -79.17102,23.59244 -118.97246,17.40707 z" 99 + id="path17" 100 + transform="scale(0.64)" /> 101 + <path 102 + style="fill:#000000;stroke:#000000;stroke-width:1.0123;stroke-dasharray:none" 103 + d="m 387.21919,396.60264 c -34.41291,-2.28548 -70.5289,-15.7454 -100.87478,-37.59461 -11.52822,-8.30038 -37.67148,-34.14763 -37.67148,-37.24494 0,-0.89141 5.91842,4.08464 13.15205,11.05787 15.05819,14.51614 32.32712,26.29477 51.80873,35.3372 29.65951,13.76654 44.64195,16.88323 86.1502,17.92124 28.16603,0.70435 33.12323,0.46223 46.3084,-2.26185 41.71998,-8.61939 73.91347,-25.12147 101.50161,-52.02877 7.46386,-7.27966 13.57065,-12.55467 13.57065,-11.72225 0,2.87708 -18.5896,23.26361 -28.71571,31.49145 -40.90127,33.23381 -90.6647,48.66852 -145.22967,45.04466 z" 104 + id="path18" 105 + transform="scale(0.64)" /> 106 + <path 107 + style="fill:#000000;stroke:#000000;stroke-width:0.258216;stroke-dasharray:none" 108 + d="m 455.71981,398.14098 c 0.12289,-0.12289 1.45607,-0.9228 2.96263,-1.77758 3.19564,-1.81312 3.49291,-2.59108 1.09437,-2.86393 l -1.64482,-0.18712 3.32884,-0.90188 c 1.83086,-0.49603 6.24516,-1.88535 9.80956,-3.08737 3.5644,-1.20202 6.59564,-2.17639 6.73608,-2.16526 0.33815,0.0268 -21.72409,11.16024 -22.16706,11.18635 -0.18867,0.0111 -0.24249,-0.0803 -0.1196,-0.20321 z" 109 + id="path19" 110 + transform="scale(0.64)" /> 111 + <path 112 + style="fill:#000000;stroke:#000000;stroke-width:0.258216;stroke-dasharray:none" 113 + d="m 579.77587,285.06725 c -0.0789,-0.36019 0.10836,-1.2022 0.4161,-1.87113 0.43776,-0.95159 0.78872,-1.21624 1.61288,-1.21624 0.8666,0 1.05337,0.16157 1.05337,0.91121 0,1.82709 -0.65054,2.83106 -1.83443,2.83106 -0.74779,0 -1.15082,-0.21151 -1.24792,-0.6549 z" 114 + id="path20" 115 + transform="scale(0.64)" /> 116 + <path 117 + style="fill:#000000;stroke:#000000;stroke-width:0.258216;stroke-dasharray:none" 118 + d="m 522.23348,364.331 c 0,-0.81903 1.32078,-1.89236 2.32863,-1.89236 1.18698,0 0.75174,1.31701 -0.6573,1.98894 -1.65149,0.78754 -1.67133,0.78639 -1.67133,-0.0966 z" 119 + id="path21" 120 + transform="scale(0.64)" /> 121 + <path 122 + style="fill:#000000;stroke:#000000;stroke-width:0.4416;stroke-dasharray:none" 123 + d="m 146.79321,181.29589 c 168.74946,93.47089 170.63142,31.99339 170.63142,31.99339 l 70.88733,-84.06107 19.44696,68.37804 -217.05321,131.11017 -85.94303,-144.91125 62.10481,-138.638028 117.93643,-17.565 84.06107,39.52125 25.09286,82.806428 -28.85679,62.73214 -42.03053,51.44036 L 289.19517,230.22696 353.18196,193.215 367.61035,127.97357 351.92732,97.862138 317.42463,81.551782 281.03999,68.378035 h -46.42178 l -45.79447,20.701607 -23.83821,27.602148 -7.52785,32.62071 z" 124 + id="path22" /> 125 + </svg>
+2
assets/svg/icon_mail.svg
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <svg id="a" width="800px" height="800px" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><style type="text/css">.st0{fill:#000000;}</style><path class="st0" d="m0 64v384h512v-384h-512zm264 203c-2 1.69-4.95 2.66-8.1 2.66-3.15 0-6.1-.964-8.08-2.64l-200-155h416l-200 155zm-169-72.5 69.5 59-117 115v-208l47.1 34.3zm104 88.3 16.4 13.9c10.8 9.22 25.2 14.3 40.4 14.3 15.2 0 29.6-5.07 40.4-14.3l16.3-13.8 119 117h-352l119-117zm148-29.4 117-93.3v208l-117-115z"/></svg>
+2
assets/svg/icon_phone.svg
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <svg id="a" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><style type="text/css">.st0{fill:#000000;}</style><path class="st0" d="m33 235c-.008-.016-.016-.024-.024-.04l-.016-.017.04.057z"/><path class="st0" d="m64.7 225c.024-.008.056-.017.08-.025l.373-.137-.453.162z"/><path class="st0" d="m501 141c-7.1-9.82-17.2-18.7-29.9-26.5v-.007c-.032-.017-.064-.041-.097-.057-.057-.032-.098-.073-.154-.105l-.008.008c-19.3-12.4-50.2-26.1-87.2-37.2-37.1-11-80.1-19.2-122-19.2-1.62 0-3.27.008-4.92.04-1.63-.025-3.27-.04-4.91-.04-42.4.016-85.3 8.18-122 19.2-37 11.1-67.9 24.8-87.2 37.2v-.008c-.025.016-.049.032-.081.049-.056.04-.122.073-.178.114l.008.007c-12.8 7.88-22.9 16.7-30 26.5-7.14 9.84-11.2 20.9-11.2 32.2-.041 12.6 5.23 24.7 14.2 34.7 6.37 9.01 12.6 18.1 18.8 27.2l.065.088c6.53 9.59 17.3 14.7 28.1 14.7 3.92 0 7.88-.68 11.7-2.01v.016l-.234.089.234-.089.049.865-.032-.865.251-.089c3.19-1.15 6.42-2.1 9.62-3.19l-.582 1.7-27.8 59.2c-10.4 22.2-15.8 46.5-15.8 71v15.6c.008 34.1 27.6 61.7 61.7 61.7h311c34.1-.008 61.7-27.6 61.7-61.7v-15.6c0-24.5-5.4-48.8-15.8-71l-27.8-59.2-.582-1.71c3.2 1.09 6.42 2.04 9.6 3.19 3.92 1.41 7.98 2.09 12 2.09 10.9.016 21.6-5.1 28.1-14.7l.032-.049c6.15-9.15 12.4-18.2 18.8-27.2 8.98-9.99 14.2-22.1 14.2-34.7.017-11.3-4.09-22.4-11.2-32.2zm-50 84c-1.17 0-2.37-.194-3.54-.614-19.2-6.94-38.7-12.8-58.4-17.7-2.21-.542-4.05-1.76-5.33-3.35-1.28-1.59-1.95-3.42-1.95-5.39.049-5.15.073-10.3.073-15.5 0-2.04 0-4.08-.008-6.13v.041c0-7.65-2.86-14.8-7.48-20.3-4.19-5.04-9.9-8.8-16.4-10.9-.251-.122-.429-.186-.599-.259-1.12-.437-1.49-.526-2.24-.752-2.51-.736-7.04-1.91-13.4-3.37-19-4.33-52.7-10.9-82-11-1.16 0-2.32.017-3.48.041-1.16-.024-2.31-.041-3.47-.041-21.5.017-45.2 3.6-64 7.19-9.35 1.8-17.4 3.61-23.2 5.01-2.9.704-5.22 1.31-6.92 1.78-.866.243-1.55.436-2.22.655l-1.23.428c-.178.073-.38.154-.647.275-6.58 2.11-12.3 5.88-16.5 10.9-4.61 5.54-7.46 12.7-7.47 20.3v-.032c-.008 1.8-.008 3.61-.008 5.41 0 5.39.024 10.8.081 16.2-.008 1.96-.68 3.81-1.96 5.41-1.29 1.58-3.13 2.81-5.33 3.35-19.7 4.83-39.2 10.7-58.4 17.6l-.064.025h-.016c-1.18.42-2.37.623-3.53.623-3.23-.017-6.01-1.48-7.57-3.76-6.34-9.42-12.8-18.8-19.4-28.1l-.468-.672-.55-.582c-6.1-6.68-8.35-12.7-8.39-18.8.016-5.28 1.83-11.1 6.53-17.6 4.67-6.5 12.3-13.5 23-20.1l.145-.088.114-.073c16.1-10.5 45.9-23.9 81.1-34.4 35.2-10.5 76.2-18.2 115-18.2 1.57 0 3.15.016 4.72.041h.421c1.54-.032 3.11-.041 4.69-.041 39.1-.016 80.1 7.68 115 18.2 35.2 10.5 65 23.9 81.1 34.4l.146.088.105.065c10.8 6.59 18.4 13.6 23.1 20.1 4.7 6.54 6.51 12.4 6.52 17.6-.04 6.02-2.28 12.1-8.38 18.8l-.55.591-.468.655c-6.58 9.28-13.1 18.6-19.4 28.1l.032-.049c-1.56 2.32-4.37 3.79-7.6 3.8zm-387 152c0-20.9 4.59-41.6 13.5-60.5l28.3-60.5 6.92-20.1c5.64-1.57 11.2-3.24 16.9-4.63 7.56-1.85 14.1-6.11 18.7-11.9 4.57-5.64 7.34-12.9 7.44-20.6h.032l.008-.324v-.323c-.057-5.31-.081-10.6-.081-16 0-1.78 0-3.56.008-5.33v-.032c0-1.42.518-2.97 1.76-4.46 1.22-1.48 3.11-2.78 5.56-3.42l9.29-2.41v-.105c7.3-1.76 18.5-4.23 31.2-6.4 15.9-2.73 34.1-4.97 49.5-4.97 1.08 0 2.14.016 3.18.032l.299.008h.258c1.05-.024 2.13-.04 3.21-.04 18.7-.016 41.5 3.31 59.2 6.74 8.61 1.66 16 3.32 21.4 4.62v.097l9.29 2.42c2.45.639 4.36 1.95 5.59 3.44 1.23 1.5 1.75 3.03 1.75 4.45v.04c.008 2.01.008 4.04.008 6.05 0 5.09-.024 10.2-.073 15.2v.218c-.008 7.88 2.82 15.3 7.47 21 4.66 5.77 11.2 10 18.7 11.9 5.67 1.39 11.3 3.06 16.9 4.63l6.92 20.1 28.3 60.5c8.88 18.9 13.5 39.6 13.5 60.5v15.6c-.008 10.2-4.11 19.4-10.8 26.1-6.71 6.7-15.9 10.8-26.1 10.8h-311c-10.2-.008-19.4-4.11-26.1-10.8-6.7-6.71-10.8-15.9-10.8-26.1v-15.6zm9.46-129v-.025c.073-.024.146-.032.219-.056l-.219.081z"/><rect class="st0" x="178" y="345" width="38.9" height="38.9"/><rect class="st0" x="178" y="292" width="38.9" height="38.9"/><rect class="st0" x="178" y="238" width="38.9" height="38.9"/><rect class="st0" x="237" y="345" width="38.9" height="38.9"/><rect class="st0" x="237" y="292" width="38.9" height="38.9"/><rect class="st0" x="237" y="238" width="38.9" height="38.9"/><rect class="st0" x="295" y="345" width="38.9" height="38.9"/><rect class="st0" x="295" y="292" width="38.9" height="38.9"/><rect class="st0" x="295" y="238" width="38.9" height="38.9"/></svg>
+2
assets/svg/icon_www.svg
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <svg id="a" width="800px" height="800px" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><style type="text/css">.st0{fill:#000000;}</style><path class="st0" d="m256 .006c-141 .007-256 115-256 256 .012 141 115 256 256 256 141-.008 256-115 256-256-.008-141-115-256-256-256zm-158 97.6c23.3-23.3 51.8-41.5 83.5-52.7-15.2 18.4-27.8 41.9-37.8 69.1h-60.5c4.68-5.7 9.62-11.2 14.8-16.4zm-32 40.4h70c-9.01 31.9-14.4 67.8-15.4 106h-87.9c2.05-38.8 14-74.9 33.3-106zm0 236c-19.3-31.1-31.2-67.2-33.3-106h88c.961 38.1 6.21 74.1 15.2 106h-69.9zm32 40.4c-5.21-5.21-10.2-10.7-14.8-16.4h60.6c4.28 11.8 9.02 22.9 14.3 33.2 6.95 13.4 14.8 25.5 23.3 35.9-31.7-11.2-60.1-29.4-83.4-52.7zm146 65.3c-3.75-.196-7.47-.477-11.2-.86-5.89-2.64-11.7-6.25-17.5-11-17.6-14.4-34-38.7-46.4-69.8h75.1v81.7zm0-106h-83.4c-9.48-31.2-15.3-67.3-16.3-106h99.8v106zm0-130h-99.7c1.01-38.7 6.88-74.8 16.4-106h83.3v106zm0-130h-75c3.11-7.79 6.37-15.3 9.93-22.2 10.6-20.6 23.2-36.9 36.4-47.7 5.78-4.71 11.6-8.32 17.5-11 3.7-.382 7.42-.664 11.2-.859v81.7zm202 24c19.3 31.1 31.2 67.2 33.3 106h-88c-.961-38.1-6.21-74.1-15.2-106h69.9zm-32-40.4c5.21 5.21 10.2 10.7 14.8 16.4h-60.6c-4.28-11.8-9.02-22.9-14.3-33.2-6.95-13.4-14.8-25.5-23.3-35.9 31.7 11.3 60.1 29.4 83.4 52.7zm-146-65.3c3.75.195 7.47.484 11.2.859 5.89 2.65 11.7 6.25 17.5 11 17.6 14.4 34 38.7 46.4 69.8h-75.1v-81.7zm0 106h83.4c9.48 31.2 15.3 67.3 16.3 106h-99.8v-106zm0 130h99.7c-1.01 38.7-6.87 74.8-16.4 106h-83.3v-106zm28.7 200c-5.78 4.71-11.6 8.31-17.5 11-3.69.375-7.41.664-11.2.86v-81.7h75c-3.11 7.79-6.37 15.3-9.93 22.2-10.6 20.6-23.2 36.9-36.4 47.7zm118-53.5c-23.3 23.3-51.8 41.5-83.5 52.7 15.2-18.4 27.8-41.9 37.8-69.1h60.5c-4.68 5.7-9.62 11.2-14.8 16.4zm32-40.4h-70c9.01-31.9 14.4-67.8 15.4-106h87.9c-2.05 38.8-14 74.9-33.3 106z"/></svg>
+8
assets/svg/linkedin.svg
··· 1 + <!-- Original Author: (Simple Icons)[https://github.com/simple-icons/simple-icons] --> 2 + 3 + <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> 4 + <title>LinkedIn</title> 5 + <path 6 + fill="#0a66c2" 7 + d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" /> 8 + </svg>
+1 -1
gleam.toml
··· 34 34 ] 35 35 stylesheets = [ 36 36 { href = "/styles.css" }, 37 - { href = "https://fontlay.com/css2?family=Lilex&display=swap" }, 37 + { href = "https://fontlay.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&family=Lilex:ital,wght@0,100..700;1,100..700&display=swap" }, 38 38 ] 39 39 title = "Mar's site"
+14
site.css
··· 48 48 margin: 0; 49 49 } 50 50 51 + #cv-a4 { 52 + font-family: "Lato", sans-serif; 53 + font-weight: 400; 54 + font-style: normal; 55 + font-size: var(--font-size); 56 + line-height: var(--line-height); 57 + --margin: 18px; 58 + --font-size: 13px; 59 + --line-height: 1.4; 60 + --color-foreground: #000000; 61 + --color-primary: #3b5417; 62 + --color-background: #ffffff; 63 + } 64 + 51 65 /* Make sure the 'go comment' button is visible even on small screens. */ 52 66 .widget .form-controls { 53 67 flex-wrap: wrap;
+3
src/homepage-ffi.mjs
··· 1 + export function set_title(str) { 2 + document.title = str; 3 + }
+257 -107
src/homepage.gleam
··· 1 + import gleam/io 2 + 1 3 /// Post data ------------------------------------------------------------------- 2 4 /// 3 5 /// Some special tags: ··· 606 608 import gleam/uri.{type Uri} 607 609 import homepage/djotparse 608 610 import homepage/from_prebuild/data 611 + import homepage/view/cv 609 612 import lustre 610 613 import lustre/attribute.{type Attribute, attribute} 611 614 import lustre/effect.{type Effect} ··· 720 723 Tagged(String) 721 724 Links 722 725 PersonalStartPage 726 + CuriculumVitae(in_dutch: Bool) 723 727 } 724 728 725 729 pub fn posts_entries() -> List(FeedEntry) { ··· 928 932 ) 929 933 930 934 ["me"] -> Me 935 + 936 + ["cv", "en"] | ["curiculim-vitae", "en"] | ["cv"] | ["curiculim-vitae"] -> 937 + CuriculumVitae(False) 938 + ["cv", "nl"] | ["curiculum-vitae", "nl"] -> CuriculumVitae(True) 939 + 931 940 ["me", "links"] -> Links 932 941 ["me", "portfolio"] -> Portfolio 933 942 ··· 941 950 } 942 951 } 943 952 953 + pub fn to_title(route: Route) -> String { 954 + let n = "Mar's Site" 955 + let m = fn(v) { v <> " — " <> n } 956 + case route { 957 + CuriculumVitae(False) -> "MLC Bloeiman / My Curiculum Vitae" 958 + CuriculumVitae(True) -> "MLC Bloeiman / Mijn Curiculum Vitae" 959 + PersonalStartPage -> "Startpage" 960 + Links -> "My links and profiles" |> m 961 + Category(c) -> "Posts about '" <> c <> "'" |> m 962 + AllAndEverything -> "...Everything!" |> m 963 + NotFound(uri:) -> "Not found: " <> uri |> uri.to_string() |> m 964 + Portfolio -> "Portfolio" |> m 965 + Me -> "Me" |> m 966 + PostById(id:) -> { 967 + let o = list.find(posts(), fn(p) { p.id == id }) 968 + case o { 969 + Ok(post) -> { 970 + post.title |> m 971 + } 972 + _ -> "Post" |> m 973 + } 974 + } 975 + Posts -> "Posts" |> m 976 + Index -> n 977 + _ -> n 978 + } 979 + } 980 + 944 981 pub fn to_url(route: Route) -> String { 945 982 case route { 946 983 Index -> "/" ··· 969 1006 Tagged(v) -> "/posts/tagged/" <> v 970 1007 Category(c) -> "/posts/category/" <> c 971 1008 Sitemap -> "/sitemap" 1009 + CuriculumVitae(False) -> "/cv/en" 1010 + CuriculumVitae(True) -> "/cv/nl" 972 1011 } 973 1012 } 974 1013 ··· 997 1036 let model = Model(route:, posts:, aliases:) 998 1037 999 1038 let effect = 1000 - modem.init(fn(uri: Uri) -> Msg { 1001 - uri 1002 - |> parse_route(model) 1003 - |> UserNavigatedTo 1004 - }) 1039 + [ 1040 + modem.init(fn(uri: Uri) -> Msg { 1041 + uri 1042 + |> parse_route(model) 1043 + |> UserNavigatedTo 1044 + }), 1045 + { change_title(route) }, 1046 + ] 1047 + |> effect.batch() 1005 1048 1006 1049 #(model, effect) 1007 1050 } 1008 1051 1052 + @external(javascript, "./homepage-ffi", "set_title") 1053 + fn set_title(to: String) -> Nil { 1054 + io.println("Set title to " <> to) 1055 + } 1056 + 1057 + fn change_title(route to: Route) { 1058 + use _ <- effect.from() 1059 + set_title(to_title(to)) 1060 + } 1061 + 1009 1062 fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { 1010 1063 case msg { 1011 - UserNavigatedTo(route:) -> #(Model(..model, route:), effect.none()) 1064 + UserNavigatedTo(route:) -> #(Model(..model, route:), change_title(route:)) 1012 1065 } 1013 1066 } 1014 1067 ··· 1108 1161 NotFound(_) -> view_not_found() |> into_main_with_badges 1109 1162 AllAndEverything -> view_all_and_everything(model) |> into_main 1110 1163 PersonalStartPage -> view_personal_start_page() |> into_main_with_badges 1164 + CuriculumVitae(in_dutch) -> cv.view(in_dutch) 1111 1165 } 1112 1166 }, 1113 1167 1114 1168 html.footer( 1115 1169 [ 1116 1170 attribute.class( 1117 - "footer sm:footer-horizontal footer-center bg-base-300 text-base-content p-4 text-xs", 1171 + "footer sm:footer-horizontal footer-center bg-base-300 text-base-content p-4 text-xs z-[40]", 1118 1172 ), 1119 1173 ], 1120 1174 [ ··· 1178 1232 attribute.class( 1179 1233 "navbar bg-secondary shadow-sm border-accent-content border-b-2", 1180 1234 ), 1235 + attribute.id("site-navbar"), 1181 1236 ], 1182 1237 [ 1183 1238 html.div([attribute.class("navbar-start")], [ ··· 1352 1407 } 1353 1408 1354 1409 fn view_portfolio() -> List(Element(Msg)) { 1410 + let active_projects = [ 1411 + view_portfolio_card_active( 1412 + id: "rac", 1413 + tech: ["Gleam", "Lustre", "Tauri", "Rust", "School"], 1414 + name: "Ranger Among Critters", 1415 + description: "An old-school-pokemon-inspired game, I picked up again as a school project. Made with Gleam, Lustre and Tauri.", 1416 + wip: True, 1417 + more: None, 1418 + links: [ 1419 + #( 1420 + "Forgejo", 1421 + "https://forge.strawmelonjuice.com/mlcbloeiman-school/HBO-ICT_startsemester_ranger-among-critters", 1422 + ), 1423 + ], 1424 + ), 1425 + view_portfolio_card_active( 1426 + id: "sweetnhouse", 1427 + tech: ["Gleam", "Lustre"], 1428 + name: "Sweet n House", 1429 + description: "Household planning and management software for neurodivergent households.", 1430 + wip: True, 1431 + more: None, 1432 + links: [ 1433 + #( 1434 + "Forgejo", 1435 + "https://forge.strawmelonjuice.com/strawmelonjuice/SweetnHouse", 1436 + ), 1437 + ], 1438 + ), 1439 + view_portfolio_card_active( 1440 + id: "luminaproject", 1441 + tech: ["Rust", "Gleam"], 1442 + name: "Lumina", 1443 + description: "Federated social media focused on media sharing and \"bubbles.\"", 1444 + more: Some([ 1445 + html.p([attribute.class("mt-6")], [ 1446 + html.text( 1447 + "One of my oldest concepts still alive. A social media-ish project, obviously slightly overambitious :)", 1448 + ), 1449 + html.br([]), 1450 + html.text( 1451 + "Lumina is a federated social media platform, similar to maybe Mastodon, but with a focus on media sharing and communities (\"bubbles\", similar to Facebook groups but without the administration that comes with them). And with a different, maybe more old-school, approach to federation. The project is still in its early stages, but I do work on it whenever I can.", 1452 + ), 1453 + ]), 1454 + html.p([attribute.class("mt-6")], [ 1455 + html.text( 1456 + "Lumina is also referred to as \"Lumina/Peonies\", this is because Peonies will possibly be the official instance of Lumina, once it's ready.", 1457 + ), 1458 + ]), 1459 + html.p([attribute.class("mt-6")], [ 1460 + html.a( 1461 + [ 1462 + attribute.href( 1463 + "https://tangled.org/strawmelonjuice.com/Lumina/src/branch/development/notes", 1464 + ), 1465 + attribute.class("link-info-content underline"), 1466 + ], 1467 + [ 1468 + html.text("Read Lumina's dev notes here"), 1469 + ], 1470 + ), 1471 + ]), 1472 + ]), 1473 + links: [ 1474 + #("Tangled", "https://tangled.org/strawmelonjuice.com/Lumina/"), 1475 + ], 1476 + wip: True, 1477 + ), 1478 + 1479 + view_portfolio_card_active( 1480 + id: "at-zero", 1481 + tech: ["Python", "Rust", "AT Protocol", "ZeroNet"], 1482 + name: "AT-Zero", 1483 + description: "Using the AT Protocol on top of ZeroNet to combine easy access with true decentralization. A long shot.", 1484 + more: None, 1485 + links: [ 1486 + #("Tangled", "https://tangled.org/strawmelonjuice.com/AT-Zero"), 1487 + ], 1488 + wip: True, 1489 + ), 1490 + view_portfolio_card_active( 1491 + description: "The very website you're on right now. Made with Gleam and Lustre, and a lot of love.", 1492 + links: [ 1493 + #("Homepage", "/"), 1494 + #( 1495 + "Forgejo", 1496 + "https://forge.strawmelonjuice.com/strawmelonjuice/homepage", 1497 + ), 1498 + ], 1499 + id: "homepage", 1500 + tech: ["Gleam", "Lustre"], 1501 + name: "This website!", 1502 + more: None, 1503 + wip: True, 1504 + ), 1505 + ] 1355 1506 [ 1356 1507 html.div([attribute.class("p-8 max-w-6xl mx-auto")], [ 1357 1508 html.h1([attribute.class("text-4xl font-bold mb-8")], [ ··· 1365 1516 ], 1366 1517 [html.text("Active Projects")], 1367 1518 ), 1368 - html.div( 1369 - [ 1370 - attribute.class("grid grid-cols-1 md:grid-cols-3 gap-3 mb-12 w-full"), 1371 - ], 1372 - [ 1373 - view_portfolio_card_active( 1374 - id: "rac", 1375 - tech: ["Gleam", "Lustre", "Tauri", "Rust", "School"], 1376 - name: "Ranger Among Critters", 1377 - description: "An old-school-pokemon-inspired game, I picked up again as a school project. Made with Gleam, Lustre and Tauri.", 1378 - wip: True, 1379 - more: None, 1380 - links: [ 1381 - #( 1382 - "Forgejo", 1383 - "https://forge.strawmelonjuice.com/mlcbloeiman-school/HBO-ICT_startsemester_ranger-among-critters", 1519 + case active_projects |> list.shuffle() { 1520 + [] -> element.none() 1521 + [a] -> 1522 + html.div( 1523 + [ 1524 + attribute.class( 1525 + "grid grid-cols-1 md:grid-cols-3 gap-3 mb-12 w-full", 1384 1526 ), 1385 1527 ], 1386 - ), 1387 - view_portfolio_card_active( 1388 - id: "sweetnhouse", 1389 - tech: ["Gleam", "Lustre"], 1390 - name: "Sweet n House", 1391 - description: "Household planning and management software for neurodivergent households.", 1392 - wip: True, 1393 - more: None, 1394 - links: [ 1395 - #( 1396 - "Forgejo", 1397 - "https://forge.strawmelonjuice.com/strawmelonjuice/SweetnHouse", 1528 + [a], 1529 + ) 1530 + [a, b] -> 1531 + html.div( 1532 + [ 1533 + attribute.class( 1534 + "grid grid-cols-1 md:grid-cols-3 gap-3 mb-12 w-full", 1398 1535 ), 1399 1536 ], 1400 - ), 1401 - view_portfolio_card_active( 1402 - id: "luminaproject", 1403 - tech: ["Rust", "Gleam"], 1404 - name: "Lumina", 1405 - description: "Federated social media focused on media sharing and \"bubbles.\"", 1406 - more: Some([ 1407 - html.p([attribute.class("mt-6")], [ 1408 - html.text( 1409 - "One of my oldest concepts still alive. A social media-ish project, obviously slightly overambitious :)", 1410 - ), 1411 - html.br([]), 1412 - html.text( 1413 - "Lumina is a federated social media platform, similar to maybe Mastodon, but with a focus on media sharing and communities (\"bubbles\", similar to Facebook groups but without the administration that comes with them). And with a different, maybe more old-school, approach to federation. The project is still in its early stages, but I do work on it whenever I can.", 1414 - ), 1415 - ]), 1416 - html.p([attribute.class("mt-6")], [ 1417 - html.text( 1418 - "Lumina is also referred to as \"Lumina/Peonies\", this is because Peonies will possibly be the official instance of Lumina, once it's ready.", 1419 - ), 1420 - ]), 1421 - html.p([attribute.class("mt-6")], [ 1422 - html.a( 1423 - [ 1424 - attribute.href( 1425 - "https://tangled.org/strawmelonjuice.com/Lumina/src/branch/development/notes", 1426 - ), 1427 - attribute.class("link-info-content underline"), 1428 - ], 1429 - [ 1430 - html.text("Read Lumina's dev notes here"), 1431 - ], 1432 - ), 1433 - ]), 1434 - ]), 1435 - links: [ 1436 - #("Tangled", "https://tangled.org/strawmelonjuice.com/Lumina/"), 1437 - ], 1438 - wip: True, 1439 - ), 1537 + [a, b], 1538 + ) 1440 1539 1441 - view_portfolio_card_active( 1442 - id: "at-zero", 1443 - tech: ["Python", "Rust", "AT Protocol", "ZeroNet"], 1444 - name: "AT-Zero", 1445 - description: "Using the AT Protocol on top of ZeroNet to combine easy access with true decentralization. A long shot.", 1446 - more: None, 1447 - links: [ 1448 - #("Tangled", "https://tangled.org/strawmelonjuice.com/AT-Zero"), 1449 - ], 1450 - wip: True, 1451 - ), 1452 - view_portfolio_card_active( 1453 - description: "The very website you're on right now. Made with Gleam and Lustre, and a lot of love.", 1454 - links: [ 1455 - #("Homepage", "/"), 1456 - #( 1457 - "Forgejo", 1458 - "https://forge.strawmelonjuice.com/strawmelonjuice/homepage", 1540 + [a, b, c] -> 1541 + html.div( 1542 + [ 1543 + attribute.class( 1544 + "grid grid-cols-1 md:grid-cols-3 gap-3 mb-12 w-full", 1459 1545 ), 1460 1546 ], 1461 - id: "homepage", 1462 - tech: ["Gleam", "Lustre"], 1463 - name: "This website!", 1464 - more: None, 1465 - wip: True, 1466 - ), 1467 - ] 1468 - |> list.shuffle(), 1469 - ), 1547 + [a, b, c], 1548 + ) 1549 + 1550 + [project_1, project_2, project_3, ..other_projects] -> { 1551 + element.fragment([ 1552 + html.div( 1553 + [ 1554 + attribute.class( 1555 + "grid grid-cols-1 md:grid-cols-3 gap-3 mb-12 w-full", 1556 + ), 1557 + ], 1558 + [project_1, project_2, project_3], 1559 + ), 1560 + html.div( 1561 + [ 1562 + attribute.class( 1563 + "collapse collapse-arrow bg-base-100 border-base-300 border", 1564 + ), 1565 + attribute("tabindex", "0"), 1566 + ], 1567 + [ 1568 + html.div([attribute.class("collapse-title font-semibold")], [ 1569 + html.text( 1570 + "These are just three of my active projects, want to see some more?", 1571 + ), 1572 + ]), 1573 + html.div([attribute.class("collapse-content text-sm")], [ 1574 + html.div( 1575 + [ 1576 + attribute.class("carousel rounded-box"), 1577 + ], 1578 + other_projects 1579 + |> list.map(fn(card) { 1580 + html.div([attribute.class("carousel-item w-2/5")], [ 1581 + card, 1582 + ]) 1583 + }), 1584 + ), 1585 + ]), 1586 + ], 1587 + ), 1588 + ]) 1589 + } 1590 + }, 1470 1591 html.h2( 1471 1592 [ 1472 1593 attribute.class( 1473 - "text-2xl font-semibold mb-6 border-b border-base-300 pb-2", 1594 + "text-2xl font-semibold mb-6 border-b border-base-300 pb-2 mt-14", 1474 1595 ), 1475 1596 ], 1476 1597 [html.text("2025: Academic & Professional")], ··· 2476 2597 [ 2477 2598 html.img([ 2478 2599 attribute.class("w-full max-h-10 p-1 rounded-sm"), 2479 - attribute.src("/img/GitHub_Lockup_Black.svg"), 2600 + attribute.src("/svg/GitHub_Lockup_Black.svg"), 2480 2601 ]), 2481 2602 ], 2482 2603 ), ··· 2525 2646 [ 2526 2647 html.img([ 2527 2648 attribute.class("h-10"), 2528 - attribute.src("/img/tangled.svg"), 2649 + attribute.src("/svg/dolly.svg"), 2529 2650 ]), 2530 2651 html.span([attribute.class("font-bold text-xl hidden md:inline")], [ 2531 2652 html.text("tangled"), ··· 2540 2661 } 2541 2662 2542 2663 fn view_not_found() -> List(Element(Msg)) { 2664 + let first = 2665 + "You glimpse into the void and see -- nothing? 2666 + Well that was somewhat expected." 2543 2667 [ 2544 2668 title("Not found"), 2545 2669 paragraph( 2546 - "You glimpse into the void and see -- nothing? 2547 - Well that was somewhat expected.", 2670 + case 2671 + [ 2672 + first, 2673 + "You've wandered off the map. There are no monsters here, just an endless, pixelated silence.", 2674 + "This page has achieved enlightenment and transcended the physical server. It no longer exists in this dimension.", 2675 + "Error 404: The digital equivalent of walking into a room and forgetting why you came here.", 2676 + "Everything you see here is a hallucination. Especially this 404 page.", 2677 + "You reached the end of the internet. Please turn around and head back the way you came.", 2678 + "404: A quiet space for reflection. Or just a broken link. Mostly the broken link thing.", 2679 + "Lost in the sauce? This page certainly is.", 2680 + "Nothing to see here. Move along, citizen.", 2681 + "You picked a bad time to get lost, friend!", 2682 + "This page is currently 'Works on my machine'—but apparently not on yours.", 2683 + "A 404 error has occurred. Please pet your nearest cat and try again.", 2684 + "You took a wrong turn at the last hyperlink. Care for a juice box while you find your way back?", 2685 + "Oh no! The pixels for this page haven't been delivered yet. The digital mail is running late.", 2686 + "You found a secret area! Unfortunately, the secret is that there’s nothing here.", 2687 + "Error 404: The hamsters powering this specific page are currently on their lunch break.", 2688 + "404: Under Construction... indefinitely. (Just kidding, it's just gone.)", 2689 + "This URL did not exist. Please refresh your browser or your outlook on life.", 2690 + "Nope, not here!", 2691 + ] 2692 + |> list.shuffle() 2693 + |> list.first() 2694 + { 2695 + Error(_) -> first 2696 + Ok(r) -> r 2697 + }, 2548 2698 ), 2549 2699 ] 2550 2700 }
+1531
src/homepage/view/cv.gleam
··· 1 + import gleam/dict 2 + import lustre/attribute.{attribute} 3 + import lustre/element.{type Element} 4 + import lustre/element/html 5 + 6 + pub fn view(in_dutch: Bool) -> Element(a) { 7 + // Dict(keyname, #(english, dutch)) 8 + let translations = 9 + [ 10 + #("langswitch", #("🇳🇱 NL", "🇬🇧 EN")), 11 + #("header-summary", #("About", "Over")), 12 + #("header-profiles", #("Profiles", "Online")), 13 + #("header-interests", #("Interests", "Interesses")), 14 + #("header-skills", #("Skills", "Vaardigheden")), 15 + #("header-experience", #("Experience", "Ervaring")), 16 + #("subheader-experience-older", #("Earlier", "Eerder")), 17 + #("header-education", #("Education", "Opleidingen")), 18 + #("header-languages", #("Languages", "Talen")), 19 + #("lang-dut", #("Dutch", "Nederlands")), 20 + #("lang-eng", #("English", "Engels")), 21 + #("word-native", #("Native", "Moedertaal")), 22 + #("cambridge-daily", #( 23 + "Daily speaker, Cambridge C1", 24 + "Dagelijkse spreker, Cambridge C1 niveau.", 25 + )), 26 + #("attribution-1", #("Designed with ", "Ontworpen met hulp van ")), 27 + #("attribution-2", #(", delivered in Gleam. ", ", uitgevoerd in Gleam. ")), 28 + #("present", #("Present", "Heden")), 29 + #("education-fontys-institute", #( 30 + "Fontys University of Applied Sciences", 31 + "Fontys Hogeschool", 32 + )), 33 + #("education-fontys-level", #("Bachelor", "HBO/Bachelor")), 34 + #("education-fontys-orientation", #( 35 + "ICT / Software Engineering", 36 + "ICT / Software Engineering", 37 + )), 38 + #("education-fontys-summary", #("", "")), 39 + #("education-kw1c-institute", #( 40 + "Koning Willem 1 College", 41 + "Koning Willem 1 College - Den Bosch", 42 + )), 43 + #("education-kw1c-level", #("College", "MBO 4+")), 44 + #("education-kw1c-orientation", #( 45 + "Software Development", 46 + "Softwareontwikkeling", 47 + )), 48 + #("education-kw1c-period", #( 49 + "February 2024 - January 2026", 50 + "Februari 2024 - Januari 2026", 51 + )), 52 + #("education-kw1c-summary", #( 53 + "Finished with submodule certificates before switching to Fontys.", 54 + "Afgerond met deelcertificaten voor mijn afslag naar Fontys.", 55 + )), 56 + #("education-bs-institute", #("Berkenschutse", "De Berkenschutse, Heeze")), 57 + #("education-bs-level", #("MAVO/HAVO", "MAVO/HAVO")), 58 + #("education-bs-orientation", #( 59 + "Theoretical learning path / Economics and Society (E&M)", 60 + "Theoretische leerweg / Economie en Maatschappij (E&M)", 61 + )), 62 + #("education-bs-period", #( 63 + "February 2024 - January 2026", 64 + "Februari 2024 - Januari 2026", 65 + )), 66 + #("education-bs-summary", #("", "")), 67 + #("education-stercollege-institute", #( 68 + "Ster College", 69 + "Ster College Eindhoven", 70 + )), 71 + #("education-stercollege-level", #("HAVO", "HAVO/VAVO")), 72 + #("education-stercollege-orientation", #( 73 + "Culture and society (C&M)", 74 + "Cultuur en maatschappij (C&M)", 75 + )), 76 + #("education-stercollege-summary", #("", "")), 77 + #("education-roc-institute", #("ROC", "ROC Tilburg")), 78 + #("education-roc-level", #("College", "MBO4")), 79 + #("education-roc-orientation", #( 80 + "Social / Disability Care", 81 + "Maatschappelijke Zorg", 82 + )), 83 + #("education-roc-summary", #("", "")), 84 + #("experience-asml-internship-title", #("ASML", "ASML")), 85 + #("experience-asml-internship-tagline", #( 86 + "Internship software engineering", 87 + "Stage als software engineer", 88 + )), 89 + #("experience-asml-internship-summary", #( 90 + "This internship solidified my passion for software architecture and inspired me to switch from college to pursuing a Bachelor’s degree at Fontys University.", 91 + "Deze stage heeft mijn passie voor softwarearchitectuur bevestigd en me geïnspireerd om van het mbo over te stappen naar een hbo-opleiding aan Fontys Hogeschool.", 92 + )), 93 + #("experience-maintain-oss-title", #("", "")), 94 + #("experience-maintain-oss-tagline", #( 95 + "Contributing to and developing Open-Source Software.", 96 + "Bijdragen aan en ontwikkeling van open-source-software.", 97 + )), 98 + #("experience-maintain-oss-summary", #( 99 + "Maintenance and development of multiple small-scale open-source-software projects some of those in use at small to mid-sized companies.", 100 + "Maintainer meerdere kleinschalige opensourcesoftwareprojecten met gebruiken door kleinere tot middelgrote bedrijven.", 101 + )), 102 + #("experience-maintain-oss-location", #( 103 + "Online and at meets", 104 + "Online en bij congressen", 105 + )), 106 + #("experience-ksf-title", #("Korean Soul Food", "Korean Soul Food")), 107 + #("experience-ksf-tagline", #("Catering services", "Horecamedewerker")), 108 + #("experience-ksf-summary", #( 109 + "Servicing, maintaining customer contacts, coordinating home-delivery/takeout counter, preparation of some cold dishes", 110 + "Bediening, onderhouden van klantcontacten, coördineren van de thuisbezorging/afhaalbalie, en het bereiden van enkele koude gerechten", 111 + )), 112 + #("experience-spotta-title", #("Spotta", "Spotta")), 113 + #("experience-spotta-tagline", #("Brochure delivery", "Folderbezorging")), 114 + #("experience-spotta-summary", #( 115 + "Deliverer of weekend brochures and sometimes specific pamphlets.", 116 + "Bezorgen van weekendfolders en soms speciale pamfletten.", 117 + )), 118 + #("interests-coding", #("Writing code", "Schrijven van code")), 119 + #("interests-reading", #( 120 + "Reading (fiction, philosophy)", 121 + "Lezen (fictie, filosofie)", 122 + )), 123 + #("interests-music", #( 124 + "Listening to music, visiting bands", 125 + "Luisteren naar muziek, bands bezoeken", 126 + )), 127 + #("interests-party", #( 128 + "Member of political party", 129 + "Lid van een politieke partij", 130 + )), 131 + #("interests-horse", #( 132 + "Horse care, with the occasional ride.", 133 + "Paardenverzorging, en af en toe ook een ritje.", 134 + )), 135 + #("skills-language-title", #( 136 + "Good verbal and written language skills in both Dutch and English.", 137 + "Goede gesproken en geschreven kennis in zowel Nederlands als Engels.", 138 + )), 139 + #("skills-rust-title", #( 140 + "Rust (programming language)", 141 + "Rust (programmeertaal)", 142 + )), 143 + #("skills-gleam-title", #( 144 + "Gleam (programming language)", 145 + "Gleam (programmeertaal)", 146 + )), 147 + #("skills-php-title", #( 148 + "PHP (programming language)", 149 + "PHP (programmeertaal)", 150 + )), 151 + #("skills-php-desc", #( 152 + "7 & 8, SymfonyPHP, API's and fullstack applications", 153 + "7 & 8, SymfonyPHP, API's en volledige applicaties", 154 + )), 155 + #("view-source", #("[source code]", "[broncode]")), 156 + #("translate", #("to Dutch", "naar Engels")), 157 + #("eindhoven-nl", #( 158 + "Eindhoven, The Netherlands", 159 + "Eindhoven, Noord-Brabant", 160 + )), 161 + #("headline", #( 162 + "Driven by creativity, activated by curiosity.", 163 + "Gedreven door creativiteit, geactiveerd door nieuwsgierigheid.", 164 + )), 165 + #("summary-summary", #( 166 + "I am deeply passionate about the intersection of people, society, and developing things! As a versatile professional, I thrive on navigating complex challenges and driving meaningful impact across diverse environments.", 167 + "Ik heb een enorme passie voor het snijvlak van mens, maatschappij en ontwikkeling! Als veelzijdige professional krijg ik energie van het aanpakken van complexe uitdagingen en het maken van een betekenisvolle impact in uiteenlopende omgevingen.", 168 + )), 169 + ] 170 + |> dict.from_list() 171 + let say = fn(keyname) { 172 + case translations |> dict.get(keyname) { 173 + Ok(v) -> 174 + case in_dutch { 175 + True -> v.1 176 + False -> v.0 177 + } 178 + |> element.text() 179 + 180 + _ -> { 181 + html.p([attribute.class("text-warning text-4xl")], [ 182 + element.text("No translation for: " <> keyname), 183 + ]) 184 + } 185 + } 186 + } 187 + html.section( 188 + [ 189 + attribute.class("bg-slate-300 fixed inset-0 z-[20] overflow-y-auto"), 190 + attribute.id("cvbg"), 191 + ], 192 + [ 193 + html.style( 194 + [], 195 + " 196 + @media print { 197 + * { 198 + overflow: hidden; 199 + -webkit-print-color-adjust: exact !important; 200 + print-color-adjust: exact !important; 201 + 202 + @page { 203 + size: a4 !important; 204 + margin: 0mm !important; 205 + } 206 + } 207 + #site-navbar,#cvmenu,footer 208 + { 209 + display: none !important; 210 + } 211 + #cv-a4 { 212 + height: 100vh !important; 213 + width: 100vw !important; 214 + margin: 0 !important; 215 + } 216 + #cvwrap { 217 + margin-top: 0px !important; 218 + margin-bottom: 0px !important; 219 + } 220 + } 221 + 222 + ", 223 + ), 224 + html.nav( 225 + [ 226 + attribute.class("fixed top-3 right-6 m-0 z-20"), 227 + attribute.id("cvmenu"), 228 + ], 229 + [ 230 + html.ul( 231 + [attribute.class("menu menu-horizontal bg-base-200 rounded-box ")], 232 + [ 233 + html.li([], [ 234 + html.a( 235 + [ 236 + attribute.href(case in_dutch { 237 + False -> "/cv/nl" 238 + True -> "/cv/en" 239 + }), 240 + attribute.class("tooltip tooltip-bottom"), 241 + ], 242 + [ 243 + html.div([attribute.class("tooltip-content z-[30]")], [ 244 + say("translate"), 245 + ]), 246 + say("langswitch"), 247 + ], 248 + ), 249 + ]), 250 + ], 251 + ), 252 + ], 253 + ), 254 + html.div([attribute.class("md:my-14"), attribute.id("cvwrap")], [ 255 + html.div( 256 + [ 257 + attribute.class( 258 + "min-h-[297mm] w-full max-w-[210mm] bg-white m-auto shadow-2xl md:my-10", 259 + ), 260 + attribute.id("cv-a4"), 261 + ], 262 + view_cv_inner(say), 263 + ), 264 + ]), 265 + html.div( 266 + [ 267 + attribute.class("w-full flex justify-center flex-wrap mb-5"), 268 + ], 269 + [ 270 + html.span( 271 + [attribute.class("flex-item h-15 w-fit text-mist-500 text-xs")], 272 + [ 273 + say("attribution-1"), 274 + html.a( 275 + [ 276 + attribute.class("link link-mauve-300"), 277 + attribute.href( 278 + "https://github.com/amruthpillai/reactive-resume", 279 + ), 280 + ], 281 + [element.text("Reactive Resume")], 282 + ), 283 + 284 + say("attribution-2"), 285 + html.a( 286 + [ 287 + attribute.class("link link-mauve-300"), 288 + attribute.href( 289 + "https://forge.strawmelonjuice.com/strawmelonjuice/homepage", 290 + ), 291 + ], 292 + [say("view-source")], 293 + ), 294 + ], 295 + ), 296 + ], 297 + ), 298 + ], 299 + ) 300 + } 301 + 302 + fn view_cv_inner(localise: fn(String) -> Element(a)) { 303 + [ 304 + html.div( 305 + [ 306 + attribute.class( 307 + "relative bg-background text-foreground underline-links", 308 + ), 309 + ], 310 + [ 311 + html.div([], [ 312 + html.div([attribute.class("relative")], [ 313 + html.div( 314 + [ 315 + attribute.class("relative grid grid-cols-3 space-x-4 p-4 pb-0"), 316 + ], 317 + [ 318 + html.img([ 319 + attribute( 320 + "style", 321 + "max-width: 80px; aspect-ratio: 1 / 1; border-radius: 6px; border-width: 0px;", 322 + ), 323 + attribute.class("relative z-20 object-cover mx-auto mt-8"), 324 + attribute.src( 325 + "https://media.strawmelonjuice.com/file/019d15b9-0ed4-7000-a700-1a3aa109d036/iqmgs5eql7zlrn06yh8931m4.jpg", 326 + ), 327 + attribute.alt("Profile"), 328 + ]), 329 + html.div( 330 + [attribute.class("relative z-15 col-span-2 text-background")], 331 + [ 332 + html.div([attribute.class("space-y-0.5 ")], [ 333 + html.h2( 334 + [attribute.class("text-3xl font-bold text-white")], 335 + [ 336 + element.text("MLC Bloeiman"), 337 + ], 338 + ), 339 + html.p([attribute.class("text-white")], [ 340 + localise("headline"), 341 + ]), 342 + ]), 343 + html.div( 344 + [ 345 + attribute.class( 346 + "col-span-2 col-start-2 mt-10 text-foreground", 347 + ), 348 + ], 349 + [ 350 + html.div( 351 + [ 352 + attribute.class( 353 + "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm", 354 + ), 355 + ], 356 + [ 357 + html.div( 358 + [attribute.class("flex items-center gap-x-1.5")], 359 + [ 360 + html.img([ 361 + attribute.class("text-primary h-4"), 362 + attribute.src("/svg/icon_loc.svg"), 363 + attribute.alt("From: "), 364 + ]), 365 + html.div([], [localise("eindhoven-nl")]), 366 + ], 367 + ), 368 + html.div( 369 + [ 370 + attribute.class( 371 + "bg-text size-1 rounded-full last:hidden", 372 + ), 373 + ], 374 + [], 375 + ), 376 + html.div( 377 + [attribute.class("flex items-center gap-x-1.5")], 378 + [ 379 + html.img([ 380 + attribute.class("text-primary h-4"), 381 + attribute.src("/svg/icon_phone.svg"), 382 + ]), 383 + html.a( 384 + [ 385 + attribute.rel("noreferrer"), 386 + attribute.class("link"), 387 + attribute.target("_blank"), 388 + attribute.href("tel:+31648728945"), 389 + ], 390 + [element.text("+31648728945")], 391 + ), 392 + ], 393 + ), 394 + html.div( 395 + [ 396 + attribute.class( 397 + "bg-text size-1 rounded-full last:hidden", 398 + ), 399 + ], 400 + [], 401 + ), 402 + html.div( 403 + [attribute.class("flex items-center gap-x-1.5")], 404 + [ 405 + html.img([ 406 + attribute.class("text-primary h-4"), 407 + attribute.src("/svg/icon_mail.svg"), 408 + attribute.alt("Mail: "), 409 + ]), 410 + html.a( 411 + [ 412 + attribute.rel("noreferrer"), 413 + attribute.target("_blank"), 414 + attribute.alt("Phone: "), 415 + attribute.class("link"), 416 + attribute.href( 417 + "mailto:mlcbloeiman@outlook.com", 418 + ), 419 + ], 420 + [element.text("mlcbloeiman@outlook.com")], 421 + ), 422 + ], 423 + ), 424 + html.div( 425 + [ 426 + attribute.class( 427 + "bg-text size-1 rounded-full last:hidden", 428 + ), 429 + ], 430 + [], 431 + ), 432 + html.div( 433 + [ 434 + attribute.class( 435 + "flex items-center gap-x-1.5 break-all", 436 + ), 437 + ], 438 + [ 439 + html.img([ 440 + attribute.class("text-primary h-4"), 441 + attribute.src("/svg/icon_www.svg"), 442 + attribute.alt("Site: "), 443 + ]), 444 + html.a( 445 + [ 446 + attribute.class( 447 + "line-clamp-1 max-w-fit link", 448 + ), 449 + attribute.rel( 450 + "noreferrer noopener nofollow", 451 + ), 452 + attribute.target("_blank"), 453 + attribute.href( 454 + "https://strawmelonjuice.com/", 455 + ), 456 + ], 457 + [element.text("https://strawmelonjuice.com/")], 458 + ), 459 + ], 460 + ), 461 + html.div( 462 + [ 463 + attribute.class( 464 + "bg-text size-1 rounded-full last:hidden", 465 + ), 466 + ], 467 + [], 468 + ), 469 + ], 470 + ), 471 + ], 472 + ), 473 + ], 474 + ), 475 + ], 476 + ), 477 + html.div( 478 + [ 479 + attribute.class( 480 + "absolute inset-x-0 top-0 h-[115px] md:h-[85px] w-full bg-primary p-5", 481 + ), 482 + ], 483 + [], 484 + ), 485 + ]), 486 + html.div([attribute.class("grid grid-cols-1 md:grid-cols-3 m-5")], [ 487 + html.div([attribute.class("sidebar group space-y-4 p-custom")], [ 488 + html.section([attribute.class("grid"), attribute.id("profiles")], [ 489 + html.h4([attribute.class("mb-2 text-base font-bold")], [ 490 + localise("header-profiles"), 491 + ]), 492 + html.div( 493 + [ 494 + attribute("style", "grid-template-columns: repeat(1, 1fr);"), 495 + attribute.class("grid gap-x-6 gap-y-3"), 496 + ], 497 + [ 498 + html.div( 499 + [ 500 + attribute.class( 501 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 502 + ), 503 + ], 504 + [ 505 + html.div( 506 + [ 507 + attribute.class( 508 + "relative -ml-4 group-[.sidebar]:ml-0", 509 + ), 510 + ], 511 + [ 512 + html.div( 513 + [ 514 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 515 + ], 516 + [ 517 + html.div([], [ 518 + html.div( 519 + [ 520 + attribute.class( 521 + "flex items-center gap-x-1.5 break-all", 522 + ), 523 + ], 524 + [ 525 + html.img([ 526 + attribute.src("/svg/linkedin.svg"), 527 + attribute.class("size-4"), 528 + attribute.alt("LinkedIn"), 529 + ]), 530 + html.a( 531 + [ 532 + attribute.class( 533 + "line-clamp-1 max-w-fit link", 534 + ), 535 + attribute.rel( 536 + "noreferrer noopener nofollow", 537 + ), 538 + attribute.target("_blank"), 539 + attribute.href( 540 + "https://www.linkedin.com/in/mlc-bloeiman-43b34028b/", 541 + ), 542 + ], 543 + [element.text("LinkedIn")], 544 + ), 545 + ], 546 + ), 547 + ]), 548 + ], 549 + ), 550 + html.div( 551 + [ 552 + attribute.class( 553 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 554 + ), 555 + ], 556 + [], 557 + ), 558 + ], 559 + ), 560 + html.div( 561 + [ 562 + attribute.class( 563 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 564 + ), 565 + ], 566 + [], 567 + ), 568 + ], 569 + ), 570 + html.div( 571 + [ 572 + attribute.class( 573 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 574 + ), 575 + ], 576 + [ 577 + html.div( 578 + [ 579 + attribute.class( 580 + "relative -ml-4 group-[.sidebar]:ml-0", 581 + ), 582 + ], 583 + [ 584 + html.div( 585 + [ 586 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 587 + ], 588 + [ 589 + html.div([], [ 590 + html.div( 591 + [ 592 + attribute.class( 593 + "flex items-center gap-x-1.5 break-all", 594 + ), 595 + ], 596 + [ 597 + html.img([ 598 + attribute.src("/svg/dolly.svg"), 599 + attribute.class( 600 + "size-4 bg-black invert", 601 + ), 602 + attribute.alt("git"), 603 + ]), 604 + html.a( 605 + [ 606 + attribute.class( 607 + "line-clamp-1 max-w-fit link", 608 + ), 609 + attribute.rel( 610 + "noreferrer noopener nofollow", 611 + ), 612 + attribute.target("_blank"), 613 + attribute.href( 614 + "https://tangled.org/strawmelonjuice.com/", 615 + ), 616 + ], 617 + [element.text("Tangled")], 618 + ), 619 + ], 620 + ), 621 + ]), 622 + ], 623 + ), 624 + html.div( 625 + [ 626 + attribute.class( 627 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 628 + ), 629 + ], 630 + [], 631 + ), 632 + ], 633 + ), 634 + html.div( 635 + [ 636 + attribute.class( 637 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 638 + ), 639 + ], 640 + [], 641 + ), 642 + ], 643 + ), 644 + html.div( 645 + [ 646 + attribute.class( 647 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 648 + ), 649 + ], 650 + [ 651 + html.div( 652 + [ 653 + attribute.class( 654 + "relative -ml-4 group-[.sidebar]:ml-0", 655 + ), 656 + ], 657 + [ 658 + html.div( 659 + [ 660 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 661 + ], 662 + [ 663 + html.div([], [ 664 + html.div( 665 + [ 666 + attribute.class( 667 + "flex items-center gap-x-1.5 break-all", 668 + ), 669 + ], 670 + [ 671 + html.img([ 672 + attribute.src( 673 + "https://cdn.simpleicons.org/github", 674 + ), 675 + attribute.class("size-4"), 676 + attribute.alt("github"), 677 + ]), 678 + html.a( 679 + [ 680 + attribute.class( 681 + "line-clamp-1 max-w-fit link", 682 + ), 683 + attribute.rel( 684 + "noreferrer noopener nofollow", 685 + ), 686 + attribute.target("_blank"), 687 + attribute.href( 688 + "https://github.com/strawmelonjuice", 689 + ), 690 + ], 691 + [element.text("GitHub")], 692 + ), 693 + ], 694 + ), 695 + ]), 696 + ], 697 + ), 698 + html.div( 699 + [ 700 + attribute.class( 701 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 702 + ), 703 + ], 704 + [], 705 + ), 706 + ], 707 + ), 708 + html.div( 709 + [ 710 + attribute.class( 711 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 712 + ), 713 + ], 714 + [], 715 + ), 716 + ], 717 + ), 718 + ], 719 + ), 720 + ]), 721 + html.section( 722 + [attribute.class("grid"), attribute.id("interests")], 723 + [ 724 + html.h4([attribute.class("mb-2 text-base font-bold")], [ 725 + localise("header-interests"), 726 + ]), 727 + html.ul([attribute.class("list list-disc list-inside")], [ 728 + html.li([], [localise("interests-coding")]), 729 + html.li([], [localise("interests-reading")]), 730 + html.li([], [localise("interests-music")]), 731 + html.li([], [localise("interests-party")]), 732 + html.li([], [localise("interests-horse")]), 733 + ]), 734 + ], 735 + ), 736 + html.section([attribute.class("grid"), attribute.id("skills")], [ 737 + html.h4([attribute.class("mb-2 text-base font-bold")], [ 738 + localise("header-skills"), 739 + ]), 740 + html.div( 741 + [ 742 + attribute("style", "grid-template-columns: repeat(1, 1fr);"), 743 + attribute.class("grid gap-x-6 gap-y-3"), 744 + ], 745 + [ 746 + html.div( 747 + [ 748 + attribute.class( 749 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 750 + ), 751 + ], 752 + [ 753 + html.div( 754 + [ 755 + attribute.class( 756 + "relative -ml-4 group-[.sidebar]:ml-0", 757 + ), 758 + ], 759 + [ 760 + html.div( 761 + [ 762 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 763 + ], 764 + [ 765 + html.div([], [ 766 + html.div([attribute.class("font-bold")], [ 767 + localise("skills-language-title"), 768 + ]), 769 + html.div([], []), 770 + ]), 771 + ], 772 + ), 773 + html.div( 774 + [ 775 + attribute.class( 776 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 777 + ), 778 + ], 779 + [], 780 + ), 781 + ], 782 + ), 783 + html.div( 784 + [ 785 + attribute.class( 786 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 787 + ), 788 + ], 789 + [], 790 + ), 791 + ], 792 + ), 793 + html.div( 794 + [ 795 + attribute.class( 796 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 797 + ), 798 + ], 799 + [ 800 + html.div( 801 + [ 802 + attribute.class( 803 + "relative -ml-4 group-[.sidebar]:ml-0", 804 + ), 805 + ], 806 + [ 807 + html.div( 808 + [ 809 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 810 + ], 811 + [ 812 + html.div([], [ 813 + html.div([attribute.class("font-bold")], [ 814 + localise("skills-rust-title"), 815 + ]), 816 + html.div([], [ 817 + html.text( 818 + "Rocket, Actix, Axum, Ratatui, Tauri, Sync & Async", 819 + ), 820 + ]), 821 + ]), 822 + ], 823 + ), 824 + html.div( 825 + [ 826 + attribute.class( 827 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 828 + ), 829 + ], 830 + [], 831 + ), 832 + ], 833 + ), 834 + html.div( 835 + [attribute.class("flex items-center gap-x-1.5")], 836 + [ 837 + html.div( 838 + [ 839 + attribute.class( 840 + "h-2 w-4 border border-primary bg-primary", 841 + ), 842 + ], 843 + [], 844 + ), 845 + html.div( 846 + [ 847 + attribute.class( 848 + "h-2 w-4 border border-primary bg-primary", 849 + ), 850 + ], 851 + [], 852 + ), 853 + html.div( 854 + [ 855 + attribute.class( 856 + "h-2 w-4 border border-primary bg-primary", 857 + ), 858 + ], 859 + [], 860 + ), 861 + html.div( 862 + [ 863 + attribute.class( 864 + "h-2 w-4 border border-primary bg-primary/25", 865 + ), 866 + ], 867 + [], 868 + ), 869 + html.div( 870 + [ 871 + attribute.class("h-2 w-4 border border-primary"), 872 + ], 873 + [], 874 + ), 875 + ], 876 + ), 877 + html.div( 878 + [ 879 + attribute.class( 880 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 881 + ), 882 + ], 883 + [], 884 + ), 885 + ], 886 + ), 887 + html.div( 888 + [ 889 + attribute.class( 890 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 891 + ), 892 + ], 893 + [ 894 + html.div( 895 + [ 896 + attribute.class( 897 + "relative -ml-4 group-[.sidebar]:ml-0", 898 + ), 899 + ], 900 + [ 901 + html.div( 902 + [ 903 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 904 + ], 905 + [ 906 + html.div([], [ 907 + html.div([attribute.class("font-bold")], [ 908 + localise("skills-gleam-title"), 909 + ]), 910 + html.div([], [ 911 + html.text( 912 + "Lustre, JS interop, Erlang & BEAM", 913 + ), 914 + ]), 915 + ]), 916 + ], 917 + ), 918 + html.div( 919 + [ 920 + attribute.class( 921 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 922 + ), 923 + ], 924 + [], 925 + ), 926 + ], 927 + ), 928 + html.div( 929 + [attribute.class("flex items-center gap-x-1.5")], 930 + [ 931 + html.div( 932 + [ 933 + attribute.class( 934 + "h-2 w-4 border border-primary bg-primary", 935 + ), 936 + ], 937 + [], 938 + ), 939 + html.div( 940 + [ 941 + attribute.class( 942 + "h-2 w-4 border border-primary bg-primary", 943 + ), 944 + ], 945 + [], 946 + ), 947 + html.div( 948 + [ 949 + attribute.class( 950 + "h-2 w-4 border border-primary bg-primary", 951 + ), 952 + ], 953 + [], 954 + ), 955 + html.div( 956 + [ 957 + attribute.class( 958 + "h-2 w-4 border border-primary bg-primary", 959 + ), 960 + ], 961 + [], 962 + ), 963 + html.div( 964 + [ 965 + attribute.class("h-2 w-4 border border-primary"), 966 + ], 967 + [], 968 + ), 969 + ], 970 + ), 971 + html.div( 972 + [ 973 + attribute.class( 974 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 975 + ), 976 + ], 977 + [], 978 + ), 979 + ], 980 + ), 981 + html.div( 982 + [ 983 + attribute.class( 984 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 985 + ), 986 + ], 987 + [ 988 + html.div( 989 + [ 990 + attribute.class( 991 + "relative -ml-4 group-[.sidebar]:ml-0", 992 + ), 993 + ], 994 + [ 995 + html.div( 996 + [ 997 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 998 + ], 999 + [ 1000 + html.div([], [ 1001 + html.div([attribute.class("font-bold")], [ 1002 + localise("skills-php-title"), 1003 + ]), 1004 + html.div([], [localise("skills-php-desc")]), 1005 + ]), 1006 + ], 1007 + ), 1008 + html.div( 1009 + [ 1010 + attribute.class( 1011 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 1012 + ), 1013 + ], 1014 + [], 1015 + ), 1016 + ], 1017 + ), 1018 + html.div( 1019 + [attribute.class("flex items-center gap-x-1.5")], 1020 + [ 1021 + html.div( 1022 + [ 1023 + attribute.class( 1024 + "h-2 w-4 border border-primary bg-primary", 1025 + ), 1026 + ], 1027 + [], 1028 + ), 1029 + html.div( 1030 + [ 1031 + attribute.class( 1032 + "h-2 w-4 border border-primary bg-primary", 1033 + ), 1034 + ], 1035 + [], 1036 + ), 1037 + html.div( 1038 + [ 1039 + attribute.class( 1040 + "h-2 w-4 border border-primary bg-primary", 1041 + ), 1042 + ], 1043 + [], 1044 + ), 1045 + html.div( 1046 + [ 1047 + attribute.class("h-2 w-4 border border-primary"), 1048 + ], 1049 + [], 1050 + ), 1051 + html.div( 1052 + [ 1053 + attribute.class("h-2 w-4 border border-primary"), 1054 + ], 1055 + [], 1056 + ), 1057 + ], 1058 + ), 1059 + html.div( 1060 + [ 1061 + attribute.class( 1062 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 1063 + ), 1064 + ], 1065 + [], 1066 + ), 1067 + ], 1068 + ), 1069 + ], 1070 + ), 1071 + ]), 1072 + html.section( 1073 + [attribute.class("grid"), attribute.id("languages")], 1074 + [ 1075 + html.h4([attribute.class("mb-2 text-base font-bold")], [ 1076 + localise("header-languages"), 1077 + ]), 1078 + html.div( 1079 + [ 1080 + attribute( 1081 + "style", 1082 + "grid-template-columns: repeat(1, 1fr);", 1083 + ), 1084 + attribute.class("grid gap-x-6 gap-y-3"), 1085 + ], 1086 + [ 1087 + html.div( 1088 + [ 1089 + attribute.class( 1090 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 1091 + ), 1092 + ], 1093 + [ 1094 + html.div( 1095 + [ 1096 + attribute.class( 1097 + "relative -ml-4 group-[.sidebar]:ml-0", 1098 + ), 1099 + ], 1100 + [ 1101 + html.div( 1102 + [ 1103 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 1104 + ], 1105 + [ 1106 + html.div([], [ 1107 + html.div([attribute.class("font-bold")], [ 1108 + localise("lang-dut"), 1109 + ]), 1110 + html.div([], [localise("word-native")]), 1111 + ]), 1112 + ], 1113 + ), 1114 + html.div( 1115 + [ 1116 + attribute.class( 1117 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 1118 + ), 1119 + ], 1120 + [], 1121 + ), 1122 + ], 1123 + ), 1124 + html.div( 1125 + [attribute.class("flex items-center gap-x-1.5")], 1126 + [ 1127 + html.div( 1128 + [ 1129 + attribute.class( 1130 + "h-2 w-4 border border-primary bg-primary", 1131 + ), 1132 + ], 1133 + [], 1134 + ), 1135 + html.div( 1136 + [ 1137 + attribute.class( 1138 + "h-2 w-4 border border-primary bg-primary", 1139 + ), 1140 + ], 1141 + [], 1142 + ), 1143 + html.div( 1144 + [ 1145 + attribute.class( 1146 + "h-2 w-4 border border-primary bg-primary", 1147 + ), 1148 + ], 1149 + [], 1150 + ), 1151 + html.div( 1152 + [ 1153 + attribute.class( 1154 + "h-2 w-4 border border-primary bg-primary", 1155 + ), 1156 + ], 1157 + [], 1158 + ), 1159 + html.div( 1160 + [ 1161 + attribute.class( 1162 + "h-2 w-4 border border-primary bg-primary", 1163 + ), 1164 + ], 1165 + [], 1166 + ), 1167 + ], 1168 + ), 1169 + html.div( 1170 + [ 1171 + attribute.class( 1172 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 1173 + ), 1174 + ], 1175 + [], 1176 + ), 1177 + ], 1178 + ), 1179 + html.div( 1180 + [ 1181 + attribute.class( 1182 + "relative space-y-2 pl-4 group-[.sidebar]:pl-0", 1183 + ), 1184 + ], 1185 + [ 1186 + html.div( 1187 + [ 1188 + attribute.class( 1189 + "relative -ml-4 group-[.sidebar]:ml-0", 1190 + ), 1191 + ], 1192 + [ 1193 + html.div( 1194 + [ 1195 + attribute.class("pl-4 group-[.sidebar]:pl-0"), 1196 + ], 1197 + [ 1198 + html.div([], [ 1199 + html.div([attribute.class("font-bold")], [ 1200 + localise("lang-eng"), 1201 + ]), 1202 + html.div([], [ 1203 + localise("cambridge-daily"), 1204 + ]), 1205 + ]), 1206 + ], 1207 + ), 1208 + html.div( 1209 + [ 1210 + attribute.class( 1211 + "absolute inset-y-0 -left-px border-l-4 border-primary group-[.sidebar]:hidden", 1212 + ), 1213 + ], 1214 + [], 1215 + ), 1216 + ], 1217 + ), 1218 + html.div( 1219 + [attribute.class("flex items-center gap-x-1.5")], 1220 + [ 1221 + html.div( 1222 + [ 1223 + attribute.class( 1224 + "h-2 w-4 border border-primary bg-primary", 1225 + ), 1226 + ], 1227 + [], 1228 + ), 1229 + html.div( 1230 + [ 1231 + attribute.class( 1232 + "h-2 w-4 border border-primary bg-primary", 1233 + ), 1234 + ], 1235 + [], 1236 + ), 1237 + html.div( 1238 + [ 1239 + attribute.class( 1240 + "h-2 w-4 border border-primary bg-primary", 1241 + ), 1242 + ], 1243 + [], 1244 + ), 1245 + html.div( 1246 + [ 1247 + attribute.class( 1248 + "h-2 w-4 border border-primary bg-primary", 1249 + ), 1250 + ], 1251 + [], 1252 + ), 1253 + html.div( 1254 + [ 1255 + attribute.class( 1256 + "h-2 w-4 border border-primary bg-primary/50", 1257 + ), 1258 + ], 1259 + [], 1260 + ), 1261 + ], 1262 + ), 1263 + html.div( 1264 + [ 1265 + attribute.class( 1266 + "absolute inset-y-0 left-0 border-l border-primary group-[.sidebar]:hidden", 1267 + ), 1268 + ], 1269 + [], 1270 + ), 1271 + ], 1272 + ), 1273 + ], 1274 + ), 1275 + ], 1276 + ), 1277 + ]), 1278 + html.div( 1279 + [attribute.class("main group space-y-4 p-custom col-span-2")], 1280 + [ 1281 + html.section([attribute.id("summary")], [ 1282 + html.h4([attribute.class("mb-2 text-base font-bold")], [ 1283 + localise("header-summary"), 1284 + ]), 1285 + html.div( 1286 + [ 1287 + attribute("style", "columns: 1;"), 1288 + attribute.class("wysiwyg"), 1289 + ], 1290 + [ 1291 + html.p([], [localise("summary-summary")]), 1292 + ], 1293 + ), 1294 + ]), 1295 + html.section( 1296 + [attribute.class("grid"), attribute.id("experience")], 1297 + [ 1298 + html.h4([attribute.class("mb-2 text-base font-bold")], [ 1299 + localise("header-experience"), 1300 + ]), 1301 + html.div( 1302 + [ 1303 + attribute( 1304 + "style", 1305 + "grid-template-columns: repeat(1, 1fr);", 1306 + ), 1307 + attribute.class("grid gap-x-6 gap-y-3"), 1308 + ], 1309 + [ 1310 + experience_item( 1311 + primary: True, 1312 + localise:, 1313 + name: "asml-internship", 1314 + location: element.text("Veldhoven"), 1315 + period: [element.text("2025")], 1316 + ), 1317 + experience_item( 1318 + primary: True, 1319 + localise:, 1320 + name: "maintain-oss", 1321 + location: localise("experience-maintain-oss-location"), 1322 + period: [ 1323 + html.text("2020 - "), 1324 + localise("present"), 1325 + ], 1326 + ), 1327 + 1328 + // html.h5([attribute.class("text-[15px] font-bold")], [ 1329 + // localise("subheader-experience-older"), 1330 + // ]), 1331 + experience_item( 1332 + primary: False, 1333 + localise:, 1334 + name: "ksf", 1335 + location: html.text("Eindhoven"), 1336 + period: [html.text("2019 – 2024")], 1337 + ), 1338 + experience_item( 1339 + primary: False, 1340 + localise:, 1341 + name: "spotta", 1342 + location: html.text("Eindhoven"), 1343 + period: [html.text("2018 - 2023")], 1344 + ), 1345 + ], 1346 + ), 1347 + ], 1348 + ), 1349 + html.section( 1350 + [attribute.class("grid"), attribute.id("education")], 1351 + [ 1352 + html.h4([attribute.class("mb-2 text-base font-bold")], [ 1353 + localise("header-education"), 1354 + ]), 1355 + html.div( 1356 + [ 1357 + attribute( 1358 + "style", 1359 + "grid-template-columns: repeat(1, 1fr);", 1360 + ), 1361 + attribute.class("grid gap-x-6 gap-y-3"), 1362 + ], 1363 + [ 1364 + education_item( 1365 + primary: True, 1366 + localise:, 1367 + name: "fontys", 1368 + period: [ 1369 + html.text("Februari 2026 - "), 1370 + localise("present"), 1371 + ], 1372 + ), 1373 + education_item( 1374 + primary: True, 1375 + localise:, 1376 + name: "kw1c", 1377 + period: [ 1378 + localise("education-kw1c-period"), 1379 + ], 1380 + ), 1381 + 1382 + education_item( 1383 + primary: False, 1384 + localise:, 1385 + name: "stercollege", 1386 + period: [element.text("2023 - 2024")], 1387 + ), 1388 + education_item( 1389 + primary: False, 1390 + localise:, 1391 + name: "roc", 1392 + period: [element.text("2021 - 2022")], 1393 + ), 1394 + education_item( 1395 + primary: False, 1396 + localise:, 1397 + name: "bs", 1398 + period: [element.text("2016 - 2020")], 1399 + ), 1400 + ], 1401 + ), 1402 + ], 1403 + ), 1404 + ], 1405 + ), 1406 + ]), 1407 + ]), 1408 + ], 1409 + ), 1410 + ] 1411 + } 1412 + 1413 + fn experience_item( 1414 + primary primary: Bool, 1415 + localise localise: fn(String) -> element.Element(a), 1416 + name name: String, 1417 + location location: element.Element(a), 1418 + period period: List(element.Element(a)), 1419 + ) { 1420 + let bordercolour = case primary { 1421 + True -> " border-primary" 1422 + False -> " border-secondary" 1423 + } 1424 + html.div([attribute.class("relative space-y-2 pl-4 group-[.sidebar]:pl-0")], [ 1425 + html.div([attribute.class("relative -ml-4 group-[.sidebar]:ml-0")], [ 1426 + html.div([attribute.class("pl-4 group-[.sidebar]:pl-0")], [ 1427 + html.div( 1428 + [ 1429 + attribute.class( 1430 + "flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start", 1431 + ), 1432 + ], 1433 + [ 1434 + html.div([attribute.class("text-left")], [ 1435 + html.div([attribute.class("font-bold")], [ 1436 + localise("experience-" <> name <> "-title"), 1437 + ]), 1438 + html.div([], [localise("experience-" <> name <> "-tagline")]), 1439 + ]), 1440 + html.div([attribute.class("shrink-0 text-right")], [ 1441 + html.div([attribute.class("font-bold")], period), 1442 + html.div([], [location]), 1443 + ]), 1444 + ], 1445 + ), 1446 + ]), 1447 + html.div( 1448 + [ 1449 + attribute.class( 1450 + "absolute inset-y-0 -left-px border-l-4 group-[.sidebar]:hidden" 1451 + <> bordercolour, 1452 + ), 1453 + ], 1454 + [], 1455 + ), 1456 + ]), 1457 + html.div([attribute.class("wysiwyg")], [ 1458 + html.p([], [localise("experience-" <> name <> "-summary")]), 1459 + ]), 1460 + html.div( 1461 + [ 1462 + attribute.class( 1463 + "absolute inset-y-0 left-0 border-l group-[.sidebar]:hidden" 1464 + <> bordercolour, 1465 + ), 1466 + ], 1467 + [], 1468 + ), 1469 + ]) 1470 + } 1471 + 1472 + fn education_item( 1473 + primary primary: Bool, 1474 + localise localise: fn(String) -> element.Element(a), 1475 + name name: String, 1476 + period period: List(element.Element(a)), 1477 + ) { 1478 + let bordercolour = case primary { 1479 + True -> " border-primary" 1480 + False -> " border-secondary" 1481 + } 1482 + html.div([attribute.class("relative space-y-2 pl-4 group-[.sidebar]:pl-0")], [ 1483 + html.div([attribute.class("relative -ml-4 group-[.sidebar]:ml-0")], [ 1484 + html.div([attribute.class("pl-4 group-[.sidebar]:pl-0")], [ 1485 + html.div( 1486 + [ 1487 + attribute.class( 1488 + "flex items-start justify-between group-[.sidebar]:flex-col group-[.sidebar]:items-start", 1489 + ), 1490 + ], 1491 + [ 1492 + html.div([attribute.class("text-left")], [ 1493 + html.div([attribute.class("font-bold")], [ 1494 + localise("education-" <> name <> "-institute"), 1495 + ]), 1496 + html.div([], [localise("education-" <> name <> "-orientation")]), 1497 + html.div([], []), 1498 + ]), 1499 + html.div([attribute.class("shrink-0 text-right")], [ 1500 + html.div([attribute.class("font-bold")], period), 1501 + html.div([], [localise("education-" <> name <> "-level")]), 1502 + ]), 1503 + ], 1504 + ), 1505 + ]), 1506 + html.div( 1507 + [ 1508 + attribute.class( 1509 + "absolute inset-y-0 -left-px border-l-4 group-[.sidebar]:hidden" 1510 + <> bordercolour, 1511 + ), 1512 + ], 1513 + [], 1514 + ), 1515 + ]), 1516 + html.div([attribute.class("wysiwyg")], [ 1517 + html.p([], [ 1518 + localise("education-" <> name <> "-summary"), 1519 + ]), 1520 + ]), 1521 + html.div( 1522 + [ 1523 + attribute.class( 1524 + "absolute inset-y-0 left-0 border-l group-[.sidebar]:hidden" 1525 + <> bordercolour, 1526 + ), 1527 + ], 1528 + [], 1529 + ), 1530 + ]) 1531 + }